Paul Khuong mostly on Lisp

rss feed

Mon, 25 Jun 2007

 

Generating webpages with Common Cold

Common Cold doesn't actually offer any special way to generate webpages. Functions simply have to return strings; how the strings are generated is irrelevant. What CC does offer is a way to encode continuations in URLs and a Hunchentoot handler to decode and invoke them.

call-with-continuation-url is a low-level function that takes an input function, captures the continuation and encodes it in an URL, sets up a copy of the dynamic bindings in the continuation, and passes its argument that URL. The function should return a string, the contents of the webpage. For example,

(defun counter ()
  (labels
      ((inner (count)
         (mprogn
          (call-with-continuation-url
           (lambda (k)
             (with-html-output-to-string
                 (*standard-output*)
               (:html
                (:head (:title "Counter"))
                (:body (fmt "Count: ~A~%" count)
                       (:a :href k "Next"))))))
          (inner (1+ count)))))
    (inner 0)))
uses cl-who to implement a simple page that counts the number of times the user has clicked on "Next". Continuation capture prunes redundant frames, so tail recursion is safe and will not lead to ever-growing URLs.

send/suspend is a simple macro that wraps call-with-continuation-url in some syntactic sugar. It should be used as (send/suspend (k) body...), where k is the variable to which the continuation's URL will be bound, and body an implicit progn (not mprogn) that should evaluate to a string. The example above can be rewritten as:

(defun counter ()
  (labels
      ((inner (count)
         (mprogn
          (send/suspend (k)
            (with-html-output-to-string
                (*standard-output*)
              (:html
               (:head (:title "Counter"))
               (:body (fmt "Count: ~A~%" count)
                      (:a :href k "Next")))))
          (inner (1+ count)))))
    (inner 0)))

Note that neither version uses CGI parameters. However, the count could clearly be one. Common Cold treats special variables as CGI parameters, exposing them in the URL as such, and updating their values according to the parameters list when possible. The counter could thus be rewritten as:

(defun counter ()
  (labels
      ((inner (count)
         (mdlet ((count count))
          (send/suspend (k)
            (with-html-output-to-string
                (*standard-output*)
              (:html
               (:head (:title "Counter"))
               (:body (fmt "Count: ~A~%" count)
                      (:a :href k "Next")))))
          (inner (1+ count)))))
    (inner 0)))

The URL will look like [base64-data]?COUNTER:COUNT=0& (the colon is actually URI-encoded), and, if the value is overridden in any way (by replacing it, or by appending a binding to COUNTER:COUNT in the query), it will replace the current one. If, however, no value is provided as a parameter, the actual value when the continuation was capture will be used as a default. Hunchentoot's parameter handling functions can of course also be used.

To register our counter function as a webpage, make-continuation-handler must be used to create a handler closure, which we can then pass to Hunchentoot's dispatcher creating functions. For example,

(push (hunchentoot:create-prefix-dispatcher
       "/counter/"
       (make-continuation-handler 'counter
                                  "/counter/"
                                  ))
      hunchentoot:*dispatch-table*)
will call counter whenever a request is made for "/counter/". It will also treat any other URL beginning with "/counter/" as a continuation URL by clipping out the prefix. A predicate (that returns a true value when its argument corresponds to a request to 'counter) can also be passed instead of a string as the second argument to make-continuation-handler.

Continuations are, by default, simply gziped and base64-encoded. While we are passing closure descriptors instead of actual code, it can still be dangerous, and probably constitutes an information leak. register-key should be used to register a private key that will be used to encrypt and decrypt (symmetrically) the continuations.

posted at: 00:00 | /Lisp/CommonCold | permalink

Made with PyBlosxom Contact me by email: pvk@pvk.ca.