# Continuation based web server in Factor

## Chris Double — 2004-06-22 23:49:45

    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@...

## Chris Double — 2004-06-23 21:09:34

    I've updated this example now. The description is at:

    http://www.double.co.nz/factor/cont.txt

    and the code in:

    http://www.double.co.nz/factor/cont-responder.factor

    The description contains instructions on how to use it with examples.

    The nice thing about this type of server is the standard concatenative

    operations work as expected:

    : test-cont-responder2 ( alist - )

      #! Test the cont-responder responder by displaying a few pages in a

      loop.

      [ "one" "two" "three" "four" ] [ display-page ] each

      "Done!" display-page ;

    'display-page' causes a web page to be displayed with the top most item

    on the stack on the page and a 'Next' anchor which takes the user to the

    next stage of the computation. The snippet above will display each item

    on the list in seperate pages with the user navigating between them

    using the back button or 'Next'.

    This version also includes POST support. You can have a FORM on a page

    and the results are returned as an alist. An example of use is:

    : display-get-name-page ( -- name )

      #! Display a page prompting for input of a name and return that name.

      [

        "Enter your name" [

          "<form method='post' action='" write write "'>" write

          "Name: <input type='text' name='name' size='20'>" write

          "<input type='submit' value='Ok'>" write

          "</form>" write

        ] html-document

      ] show

      "name" swap assoc ;

    : test-cont-responder ( alist - )

      #! Test the cont-responder responder by displaying a few pages in a

      row.

      drop

      "Page one" display-page

      "Hello " display-get-name-page cat2 display-page

      "Page three" display-page ;

    Here 'display-get-name-page' displays a page prompting for the name, and

    returns it by extracting it out of the alist returned by 'show'. 'Hello

    ' is concatenated to it and it is displayed on an other page. These

    pages can be bookmarked, revisited, etc.

    Slava, I implemented the breaking up of the POST data in the following

    word:

    : post-request>alist ( post-request -- alist )

      #! Return an alist containing name/value pairs from the

      #! post data.

      dup "&" swap str-contains [

        "(.+)&(.+)" groups [ "(.+)=(.+)" groups uncons car cons ] inject

      ] [

        "(.+)=(.+)" groups uncons car cons unit

      ] ifte ;

    It's called like:

      read-post-request post-request>alist

    I'm not sure if I need the decode-url there? I suspect I need to decode

    the name and value parts of the POST data.

    Upcoming improvements will be allowing anchors to arbitary quotations,

    callbacks, and some examples.

    Chris.

    --

      Chris Double

      chris.double@...