AllegroServe
You can serve Web content using Common Lisp in a number of ways; there are at least three open-source Web servers written in Common Lisp as well as plug-ins such as mod_lisp5 and Lisplets6 that allow the Apache Web server or any Java Servlet container to delegate requests to a Lisp server running in a separate process.
For this chapter, you’ll use a version of the open-source Web server AllegroServe, originally written by John Foderaro at Franz Inc.. AllegroServe is included in the version of Allegro available from Franz for use with this book. If you’re not using Allegro, you can use PortableAllegroServe, a friendly fork of the AllegroServe code base, which includes an Allegro compatibility layer that allows PortableAllegroServe to run on most Common Lisps. The code you’ll write in this chapter and in Chapter 29 should run in both vanilla AllegroServe and PortableAllegroServe.
AllegroServe provides a programming model similar in spirit to Java Servlets—each time a browser requests a page, AllegroServe parses the request and looks up an object, called an entity, which handles the request. Some entity classes provided as part of AllegroServe know how to serve static content—either individual files or the contents of a directory tree. Others, the ones I’ll spend most of this chapter discussing, run arbitrary Lisp code to generate the response.7
But before I get to that, you need to know how to start AllegroServe and set it up to serve a few files. The first step is to load the AllegroServe code into your Lisp image. In Allegro, you can simply type (require :aserve)
. In other Lisps (or in Allegro), you can load PortableAllegroServe by loading the file INSTALL.lisp
at the top of the portableaserve
directory tree. Loading AllegroServe will create three new packages, NET.ASERVE
, NET.HTML.GENERATOR
, and NET.ASERVE.CLIENT
.8
After loading the server, you start it with the function start
in the NET.ASERVE
package. To have easy access to the symbols exported from NET.ASERVE
, from COM.GIGAMONKEYS.HTML
(a package I’ll discuss in a moment), and from the rest of Common Lisp, you should create a new package to play in like this:
CL-USER> (defpackage :com.gigamonkeys.web
(:use :cl :net.aserve :com.gigamonkeys.html))
#<The COM.GIGAMONKEYS.WEB package>
Now switch to that package with this **IN-PACKAGE**
expression:
CL-USER> (in-package :com.gigamonkeys.web)
#<The COM.GIGAMONKEYS.WEB package>
WEB>
Now you can use the exported names from NET.ASERVE
without qualification. The function start
starts the server. It takes quite a number of keyword parameters, but the only one you need to pass is :port
, which specifies the port to listen on. You should probably use a high port such as 2001 instead of the default port for HTTP servers, 80, because on Unix-derived operating systems only the root user can listen on ports below 1024. To run AllegroServe listening on port 80 on Unix, you’d need to start Lisp as root and then use the :setuid
and :setgid
parameters to tell start
to switch its identity after opening the port. You can start a server listening on port 2001 like this:
WEB> (start :port 2001)
#<WSERVER port 2001 @ #x72511c72>
The server is now running in your Lisp. It’s possible you’ll get an error that says something about “port already in use” when you try to start the server. This means port 2001 is already in use by some other server on your machine. In that case, the simplest fix is to use a different port, supplying a different argument to start
and then using that value instead of 2001 in the URLs used throughout this chapter.
You can continue to interact with Lisp via the REPL because AllegroServe starts its own threads to handle requests from browsers. This means, among other things, that you can use the REPL to get a view into the guts of your server while it’s running, which makes debugging and testing a lot easier than if the server is a complete black box.
Assuming you’re running Lisp on the same machine as your browser, you can check that the server is up and running by pointing your browser at http://localhost:2001/
. At this point you should get a page-not-found error message in the browser since you haven’t published anything yet. But the error message will be from AllegroServe; it’ll say so at the bottom of the page. On the other hand, if the browser displays an error dialog that says something like “The connection was refused when attempting to contact localhost:2001,” it means either that the server isn’t running or that you started it with a different port than 2001.
Now you can publish some files. Suppose you have a file hello.html
in the directory /tmp/html
with the following contents:
<html>
<head>
<title>Hello</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
You can publish it individually with the publish-file
function.
WEB> (publish-file :path "/hello.html" :file "/tmp/html/hello.html")
#<NET.ASERVE::FILE-ENTITY @ #x725eddea>
The :path
argument is the path that will appear in the URL requested by the browser, while the :file
argument is the name of the file in the file system. After evaluating the publish-file
expression, you can point your browser to http://localhost:2001/hello.html
, and it should display a page something like Figure 26-2.
Figure 26-2. http://localhost:2001/hello.html
You could also publish a whole directory tree of files using the publish-directory
function. First let’s clear out the already published entity with the following call to publish-file
:
WEB> (publish-file :path "/hello.html" :remove t)
NIL
Now you can publish the whole /tmp/html/
directory (and all its subdirectories) with the publish-directory
function.
WEB> (publish-directory :prefix "/" :destination "/tmp/html/")
#<NET.ASERVE::DIRECTORY-ENTITY @ #x72625aa2>
In this case, the :prefix
argument specifies the beginning of the path part of URLs that should be handled by this entity. Thus, if the server receives a request for http://localhost:2001/foo/bar.html
, the path is /foo/bar.html
, which starts with /
. This path is then translated to a filename by replacing the prefix, /
, with the destination, /tmp/html/
. Thus, the URL http://localhost:2001/hello.html
will still be translated into a request for the file /tmp/html/hello.html
.