weblogging at the beach

« Attack of the Spam Bots | Main | Corporate Blanding »

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 November 23, 2007 04:12 PM

Trackback Pings

TrackBack URL for this entry:
http://www.untyped.com/mt/mt-tb.cgi/125

Comments