A quick Hunchentoot primer

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
      *show-lisp-backtraces-p* t)

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:

(define-easy-handler (easy-demo :uri "/lisp/hello"
                                :default-request-type :get)
    ((state-variable :parameter-type 'string))
  (with-html-output-to-string (*standard-output* nil :prologue t)
    (:html
     (:head (:title "Hello, world!"))
     (:body
      (:h1 "Hello, world!")
      (:p "This is my Lisp web server, running on Hunchentoot,"
          " 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:

(define-easy-handler (easy-demo :uri "/lisp/hello"
                                :default-request-type :post)
    ((state-variable :parameter-type 'string))
  (with-html-output-to-string (*standard-output* nil :prologue t)
    (:html
     (:head (:title "Hello, world!"))
     (:body
      :style "margin: 20px"
      (:h1 "Hello, world!")
      (:p "This is my Lisp web server, running on Hunchentoot,"
          " 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
           (:table
            :border 0 :cellpadding 5 :cellspacing 0
            (:tr (:td :style "text-align: right" (str "Say hello:"))
                 (:td (:input :type :text
                                    :name "state-variable"
                                    :value state-variable))
                 (:td (:input :type :submit :value "Submit"))))))
      (:p "The string you entered was: " (str state-variable))))))

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.