Common Cold

a lightweight continuations-based webpage package with cooler URLs for Common Lisp


Writing dynamic webpages with continuations can save a lot of pain, by letting the computer compile the inversion of control away. Unfortunately, continuations can rarely be exported outside the language implementation's runtime, so we are usually forced to save them at the server, and only pass a handle to the client. Not only may this open the server up to DOS attacks, but it also usually means that URLs have a finite, and relatively short, lifetime.

  1. What makes Common Cold different
  2. Finer points of static and dynamic scoping
  3. More on special variables in Common Cold
  4. Common Cold's own special forms
  5. How to use continuations in dynamic pages
  6. Miscellaneous information
  7. Links to the source files and a tarball

Common Cold (CC)'s continuations are serialisable, avoiding these problems and much of the complexity associated with them. Moreover, instead of attempting to 'hide' the fact that we do not use native continuations, I tried to make the transformation as explicit as possible (by having the package use its own special forms instead of a codewalker), but still usable (by using its own special forms). The programmer can thus easily control where the transformation will be made, and completely avoid any overhead in sections of code that will never be captured. Since the borders between the vanilla CL world and that of transformed code are kept explicit, the danger of being surprised by a leaky abstraction, e.g. when mixing continuations and native higher-order functions, should be much lesser.

CC supports both lexical bindings (static scoping) and special variables (dynamic scoping). Serialised closures or continuations always create fresh copies of the lexical scopes in the state they were when the closure or the continuation frame was created. Assigning to lexically bound variables can result in surprising behaviour, especially since closures/continuations that have not been deserialised have normal CL lexical bindings. Again, in the interest of explicitness, nothing is done to prevent this from happening; the programmer may very well know that a given variable is only assigned to before capturing its scope. Note that side-effecting lexically bound variables in continuations-based web framework is often a cause of weird bugs, since it can be difficult to know when bindings must be shared, and when they must be copied. Special variables are the prime way to introduce mutable bindings in CC. Specials created in CL code or via CC's own special forms can be mixed transparently, but only CC's special forms will capture them in continuations. Dynamic bindings are never shared between continuations, and are recaptured every time a continuation is captured. Assignments to special variables are thus carried from a continuation's invocation to the next.

Specials are also treated specially by the web serving code. While the bindings are always saved in the continuations, the topmost (non shadowed) ones are also exposed as CGI parameters (as their qualified, URL-encoded, SYMBOL-NAME) to the continuation's URL (this part may be buggy with GET forms?). When a continuation is invoked (a response is sent to the server), the topmost special bindings are replaced (as if by assignment) by the rightmost parameter with the corresponding name, if any. Parameters can thus simply be special variables (I should create a special package for them to avoid their being qualified), which will be side-effected with their value in the form if any. Since CC uses Hunchentoot, it is of course also possible to use Hunchentoot's functions for better access to parameters or control of the headers.

Lexical bindings may be created via bind, mlet* or mlet (if the computed values may capture the continuations; otherwise, let and let* are fine). Local functions should be bound normally, using flet and labels. Special variables may be introduced with dbind, mdlet* or mdlet (if no continuation will be captured in their dynamic scope, normal CL special forms will also work). Finally, mprogn introduces sequencing (when one of the forms may capture its continuation), mcatch a catch frame (that interacts with CL's catch and throw correctly, but is captured in continuations), and mblock, mreturn-from and mreturn (which may not always interact correctly with CL's block/return-from).

Page creating functions simply return a string, and may use Hunchentoot's functions freely. To use continuations, call-with-​continuation-​url or send/suspend may be used. (call-with-​continuation-​url [argument-​function]) unwinds the stack (thus making it closer to shift), builds a copy of the continuation's dynamic scope environment, and calls its argument function, passing it the URL representing its continuation. The argument function should return a string, the web page's contents. (send/suspend ([k]) [body]) builds on top of call-with-​continuation-​url and executes [body] with [k] bound to the URL representing send/suspend's continuation. The body should evaluate to the response page's contents. In both cases, only a copy of the dynamic bindings is restored. Side effects, even to special variables, will not be reflected in the captured continuations, and mcatchs, mblocks, etc. are not active.

The continuation part of URLs are encrypted using AES when a secret key has been registered using register-key. Registering a zero-length key disables the encryption (which is disabled by default). Continuations are then base64 (adapted to URIs) encodings of unencrypted gziped strings.

ensure-all-builders may be called at any time to compile all fragment (closures or continuation frames) deserialising functions. In would then be possible to export the data to other nodes for easier load balancing.

Finally, make-continuation-​handler should be used to create a dispatch function for Hunchentoot. The example should make its usage clear.