I wrote yesterday about setting up Hunchentoot, a Common Lisp web server running behind Apache, for rendering dynamic web pages in Lisp. What I neglected to mention was how one goes about coding such pages. Fortunately, that’s the easiest part of all, so I wanted to provide a very short primer on getting your first Lisp web pages up and running.
I’m going to assume that you’ve implemented every step in yesterday’s article,
including the Swank server that allows you to connect to a running Hunchentoot
server over SSH, using Emacs and SLIME. This means you’ve already opened your
SSH tunnel to the server, typed M-x slime-connect
, bound it to your local
reflection port, and now have a Lisp REPL talking to your Hunchentoot server.
See yesterday’s article if you still don’t have that running yet. Or
contact me if you’re having a difficult time getting things to work.
With that setup, create a file in Emacs called webserver.lisp
. If you don’t
have SLIME setup locally, try using Lisp in a Box if you’re on Windows or
Linux, or Ready Lisp if you’re on Mac OS X. You’ll know it’s all working if
you see “Slime:” in your buffer’s mode-line, indicating the Lisp buffer is
associated with your remote SLIME connection.
Now enter the following forms into your Lisp file. You could enter these into the Lisp REPL directly, but writing them in a file first is a handy way of taking notes and preserving your work in a textual form:
defpackage :webserver
(:use :common-lisp :hunchentoot :cl-who))
(
in-package :webserver)
(
setf *dispatch-table*
(list #'dispatch-easy-handlers
(#'default-dispatcher))
setf *show-lisp-errors-p* t
(t) *show-lisp-backtraces-p*
This sets up the basic framework for your webserver. Once saved to disk, type
C-x h
to mark the entire buffer, and C-c C-r
to submit the region to the
remote Lisp for evaluation. (Note: The second setq
above is completely
optional, and should be removed for a production server. But having it makes
errors much easier to track down in the initial stages).
Your basic dispatcher is now setup. If you visit the home page of your
webserver, however, you’ll still see the old default page because you’re using
default-dispatcher
for everything. Let’s define a new page handler that does
some work.
Below is a basic Hello World handler. Add it to the end of your file, put your
cursor anywhere within the handler’s definition, and type C-c C-c
to have
SLIME compile the definition into your webserver:
"/lisp/hello"
(define-easy-handler (easy-demo :uri
:default-request-type :get)'string))
((state-variable :parameter-type *standard-output* nil :prologue t)
(with-html-output-to-string (
(:html"Hello, world!"))
(:head (:title
(:body"Hello, world!")
(:h1 "This is my Lisp web server, running on Hunchentoot,"
(:p " as described in "
(:a :href"http://newartisans.com/blog_files/hunchentoot.primer.php"
"this blog entry")
" on Common Lisp and Hunchentoot.")))))
This kind of easy handler is just that: very easy to make. Hunchentoot also
allows for much more sophisticated handlers, about which I refer you to the documentation. But right now we just need a handler to capture requests
bound for /lisp/hello
, in response to which we’ll return a Lisp-constructed
HTML page. The reason I using the path /lisp/hello
here has to do with the way
my Apache is configured. If you run Hunchentoot at root level, you could use
/hello
instead.
Try visiting your /lisp/hello
page now. You should see a brief greeting to the
entire planet!
You may have noticed, after compiling this definition, that it warns about
state-variable
not being used. There’s a good reason for that: we never used
it. So let’s further extend the example server with an input textbox and
submit button, using the state variable to get information back from the
client via HTTP POST. Notice how the :default-request-type
changes to :post
in
this code:
"/lisp/hello"
(define-easy-handler (easy-demo :uri
:default-request-type :post)'string))
((state-variable :parameter-type *standard-output* nil :prologue t)
(with-html-output-to-string (
(:html"Hello, world!"))
(:head (:title
(:body"margin: 20px"
:style "Hello, world!")
(:h1 "This is my Lisp web server, running on Hunchentoot,"
(:p " as described in "
(:a :href"http://newartisans.com/blog_files/hunchentoot.primer.php"
"this blog entry")
" on Common Lisp and Hunchentoot.")
(:p (:form
:method :post
(:table0 :cellpadding 5 :cellspacing 0
:border "text-align: right" (str "Say hello:"))
(:tr (:td :style :input :type :text
(:td (:name "state-variable"
:value state-variable)):input :type :submit :value "Submit"))))))
(:td ("The string you entered was: " (str state-variable)))))) (:p
If you visit my own Hunchentoot server right now, this is exactly the page you’ll see, handled by this very code (which I submitted to the server via Emacs, without ever restarting it):
And that’s that, simple web pages rendered by the multi-threaded Hunchentoot
application server. Anything not on the /lisp
page is served by Apache, while
everything under “/lisp” goes to Hunchentoot. But again, this depends entirely
on how you have Apache configured.
At this point I recommend visiting the Hunchentoot and CL-WHO web pages, to learn more about what these frameworks have to offer.
For those from an ASP or PHP background: because Lisp already provides such good facilities for constructing and manipulating hierarchical data, you’ll never find yourself inserting code stubs directly into HTML code – or dealing with the kind of hacks other frameworks use that turn plain HTML into a pseudo-language for rendering lists of controls, etc. When you work in Lisp, everything is in Lisp.