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