I've implemented a proof of concept continuation based web server in
Factor. For information on continuation based web servers see:
http://radio.weblogs.com/0102385/2003/08/30.html#a422
http://www.double.co.nz/scheme/modal-web-server.html
Draft of the implementation is below. Comments welcome.
----------------------8<---------------------
In a continuation based web application the current position within
the web application is identified by a random URL. In this example the
URL is generated as a string of random digits:
: get-random-id ( -- id )
"" 16 [ random-digit >str cat2 ] times ;
Each URL is associated with a word or quotation. When that URL is
accessed, that quotation is executed. The quotation will receive a
table on the top of the stack which holds any POST or query parameters
(not currently implemented in this proof of concept). A global table
is maintained that holds this association:
: continuation-table ( -- <table> )
#! Return the global table of continuations
"cont" get ;
: reset-continuation-table ( -- )
#! Create the initial global table
<table> "cont" set ;
: register-continuation ( cont -- id )
#! Store a continuation in the table and associate it with
#! a random id.
continuation-table [ get-random-id dup [ set ] dip ] bind ;
: get-registered-continuation ( id -- cont )
#! Return the continuation associated with the given id.
continuation-table [ get ] bind ;
: resume-continuation ( value id -- )
#! Call the continuation associated with the given id,
#! with 'value' on the top of the stack.
get-registered-continuation call ;
When a an URL is accessed, the continuation for the specific URL is
obtained and called. That continuation needs to exit back to the
caller when it has some HTML that it needs to display. returning that
HTML to the caller. To exit back to the caller it calls an 'exit
continuation' that is stored in an "exit" variable:
: exit-continuation ( -- exit )
#! Get the current exit continuation
"exit" get ;
: call-exit-continuation ( value -- )
#! Call the exit continuation, passing it the given value on the
#! top of the stack.
"exit" get call ;
: with-exit-continuation ( [ code ] -- )
#! Call the quotation with the variable "exit" bound such that when
#! the exit continuation is called, computation will resume from the
#! end of this 'with-exit-continuation' call, with the value passed
#! to the exit continuation on the top of the stack.
[ "exit" set call call-exit-continuation ] callcc1 nip ;
All this calling of continuations is hidden behind a single 'show'
call. 'show' will take a quotation on the stack. That quotation should
return an HTML string. 'show' will call it to generate the HTML and
call the exit continuation with this string on the stack so it gets
returned to the httpd responder. The quotation receives a 'url' on the
top of the stack which is the 'id' of the continuation to resume when
that URL is accessed.
To generate the string of HTML I use 'with-string-stream' which calls
a quotation and all output inside that call gets appended to a string:
: with-string-stream ( quot -- string )
#! Call the quotation with standard output bound to a string output
#! stream. Return the string on exit.
<namespace> [
"stdio" <string-output-stream> put call "stdio" get stream>str
] bind ;
: show ( [ code ] -- )
#! Call 'code' with the URL associated with the current
#! continuation. Return the HTML string generated by that code
#! to the exit continuation. When the URL is later referenced then
#! computation will resume from this 'show' call with a <table> on
#! the stack containing any query or post parameteres.
[
register-continuation swap with-string-stream
call-exit-continuation
] callcc1
nip ;
An httpd responder is used that takes the ID as an argument, retrieves
the continuation associated with it and calls it:
: cont-responder ( id -- )
#! httpd responder that retrieves a continuation and calls it.
[ <table> swap resume-continuation ] with-exit-continuation
serving-html print drop ;
Some code to install the responder:
"httpd-responders" get [
<responder> [
[ cont-responder ] "get" set
] extend "cont" set
] bind
Now to test it. Here's a function that displays some text on an HTML
page:
: display-page ( title -- )
[
swap [
"<a href='" write write "'>Next</a>" write
] html-document
] show ;
Note that it contains an A HREF link to the URL that resumes the
computation (The quotation passed to show has this URL passed to it by
show).
A word that displays a sequence of these pages would be:
: test
"Page one" display-page
"Page two" display-page
"Page three" display-page ;
Now we register this word with the continuation system:
[ test ] register-continuation
This returns an ID which can be used from the web server to resume
it. Start the web server:
8888 httpd
Now access the URL with that id:
http://localhost:8888/cont/1234567890
(where 1234567890 is the ID returned by register-continuation above)
You'll see the first page and a link. Click the link and you'll see
the second page. Ditto with the third. You can go back and forth with
the back button, refresh, bookmark, etc as expected.
Note that this is just a proof of concept. In a real framework the
continuations need to be expired after time. It would also enable
generating links to other pages, etc rather than just a sequence of
pages. Implementing POST is also an important addition to make things
useful. I plan to flesh this out over the next few days and present
some more useful examples. My main point was to see if it was possible
to do this type of thing in Factor.
The number of words required to get things going is amazingly small
and it was very easy to develop this far.
If you want to try the above I used the following 'uses':
USE: stdio
USE: unparse
USE: httpd
USE: httpd-responder
USE: random
USE: continuations;
----------------------8<---------------------
--
Chris Double
chris.double@...