Clojure Web Server (in less than 100 lines)

(Edit: Welcome reddit readers. The following is a howto on setting up an embedded Jetty server in Clojure, and writing a minimal servlet that serves up dynamic content.)

Last week I discovered a very nice language named Clojure. It’s based on Lisp, but hosted on the Java platform, and running inside the JVM. It has some lovely features, like native support for Java collections, and a clean API that ditches a lot of the old ANSI CL library cruft.

But the best part for me was language interop: Clojure can effortlessly load and use any external Java library. And there is a ton of good ones out there, including a rich set that comes with the Java platform. (This may seem like a minor point, but most free Lisps lack good libraries; only the non-free Allegro CL has really extensive, cross-platform ones.)

So I decided to take the language for a spin, especially exercising the language interop bit. For example, by building a little web server to serve up some dynamic content. You know, web development 101 kind of stuff. This turns out to be really easy to do. :)

Since Clojure is backed by Java, we can use an existing server library to do the heavy lifting for us. Several high-quality ones are available - I went with Jetty, a servlet-based engine that’s really easy to embed in custom applications. Using a servlet container will take care of all the mundane bookkeeping for us.

In the following posting, I’m reproducing the steps required to get a Clojure-based web server up and running, in much less than 100 lines of code. Hope you find it interesting!

I’ll split this into three parts:

  1. Getting Jetty and setting up your Clojure environment
  2. Creating a server that only serves up static pages
  3. Modifying the server to provide dynamic content

Let’s go through them.

 

1. Getting Jetty and environment setup.

(The following requires Clojure release 2008/03/29 or later.)

Jetty is a huge library, but we’ll only need three jars from it. So let’s do the following:

  • Download Jetty 6.1.9, or whichever is latest, from the official site
  • Get the following jar files from the archive, and place them in some preferred directory (I put them under clojure/lib):
    • jetty-6.1.9.jar
    • jetty-util-6.1.9.jar
    • servlet-api-2.5-6.1.9.jar
  • In whatever script / batch file / elisp command you use to start up your Clojure instance, modify your classpath to include them. In my case, I end up with the enormous:
  • “java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=4096 -cp c:/users/rob/documents/clojure/clojure.jar;c:/users/rob/documents/clojure/lib/jetty-6.1.9.jar;c:/users/rob/documents/clojure/lib/jetty-util-6.1.9.jar;c:/users/rob/documents/clojure/lib/servlet-api-2.5-6.1.9.jar clojure.lang.Repl c:/users/rob/documents/clojure/src/boot.clj”
  • To verify, start up your Clojure instance, and try creating a new Jetty Server instance - it should look something like the following:

    Clojure
    user=> (import '(org.mortbay.jetty Server))
    nil
    user=> (new Server)
    Server@1f0f8ff
    user=>

    All right, now we’re ready to have some fun.

     

    2. Writing a server for static pages

    Jetty comes with a default servlet for static html pages. We just need to configure it.

    We could do this from an xml file, as recommended for Jetty deployment in production environments. But that’s more work than is needed for this experiment.  :)  Instead, we’ll configure a small and minimal server instance from Clojure code itself.

    First, a big bunch of imports:

    (import
     '(java.io File)
     '(javax.servlet.http HttpServlet HttpServletRequest HttpServletResponse)
     '(org.mortbay.jetty Server Handler Connector NCSARequestLog)
     '(org.mortbay.jetty.handler HandlerCollection ContextHandlerCollection RequestLogHandler)
     '(org.mortbay.jetty.nio BlockingChannelConnector)
     '(org.mortbay.jetty.servlet Context DefaultServlet ServletHolder SessionHandler))

    Now let’s add a few constants that we’ll need later on (customize as needed, of course):

    ;; server port
    (def *port* 8080)
    ;; static pages will be served from this directory
    (def *wwwdir* "C:/Users/Rob/Documents/Projects/Clojure Http Server/pages")
    ;; log files go here
    (def *logdir* "C:/Users/Rob/Documents/Projects/Clojure Http Server/log")
    ;; specifies naming pattern for log files
    (def *logfiles* "log.yyyy_mm_dd.txt")

    The Jetty server itself contains some connectors, which read data from sockets and feed it to a bunch handlers. Let’s make one.

    ;; create connector on the specified port
    (defn make-connectors [port]
      (let [conn (new BlockingChannelConnector)]
        (. conn (setPort port))
        (into-array [conn])))

    One of the built-in handlers is a logger, which just writes out an access log file. Another built-in one is a context handler, which contains a set of servlets. It preprocesses any received data, and then hands it over to one of its servlets, based on the path being requested in the URL.

    Here’s how I configure both of them:

    ;; configures a default servlet to serve static pages from the "/" directory
    (defn configure-context-handlers [contexts]
      (let [context (new Context contexts "/" (. Context NO_SESSIONS))]
        (. context (setWelcomeFiles (into-array ["index.html"])))
        (. context (setResourceBase (. (new File *wwwdir*) (getPath))))
        (. context (setSessionHandler (new SessionHandler)))
        (. context (addServlet (new ServletHolder (new DefaultServlet)) "/*"))
        context))
    ;; set up handlers: the context ones, and a logger to track all http requests
    (defn make-handlers [contexts]
      (let [handlers (new HandlerCollection)
            logger (new RequestLogHandler)
            logfile (new File *logdir* *logfiles*)]
        (. logger (setRequestLog (new NCSARequestLog (. logfile (getPath)))))
        (. handlers (addHandler logger))
        (. handlers (addHandler contexts))
        handlers))

    All that’s left is standing up the actual server. That’s frighteningly easy:

    ;; make an empty collection of context handlers - we'll configure it later
    (defn make-contexts []
      (new ContextHandlerCollection))
    
    ;; make an instance of the http server
    (defn make-server
      ([] (make-server *port*))
      ([port]
         (let [contexts (make-contexts)
               connectors (make-connectors port)
               handlers (make-handlers contexts)
               server (new Server)]
           (. server (setConnectors connectors))
           (. server (setHandler handlers))
           (configure-context-handlers contexts)
           server)))

    And that’s it! Go ahead, try it!

    First, place an “index.html” page in the directory you specified under *wwwdir*. Then run the server:

    user=> (def server (make-server))
    2008-04-26 23:32:02.752::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
    #<Var: user/server>
    user=> (. server (start))
    2008-04-26 23:32:10.863::INFO:  jetty-6.1.9
    2008-04-26 23:32:10.898::INFO:  Opened C:\Users\Rob\Documents\Projects\Clojure Http Server\log\log.2008_04_27.txt
    2008-04-26 23:32:10.962::INFO:  Started BlockingChannelConnector@0.0.0.0:8080
    nil

    … and then navigate over to the right page:

    To stop the server, just call the method with the same name:

    user=> (. server (stop))
    nil
     

    3. Servlet that produces dynamic content

    The next step is writing our own servlet, which will serve arbitrary, dynamic content. The Java Servlet API is quite powerful, and makes it easy to build all sorts of wacky web wonders.

    Let’s start by writing our own request processing function: it will take an HTTP request and response objects, and just write out current time as the response:

    (defn process [#^HttpServletRequest req #^HttpServletResponse resp]
      (let [out (. resp (getOutputStream))]
        (. out (println (str "Test Successful at " (new java.util.Date)))))) 

    (The type hints aren’t strictly necessary, but they’re there for my sanity, to remind me what Java classes are hiding behind the various variables.)

    Also, make a new servlet that will use this function to process each request:

    ;; implementation of an HttpServlet, overriding just one function:
    ;;   protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    (defn make-test-servlet []
         (proxy [HttpServlet] []
           (doGet [#^HttpServletRequest req #^HttpServletResponse resp]
              (process req resp))))

    Finally, hook it up to the context handler, so that it will serve all requests for the “/test” URL. We just modify the function mentioned above, by adding a single line highlighted below:

    (defn configure-context-handlers [contexts]
      (let [context (new Context contexts "/" (. Context NO_SESSIONS))]
        (. context (setWelcomeFiles (into-array ["index.html"])))
        (. context (setResourceBase (. (new File *wwwdir*) (getPath))))
        (. context (setSessionHandler (new SessionHandler)))
        (. context (addServlet (new ServletHolder (new DefaultServlet)) "/*"))
        (. context (addServlet (new ServletHolder (make-test-servlet)) "/test"))
        context))

    That’s that! Now run the server again, and check out the results:

     

    Final Words

    This is clearly just an experiment, and not a production-ready system. But using Jetty as the server engine makes the system easy to build and very robust - it’s easy to imagine how one could go about building an actual, fully-featured web service around this technology.

    Hope this was amusing!