weblogging at the beach

« October 2007 | Main | December 2007 »

November 23, 2007

Custom Dispatchers in the PLT Scheme Web Server

We've just released Instaweb 2.0. Instaweb is our utility that takes care of setting up the PLT web-server and running servlets. If you have a servlet in a file called servlet.ss with Instaweb you just need to write the following lines to get it running:

  (require (planet "instaweb.ss" ("schematics" "instaweb.plt" 2)))
  (instaweb)

The new version of Instaweb includes many new options and works in a slightly different way to the 1.0 branch. To my mind the best new feature is that Instaweb now configures the web-server to pass to the servlet all requests that don't match a file in the htdocs directory. This means your servlet no longer has to live under a URL starting with /servlets. You can read the documentation to get the full details of what's new. What I want to talk about here is how we implelemented this, as it illustrates some very nice features of the web-server that aren't well known.

In the web-server's terminology a dispatcher is a function that may generate a response given a request. Examples includes the filesystem dispatcher, which responds to requests with the contents of a file, and the servlet dispatcher, which invokes a servlet. Dispatchers are arranged in a list. The first dispatcher in the list inspects the request and, if it decides the request is relevant, generates a response. Otherwise control is passed to the next dispatcher in the list. For some time now the web-server has had a configurable dispatcher pipeline, which can be set by simply passing a value with the #:dispatch keyword to the serve function.

The web-server provides a number of dispatchers, all in the dispatchers subdirectory of the web-server collection. They all provide a make function that does most of the work. Here's how to use the file, servlet, and sequence dispatchers, the most generally useful ones:

  1. The file dispatcher, in dispatch-files.ss, takes a single parameter, a function that converts a URL to a path (and another value that the dispatcher ignores). The path can name a file, which the dispatcher will serve if such a file actually exists, or it can name a directory, in which case the dispatcher will look for a file within that directory called index.html or index.htm.

    To use the file dispatcher you will probably want the handy make-url->path function in filesystem-map.ss. Pass this function a base path (the directory where your files live), and it will return a function suitable to pass to the file dispatcher.

    Here's an example of use:

      (require 
        (prefix file: (lib "dispatch-files.ss" "web-server" "dispatchers"))
        (lib "filesystem-map.ss" "web-server" "dispatchers"))
    
      (define base-path (string->path "/my/directory/of/files"))
    
      ;; htdocs-url->path : path -> (url -> path (list-of path-element))
      (define (htdocs-url->path path)
        (make-url->path (path->complete-path path)))
      
      ;; dispatch-htdocs : (connection request -> response)
      (define dispatch-htdocs
        (file:make #:url->path (htdocs-url->path base-path)))
    
  2. The servlet dispatcher, in dispatch-servlets.ss is a bit more difficult to use as you need a function from the private subcollection of the web-server, suggesting the code reorganisation isn't quite finished. The make function takes two arguments, the first being a cache-table, and the second being a function that, like for the file dispatcher, maps URLs to paths. To construct a cache-table use the following lines of code:

      (require
        (lib "cache-table.ss" "web-server" "private"))
    
      (define cache-table (box (make-cache-table)))
    

    If you want all URLs to go a particular servlet, as in Instaweb, the URL to path function just needs to return the path of the servlet. The function used in Instaweb is this:

      ;; serlvet-url->path : url -> path (list-of path-element)
      (define (servlet-url->path url)
        (let ([complete-servlet-path (path->complete-path servlet-path)])
          (values complete-servlet-path (explode-path* complete-servlet-path))))
    

    Now we can create a dispatcher as follows:

      ;; clear-servlet-cache! : -> void
      ;; dispatch-servlets:    connection request -> response
      (define-values (clear-servlet-cache! dispatch-servlets)
        (servlet:make (box (make-cache-table))
                      #:url->path servlet-url->path))
    
  3. The sequencer dispatcher couldn't be easier to use. It just takes any numbe of dispatchers and creates new dispatcher that tries them in sequence. For example:

      ;; dispatch-all : connection request -> response
      (define dispatch-all
        (sequencer:make dispatch-htdocs
                        dispatch-servlets))
    

With the above you should be able to create your own custom dispatchers. If you have problems just read the (very short) Instaweb code!

Posted by Noel at 04:12 PM | Comments (0) | TrackBack