remove out of date cont-responder docs
parent
a452f95788
commit
9f5845d09e
|
@ -193,7 +193,7 @@ M: promised-label set-label-color set-promised-label-color ;
|
||||||
M: promised-label set-label-font set-promised-label-font ;
|
M: promised-label set-label-font set-promised-label-font ;
|
||||||
|
|
||||||
: fib ( n -- n )
|
: fib ( n -- n )
|
||||||
yield dup 2 < [ drop 1 ] [ dup 1 - fib swap 2 - fib + ] if ;
|
1 sleep dup 2 < [ drop 1 ] [ dup 1 - fib swap 2 - fib + ] if ;
|
||||||
|
|
||||||
: test-promise-ui ( -- )
|
: test-promise-ui ( -- )
|
||||||
<promise> dup <promised-label> gadget. [ 30 fib unparse swap fulfill ] cons spawn drop ;
|
<promise> dup <promised-label> gadget. [ 15 fib unparse swap fulfill ] cons spawn drop ;
|
||||||
|
|
|
@ -1,375 +0,0 @@
|
||||||
cont-responder v0.3
|
|
||||||
===================
|
|
||||||
|
|
||||||
NOTE: This documentation is slightly out of date with respect to the
|
|
||||||
current code but contains the basic idea.
|
|
||||||
|
|
||||||
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 )
|
|
||||||
#! Generate a random id to use for continuation URL's
|
|
||||||
<% 16 [ random-digit % ] times %> ;
|
|
||||||
|
|
||||||
Each URL is associated with a quotation or continuation. When that URL
|
|
||||||
is accessed, that quotation is executed. The quotation will receive an
|
|
||||||
alist on the top of the stack which holds any POST or query
|
|
||||||
parameters. A global table is maintained that holds the association of
|
|
||||||
quotations to id's.
|
|
||||||
|
|
||||||
: continuation-table ( -- <namespace> )
|
|
||||||
#! Return the global table of continuations
|
|
||||||
"cont" get ;
|
|
||||||
|
|
||||||
: reset-continuation-table ( -- )
|
|
||||||
#! Create the initial global table
|
|
||||||
<namespace> "cont" set ;
|
|
||||||
|
|
||||||
: register-continuation ( quot -- 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 ( quot -- )
|
|
||||||
#! 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.
|
|
||||||
|
|
||||||
The HTML page that 'show' displays can contain links to
|
|
||||||
'callbacks'. These are links to other quotations that when called
|
|
||||||
will perform some action and return back to the calling page. To
|
|
||||||
return back to the calling page 'show' must capture and store the
|
|
||||||
current continuation before 'show' does anything so it can be later
|
|
||||||
return to by the callback. The following words store the current
|
|
||||||
continuation and retrieve it:
|
|
||||||
|
|
||||||
: store-callback-cc ( -- )
|
|
||||||
#! Store the current continuation in the variable 'callback-cc'
|
|
||||||
#! so it can be returned to later by callbacks. Note that it
|
|
||||||
#! recalls itself when the continuation is called to ensure that
|
|
||||||
#! it resets it's value back to the most recent show call.
|
|
||||||
[
|
|
||||||
[ "callback-cc" set call ] callcc0 drop store-callback-cc
|
|
||||||
] callcc0 ;
|
|
||||||
|
|
||||||
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 ( quot -- alist )
|
|
||||||
#! Call the quotation 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 alist on
|
|
||||||
#! the stack containing any query or post parameters.
|
|
||||||
store-callback-cc
|
|
||||||
[
|
|
||||||
register-continuation swap with-string-stream
|
|
||||||
call-exit-continuation
|
|
||||||
] callcc1
|
|
||||||
nip ;
|
|
||||||
|
|
||||||
An httpd get responder is used that takes the ID as an argument, retrieves
|
|
||||||
the continuation associated with it and calls it. For the post
|
|
||||||
responder the post data is converted into an alist and it is put on
|
|
||||||
the top of the stack when calling the continuation.
|
|
||||||
|
|
||||||
: cont-get-responder ( id -- )
|
|
||||||
#! httpd responder that retrieves a continuation and calls it.
|
|
||||||
[ f swap resume-continuation ] with-exit-continuation
|
|
||||||
serving-html print drop ;
|
|
||||||
|
|
||||||
: 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 ;
|
|
||||||
|
|
||||||
: cont-post-responder ( id -- )
|
|
||||||
#! httpd responder that retrieves a continuation for the given
|
|
||||||
#! id and calls it with the POST data as an alist on the top
|
|
||||||
#! of the stack.
|
|
||||||
[
|
|
||||||
read-post-request post-request>alist swap resume-continuation
|
|
||||||
] with-exit-continuation
|
|
||||||
serving-html print drop ;
|
|
||||||
|
|
||||||
Some code to install the responder:
|
|
||||||
|
|
||||||
: install-cont-responder ( -- )
|
|
||||||
#! Install the cont-responder in the table of httpd responders
|
|
||||||
"httpd-responders" get [
|
|
||||||
<responder> [
|
|
||||||
[ cont-get-responder ] "get" set
|
|
||||||
[ cont-post-responder ] "post" set
|
|
||||||
reset-continuation-table
|
|
||||||
] extend "cont" set
|
|
||||||
] bind ;
|
|
||||||
|
|
||||||
Now to test it. Here's a function that displays some text on an HTML page:
|
|
||||||
|
|
||||||
: display-page ( title -- )
|
|
||||||
#! Display a page with some text to test the cont-responder.
|
|
||||||
#! The page has a link to the 'next' continuation.
|
|
||||||
[
|
|
||||||
swap [
|
|
||||||
"<a href='" write write "'>Next</a>" write
|
|
||||||
] html-document
|
|
||||||
] show drop ;
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
An example of a POST request is a page that requests input of a
|
|
||||||
name. The following function calls show to display it and returns the
|
|
||||||
result of the 'name' field by retrieving it from the alist. Notice how
|
|
||||||
the 'action' of the post request is set to the URL passed in to the
|
|
||||||
quotation passed to show. So the 'next' that happens here is not an A
|
|
||||||
HREF but the action field of the POST.
|
|
||||||
|
|
||||||
: 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 ;
|
|
||||||
|
|
||||||
A word that displays a sequence of these pages would be:
|
|
||||||
|
|
||||||
: 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 ;
|
|
||||||
|
|
||||||
This displays the first page, then a page prompting for the
|
|
||||||
name. "Hello " is concatenated to the result of the page and a third
|
|
||||||
page is displayed. A fourth page is available as well.
|
|
||||||
|
|
||||||
The following registers this word with the continuation system:
|
|
||||||
|
|
||||||
: register-test-cont-responder ( -- id )
|
|
||||||
#! Register the test-cont-responder word so that accessing the
|
|
||||||
#! URL with the returned ID will call it.
|
|
||||||
"httpd-responders" get [
|
|
||||||
"cont" get [
|
|
||||||
[ test-cont-responder ] register-continuation
|
|
||||||
] bind
|
|
||||||
] bind ;
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
(replace 1234567890 with 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 requesting a name. Enter the name and press 'Ok'. A
|
|
||||||
third page will display a message using that name. You can book mark,
|
|
||||||
refresh, go back, enter a new name, etc as expected.
|
|
||||||
|
|
||||||
You can do any form of computation inside the handlers. Here's an
|
|
||||||
example of looping a set number of times:
|
|
||||||
|
|
||||||
: 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 ;
|
|
||||||
|
|
||||||
: register-test-cont-responder2 ( -- id )
|
|
||||||
#! Register the test-cont-responder2 word so that accessing the
|
|
||||||
#! URL with the returned ID will call it.
|
|
||||||
"httpd-responders" get [
|
|
||||||
"cont" get [
|
|
||||||
[ test-cont-responder2 ] register-continuation
|
|
||||||
] bind
|
|
||||||
] bind ;
|
|
||||||
|
|
||||||
There is currently a limited ability to do 'callbacks'. You can
|
|
||||||
register a quotation as an HTML A HREF anchor thar when accessed by
|
|
||||||
the browser will run the quotation and then return to the most recent
|
|
||||||
'show' call. This has the effect of allowing 'subroutine' calls as
|
|
||||||
page links that can do anything (including display other pages and
|
|
||||||
complicated action) and will return back to the originating page. The
|
|
||||||
word to generate this link is:
|
|
||||||
|
|
||||||
: quot-href ( text quot -- )
|
|
||||||
#! Write to standard output an HTML HREF where the href,
|
|
||||||
#! when referenced, will call the quotation and then return
|
|
||||||
#! back to the most recent 'show' call (via the callback-cc).
|
|
||||||
#! The text of the link will be the 'text' argument on the
|
|
||||||
#! stack.
|
|
||||||
"<a href='" write
|
|
||||||
"callback-cc" get [ call ] cons append register-continuation write
|
|
||||||
"'>" write
|
|
||||||
write
|
|
||||||
"</a>" write ;
|
|
||||||
|
|
||||||
An example of use is a simple menu page that displays links to the
|
|
||||||
words written previously:
|
|
||||||
|
|
||||||
: test-cont-responder3 ( alist - )
|
|
||||||
#! Test the quot-href word by displaying a menu of the current
|
|
||||||
#! test words. Note that we drop the 'url' argument to the show
|
|
||||||
#! quotation as we don't link to a 'next' page.
|
|
||||||
drop
|
|
||||||
[
|
|
||||||
drop
|
|
||||||
"Menu" [
|
|
||||||
"<ol>" write
|
|
||||||
"<li>" write
|
|
||||||
"Test responder1" [ test-cont-responder ] quot-href
|
|
||||||
"</li>" write
|
|
||||||
"<li>" write
|
|
||||||
"Test responder2" [ test-cont-responder2 ] quot-href
|
|
||||||
"</li>" write
|
|
||||||
"</ol>" write
|
|
||||||
] html-document
|
|
||||||
] show drop ;
|
|
||||||
|
|
||||||
: register-test-cont-responder3 ( -- id )
|
|
||||||
#! Register the test-cont-responder3 word so that accessing the
|
|
||||||
#! URL with the returned ID will call it.
|
|
||||||
"httpd-responders" get [
|
|
||||||
"cont" get [
|
|
||||||
[ test-cont-responder3 ] register-continuation
|
|
||||||
] bind
|
|
||||||
] bind ;
|
|
||||||
|
|
||||||
You should now be able to click on the menu items, navigate through
|
|
||||||
those pages and when the sequence of pages ends, return back to the
|
|
||||||
original menu page.
|
|
||||||
|
|
||||||
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. 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.
|
|
||||||
|
|
||||||
The code is contained in cont-responder.factor. Here are the steps to
|
|
||||||
run all the examples starting with a default Factor 0.60 install:
|
|
||||||
|
|
||||||
1) java -cp Factor.jar factor.FactorInterpreter
|
|
||||||
-db:factor.db.FileStore:factor.db
|
|
||||||
|
|
||||||
At the prompt enter:
|
|
||||||
|
|
||||||
---8<---
|
|
||||||
USE: httpd-responder
|
|
||||||
default-responders
|
|
||||||
exit
|
|
||||||
---8<---
|
|
||||||
|
|
||||||
2) java -cp Factor.jar factor.FactorInterpreter
|
|
||||||
-db:factor.db.FileStore:factor.db
|
|
||||||
-no-compile
|
|
||||||
|
|
||||||
At the prompt enter:
|
|
||||||
|
|
||||||
---8<---
|
|
||||||
USE: httpd
|
|
||||||
"cont-responder.factor" run-file
|
|
||||||
USE: cont-responder
|
|
||||||
init-cont-responder
|
|
||||||
register-test-cont-responder .
|
|
||||||
---8<---
|
|
||||||
|
|
||||||
Make note of the number returned by the last line call this [1].
|
|
||||||
|
|
||||||
---8<---
|
|
||||||
register-test-cont-responder2 .
|
|
||||||
---8<---
|
|
||||||
|
|
||||||
Make note of the number returned by the last line call this [2].
|
|
||||||
|
|
||||||
---8<---
|
|
||||||
register-test-cont-responder3 .
|
|
||||||
---8<---
|
|
||||||
|
|
||||||
Make note of the number returned by the last line call this [3].
|
|
||||||
|
|
||||||
---8<---
|
|
||||||
8888 httpd
|
|
||||||
---8<---
|
|
||||||
|
|
||||||
For the first example go to http://localhost:8888/cont/[1]
|
|
||||||
where you replace [1] with the number from [1] above. In my system
|
|
||||||
it was:
|
|
||||||
|
|
||||||
http://localhost:8888/cont/0406763029866672
|
|
||||||
|
|
||||||
For the second example go to http://localhost:8888/cont/[2]
|
|
||||||
where you replace [2] with the number from [2] above. In my system
|
|
||||||
it was:
|
|
||||||
|
|
||||||
http://localhost:8888/cont/6018237533813007
|
|
||||||
|
|
||||||
For the menu example go to http://localhost:8888/cont/[3]
|
|
||||||
where you replace [3] with the number from [3] above. In my system
|
|
||||||
it was:
|
|
||||||
|
|
||||||
http://localhost:8888/cont/3568874223456634
|
|
||||||
|
|
||||||
3) You can use the inspector to look at the continuation table:
|
|
||||||
|
|
||||||
http://localhost:8888/inspect/global'httpd-responders'cont'cont
|
|
Loading…
Reference in New Issue