Setting up a server for Lisp web apps

Table of Contents

1 Introduction

This note documents how to set up a basic Lisp web app server.

2 Software

The main software that you'll need is a Lisp interpreter. These instructions assume sbcl (Steel Bank Common Lisp) but other Lisp implementations may work as well. On Debian you can install sbcl using apt:

$ sudo apt-get install sbcl

To run the server on port 80, we'll use the program setcap

$ sudo apt-get install libcap2-bin

3 Libraries

The main Lisp libraries we will use are huchentoot and cl-who. These can be installed using a the quicklisp Lisp package manager. According to https://www.quicklisp.org/beta/ quicklisp may be installed as follows:

$ curl -O https://beta.quicklisp.org/quicklisp.lisp
$ sbcl --load quicklisp.lisp
* (quicklisp-quickstart:install)
* (ql:add-to-init-file)
* (quit)

This installs quicklisp. To install hunchentoot and cl-who, we can go back into sbcl and call quicklisp:

$ sbcl
* (ql:quickload :hunchentoot)
* (ql:quickload :cl-who)
* (quit)

Now we are ready to run our first Lisp web app.

4 Example code

Here is the source code of hello.lisp:

(require "hunchentoot")
(require "cl-who")
(use-package :cl-who)
(use-package :hunchentoot)

(defclass search-server (acceptor)
  ((dispatch-table
    :initform '()
    :accessor dispatch-table
    :documentation "List of dispatch functions")))

(defvar *mysrv* (make-instance 'search-server :port 80))

(defun find-not-nil (l p) 
  (if (endp l)
      nil
    (or (funcall p (car l))
         (find-not-nil (cdr l) p))))

(defmethod acceptor-dispatch-request ((srv search-server) (req request))
  (let ((l (find-not-nil (dispatch-table srv)
                         (lambda (disp) (funcall disp req)))))
    (or l (call-next-method))))


(defmacro with-html ((var) &body body)
  `(with-html-output-to-string (,var)
                               ,@body))

(defun my-prefix-disp (prefix handler)
  (lambda (req)
    (let ((m (mismatch prefix (script-name* req))))
      (if (or (null m) (>= m (length prefix)))
          (funcall handler req)))))

(defun push-dispatcher (srv disp)
  (push disp (dispatch-table srv)))

(let ((counter 0))
  (defun dummy-dispatch (req)
    (with-html (s)
               (:html
                (:body
                 "Greetings! You are visitor number "
                 (str (incf counter)))))))

(push-dispatcher *mysrv*
                 (my-prefix-disp "/hello" (quote dummy-dispatch)))

(start *mysrv*)

Next we will see how to run our web app.

5 Launching the web app

First, we need to allow hunchentoot to listen on port 80. This can be done with the setcap program

$ sudo setcap 'cap_net_bind_service=+ep' /usr/bin/sbcl

Start the web app as follows:

$ sbcl
* (load "hello.lisp")

You may see a few warnings; these can be ignored.

6 Testing it out

Launch a web browser and visit http://yoursite/hello. If everything is working, you should see something like this

screenshot_hellolisp_1.png

Something you can play with: Refresh the page a few times and note how the visitor number field is updated.

screenshot_hellolisp_10.png

This logic is implemented inside the web app in the dummy-dispatch function (look for the call to incf there)

Created: 2020-08-04 Tue 07:22

Validate