Changes to cont-responder:
added start of a tutorial added numbers game example add cont-responder testing functions minor refactoring of cont-responder eval-responder now works again! Changes to parser-combinators: modify str-head and str-tail usage for their changed stack effectscvs
parent
cf75abc247
commit
e3e434e649
|
@ -0,0 +1,103 @@
|
||||||
|
! cont-number-guess
|
||||||
|
!
|
||||||
|
! Copyright (C) 2004 Chris Double.
|
||||||
|
!
|
||||||
|
! Redistribution and use in source and binary forms, with or without
|
||||||
|
! modification, are permitted provided that the following conditions are met:
|
||||||
|
!
|
||||||
|
! 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
! this list of conditions and the following disclaimer.
|
||||||
|
!
|
||||||
|
! 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
! this list of conditions and the following disclaimer in the documentation
|
||||||
|
! and/or other materials provided with the distribution.
|
||||||
|
!
|
||||||
|
! THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
! INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
! FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
! DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
! PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
! OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
! WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
! OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
! ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
!
|
||||||
|
! This example modifies the console based 'numbers-game' example
|
||||||
|
! in a very minimal way to demonstrate conversion of a console
|
||||||
|
! program to a web based application.
|
||||||
|
!
|
||||||
|
! All that was required was changing the input and output functions
|
||||||
|
! to use HTML. The remaining code was untouched.
|
||||||
|
!
|
||||||
|
! The result is not that pretty but it shows the basic idea.
|
||||||
|
IN: numbers-game
|
||||||
|
USE: combinators
|
||||||
|
USE: kernel
|
||||||
|
USE: math
|
||||||
|
USE: random
|
||||||
|
USE: parser
|
||||||
|
USE: html
|
||||||
|
USE: cont-responder
|
||||||
|
USE: cont-utils
|
||||||
|
USE: stack
|
||||||
|
USE: stdio
|
||||||
|
USE: namespaces
|
||||||
|
|
||||||
|
: web-print ( str -- )
|
||||||
|
#! Display the string in a web page.
|
||||||
|
[
|
||||||
|
swap dup
|
||||||
|
<html>
|
||||||
|
<head> <title> write </title> </head>
|
||||||
|
<body>
|
||||||
|
<p> write </p>
|
||||||
|
<p> <a href= a> "Press to continue" write </a> </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show 2drop ;
|
||||||
|
|
||||||
|
: read-number ( -- )
|
||||||
|
[
|
||||||
|
<html>
|
||||||
|
<head> <title> "Enter a number" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<form action= method= "post" form>
|
||||||
|
<p>
|
||||||
|
"Enter a number:" write
|
||||||
|
<input type= "text" name= "num" size= "20" input/>
|
||||||
|
<input type= "submit" value= "Press to continue" input/>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show [ "num" get ] bind parse-number ;
|
||||||
|
|
||||||
|
: guess-banner
|
||||||
|
"I'm thinking of a number between 0 and 100." web-print ;
|
||||||
|
: guess-prompt "Enter your guess: " web-print ;
|
||||||
|
: too-high "Too high" web-print ;
|
||||||
|
: too-low "Too low" web-print ;
|
||||||
|
: correct "Correct - you win!" web-print ;
|
||||||
|
: inexact-guess ( actual guess -- )
|
||||||
|
< [ too-high ] [ too-low ] ifte ;
|
||||||
|
|
||||||
|
: judge-guess ( actual guess -- ? )
|
||||||
|
2dup = [
|
||||||
|
2drop correct f
|
||||||
|
] [
|
||||||
|
inexact-guess t
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: number-to-guess ( -- n ) 0 100 random-int ;
|
||||||
|
|
||||||
|
: numbers-game-loop ( actual -- )
|
||||||
|
dup guess-prompt read-number judge-guess [
|
||||||
|
numbers-game-loop
|
||||||
|
] [
|
||||||
|
drop
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: numbers-game number-to-guess numbers-game-loop ;
|
||||||
|
|
||||||
|
"numbers-game" [ numbers-game ] install-cont-responder
|
|
@ -44,6 +44,9 @@ USE: url-encoding
|
||||||
USE: unparser
|
USE: unparser
|
||||||
USE: hashtables
|
USE: hashtables
|
||||||
|
|
||||||
|
USE: prettyprint
|
||||||
|
USE: inspector
|
||||||
|
|
||||||
: expiry-timeout ( -- timeout-seconds )
|
: expiry-timeout ( -- timeout-seconds )
|
||||||
#! Number of seconds to timeout continuations in
|
#! Number of seconds to timeout continuations in
|
||||||
#! continuation table. This value will need to be
|
#! continuation table. This value will need to be
|
||||||
|
@ -194,9 +197,7 @@ DEFER: show
|
||||||
: with-string-stream ( quot -- string )
|
: with-string-stream ( quot -- string )
|
||||||
#! Call the quotation with standard output bound to a string output
|
#! Call the quotation with standard output bound to a string output
|
||||||
#! stream. Return the string on exit.
|
#! stream. Return the string on exit.
|
||||||
<namespace> [
|
1024 <string-output-stream> dup >r swap with-stream r> stream>str ;
|
||||||
"stdio" 1024 <string-output-stream> put call "stdio" get stream>str
|
|
||||||
] bind ;
|
|
||||||
|
|
||||||
: redirect-to-here ( -- )
|
: redirect-to-here ( -- )
|
||||||
#! Force a redirect to the client browser so that the browser
|
#! Force a redirect to the client browser so that the browser
|
||||||
|
@ -234,8 +235,7 @@ DEFER: show
|
||||||
call-exit-continuation
|
call-exit-continuation
|
||||||
] callcc1
|
] callcc1
|
||||||
nip ;
|
nip ;
|
||||||
USE: prettyprint
|
|
||||||
USE: inspector
|
|
||||||
|
|
||||||
: cont-get-responder ( id-or-f -- )
|
: cont-get-responder ( id-or-f -- )
|
||||||
#! httpd responder that retrieves a continuation and calls it.
|
#! httpd responder that retrieves a continuation and calls it.
|
||||||
|
@ -254,17 +254,12 @@ USE: inspector
|
||||||
] ifte
|
] ifte
|
||||||
[ write flush ] when* drop ;
|
[ write flush ] when* drop ;
|
||||||
|
|
||||||
: post-request>namespace ( post-request -- namespace )
|
|
||||||
#! Return a namespace containing the name/value's from the
|
|
||||||
#! post data.
|
|
||||||
alist>hash ;
|
|
||||||
|
|
||||||
: cont-post-responder ( id -- )
|
: cont-post-responder ( id -- )
|
||||||
#! httpd responder that retrieves a continuation for the given
|
#! httpd responder that retrieves a continuation for the given
|
||||||
#! id and calls it with the POST data as an alist on the top
|
#! id and calls it with the POST data as a hashtable on the top
|
||||||
#! of the stack.
|
#! of the stack.
|
||||||
[
|
[
|
||||||
"response" get post-request>namespace swap resume-continuation
|
"response" get alist>hash swap resume-continuation
|
||||||
] with-exit-continuation
|
] with-exit-continuation
|
||||||
print drop ;
|
print drop ;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
! cont-testing
|
||||||
|
!
|
||||||
|
! Copyright (C) 2004 Chris Double.
|
||||||
|
!
|
||||||
|
! Redistribution and use in source and binary forms, with or without
|
||||||
|
! modification, are permitted provided that the following conditions are met:
|
||||||
|
!
|
||||||
|
! 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
! this list of conditions and the following disclaimer.
|
||||||
|
!
|
||||||
|
! 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
! this list of conditions and the following disclaimer in the documentation
|
||||||
|
! and/or other materials provided with the distribution.
|
||||||
|
!
|
||||||
|
! THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
! INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
! FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
! DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
! PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
! OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
! WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
! OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
! ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
!
|
||||||
|
! Words for testing continuation based responders at the console
|
||||||
|
! prompt.
|
||||||
|
!
|
||||||
|
! To start a 'test' session use '<cont-test-state>' to push the
|
||||||
|
! continuation responder state on the stack.
|
||||||
|
!
|
||||||
|
! Then use 'test-cont-function' to call a continuation responder word.
|
||||||
|
! All output will go to the console. From this output you will see
|
||||||
|
! links that you can 'visit' by doing a simulated click. Use the
|
||||||
|
! 'test-cont-click' function by passing the state, the 'id' of the click
|
||||||
|
! continuation, and 'f' or a hashtable containing the post data. The output
|
||||||
|
! from this will be displayed.
|
||||||
|
!
|
||||||
|
! eg.
|
||||||
|
! <cont-test-state> [ test-cont-responder ] test-cont-function
|
||||||
|
! => HTTP/1.1 302 Document Moved
|
||||||
|
! Location: 8506502852110820
|
||||||
|
! Content-Length: 0
|
||||||
|
! Content-Type: text/plain
|
||||||
|
!
|
||||||
|
! "8506502852110820" f test-cont-click
|
||||||
|
! => HTTP/1.0 200 Document follows
|
||||||
|
! Content-Type: text/html
|
||||||
|
!
|
||||||
|
! <html><head><title>Page one</title></head><body>
|
||||||
|
! <h1>Page one</h1><a href='5431597582800278'>Next</a>
|
||||||
|
! </body></html>
|
||||||
|
!
|
||||||
|
! "5431597582800278" f test-cont-click
|
||||||
|
! => HTTP/1.1 302 Document Moved
|
||||||
|
! Location: 7944183606904129
|
||||||
|
! Content-Length: 0
|
||||||
|
! Content-Type: text/plain
|
||||||
|
!
|
||||||
|
! "7944183606904129" f test-cont-click
|
||||||
|
! => HTTP/1.0 200 Document follows
|
||||||
|
! Content-Type: text/html
|
||||||
|
!
|
||||||
|
! <html><head><title>Enter your name</title></head>
|
||||||
|
! <body><h1>Enter your name</h1>
|
||||||
|
! <form method='post'action='8503790719833723'>
|
||||||
|
! Name: <input type='text'name='name'size='20'>
|
||||||
|
! <input type='submit'value='Ok'>
|
||||||
|
! </form></body></html>
|
||||||
|
!
|
||||||
|
! "8503790719833723" [ [ "name" | "Chris" ] ] alist>hash test-cont-click
|
||||||
|
! => HTTP/1.1 302 Document Moved
|
||||||
|
! Location: 8879727708050260
|
||||||
|
! Content-Length: 0
|
||||||
|
! Content-Type: text/plain
|
||||||
|
!
|
||||||
|
! "8879727708050260" f test-cont-click
|
||||||
|
! => HTTP/1.0 200 Document follows
|
||||||
|
! Content-Type: text/html
|
||||||
|
!
|
||||||
|
! <html><head><title>Hello Chris</title></head>
|
||||||
|
! <body><h1>Hello Chris</h1>
|
||||||
|
! <a href='0937854264503953'>Next</a>
|
||||||
|
! </body></html>
|
||||||
|
!
|
||||||
|
! etc.
|
||||||
|
IN: cont-responder
|
||||||
|
USE: namespaces
|
||||||
|
USE: stack
|
||||||
|
USE: combinators
|
||||||
|
USE: stdio
|
||||||
|
|
||||||
|
: <cont-test-state> ( -- <state> )
|
||||||
|
#! Create a namespace holding data required
|
||||||
|
#! for testing continuation based responder functions
|
||||||
|
#! at the interpreter console.
|
||||||
|
<namespace> [
|
||||||
|
reset-continuation-table
|
||||||
|
init-session-namespace
|
||||||
|
] extend ;
|
||||||
|
|
||||||
|
: test-cont-function ( <state> quot -- <state> )
|
||||||
|
#! Call a continuation responder function with required
|
||||||
|
#! plumbing set up so output is displayed to the console.
|
||||||
|
swap dup >r [
|
||||||
|
[ call ] with-exit-continuation
|
||||||
|
] bind write drop r> ;
|
||||||
|
|
||||||
|
: test-cont-click ( <state> id data -- <state> )
|
||||||
|
#! Test function to 'click' a continuation with the given
|
||||||
|
#! 'id' and post data. Display the results on the console.
|
||||||
|
rot dup >r [
|
||||||
|
[ swap resume-continuation ] with-exit-continuation
|
||||||
|
] bind write 2drop r> ;
|
|
@ -39,7 +39,10 @@ USE: logic
|
||||||
USE: combinators
|
USE: combinators
|
||||||
USE: live-updater
|
USE: live-updater
|
||||||
USE: prettyprint
|
USE: prettyprint
|
||||||
|
USE: unparser
|
||||||
USE: words
|
USE: words
|
||||||
|
USE: vectors
|
||||||
|
USE: logging
|
||||||
|
|
||||||
: <evaluator> ( stack msg history -- )
|
: <evaluator> ( stack msg history -- )
|
||||||
#! Create an 'evaluator' object that holds
|
#! Create an 'evaluator' object that holds
|
||||||
|
@ -71,13 +74,12 @@ USE: words
|
||||||
: escape-quotes ( string -- string )
|
: escape-quotes ( string -- string )
|
||||||
#! Replace occurrences of single quotes with
|
#! Replace occurrences of single quotes with
|
||||||
#! backslash quote.
|
#! backslash quote.
|
||||||
[ dup [ [ "'" | "\\'" ] [ "\"" | "\\\"" ] ] assoc dup rot ? ] str-map ;
|
[ dup [ [ CHAR: ' | "\\'" ] [ CHAR: " | "\\\"" ] ] assoc dup rot ? ] str-map ;
|
||||||
|
|
||||||
: make-eval-javascript ( string -- string )
|
: make-eval-javascript ( string -- string )
|
||||||
#! Give a string return some javascript that when
|
#! Give a string return some javascript that when
|
||||||
#! executed will set the eval textarea to that string.
|
#! executed will set the eval textarea to that string.
|
||||||
[ "document.forms.main.eval.value=\"" , escape-quotes , "\"" , ] make-string ;
|
[ "document.forms.main.eval.value=\"" , escape-quotes , "\"" , ] make-string ;
|
||||||
|
|
||||||
: write-eval-link ( string -- )
|
: write-eval-link ( string -- )
|
||||||
#! Given text to evaluate, create an A HREF link which when
|
#! Given text to evaluate, create an A HREF link which when
|
||||||
#! clicked sets the eval textarea to that value.
|
#! clicked sets the eval textarea to that value.
|
||||||
|
@ -87,7 +89,7 @@ USE: words
|
||||||
#! Write out html to display the stack.
|
#! Write out html to display the stack.
|
||||||
<table border= "1" table>
|
<table border= "1" table>
|
||||||
<tr> <th> "Callstack" write </th> </tr>
|
<tr> <th> "Callstack" write </th> </tr>
|
||||||
[ <tr> <td> write-eval-link </td> </tr> ] each
|
[ <tr> <td> [ unparse write ] with-string-stream write-eval-link </td> </tr> ] each
|
||||||
</table> ;
|
</table> ;
|
||||||
|
|
||||||
: display-clear-history-link ( -- )
|
: display-clear-history-link ( -- )
|
||||||
|
@ -112,7 +114,7 @@ USE: words
|
||||||
"responder" "inspect" put
|
"responder" "inspect" put
|
||||||
<table border= "1" table>
|
<table border= "1" table>
|
||||||
<tr> <th colspan= "2" th> "Source" write </th> </tr>
|
<tr> <th colspan= "2" th> "Source" write </th> </tr>
|
||||||
<tr> <td colspan= "2" td> [ see ] with-simple-html-output </td> </tr>
|
<tr> <td colspan= "2" td> [ [ parse ] [ [ "No such word" write ] [ car see ] ifte ] catch ] with-simple-html-output </td> </tr>
|
||||||
<tr> <th> "Apropos" write </th> <th> "Usages" write </th> </tr>
|
<tr> <th> "Apropos" write </th> <th> "Usages" write </th> </tr>
|
||||||
<tr> <td valign= "top" td> [ apropos. ] with-simple-html-output </td>
|
<tr> <td valign= "top" td> [ apropos. ] with-simple-html-output </td>
|
||||||
<td valign= "top" td> [ usages. ] with-simple-html-output </td>
|
<td valign= "top" td> [ usages. ] with-simple-html-output </td>
|
||||||
|
@ -169,10 +171,18 @@ USE: words
|
||||||
"eval" get
|
"eval" get
|
||||||
] bind ;
|
] bind ;
|
||||||
|
|
||||||
|
: infra ( list quot -- list )
|
||||||
|
#! Call the quotation using 'list' as the datastack
|
||||||
|
#! return the result datastack as a list.
|
||||||
|
datastack >r
|
||||||
|
swap list>vector tuck vector-push
|
||||||
|
set-datastack call datastack vector>list
|
||||||
|
r> >pop> >pop> tuck vector-push set-datastack ;
|
||||||
|
|
||||||
: do-eval ( list string -- list )
|
: do-eval ( list string -- list )
|
||||||
#! Evaluate the expression in 'string' using 'list' as
|
#! Evaluate the expression in 'string' using 'list' as
|
||||||
#! the datastack. Return the resulting stack as a list.
|
#! the datastack. Return the resulting stack as a list.
|
||||||
parse unit append restack call unstack ;
|
parse infra ;
|
||||||
|
|
||||||
: do-eval-to-string ( list string -- list string )
|
: do-eval-to-string ( list string -- list string )
|
||||||
#! Evaluate expression using 'list' as the current callstack.
|
#! Evaluate expression using 'list' as the current callstack.
|
||||||
|
@ -202,8 +212,9 @@ USE: words
|
||||||
[
|
[
|
||||||
run-eval-requester
|
run-eval-requester
|
||||||
] [
|
] [
|
||||||
show-message-page
|
dup [ show-message-page ] [ drop ] ifte
|
||||||
] catch
|
] catch
|
||||||
] forever ;
|
] forever ;
|
||||||
|
|
||||||
"eval" [ [ ] "None" [ ] <evaluator> eval-responder ] install-cont-responder
|
"eval" [ [ ] "None" [ ] <evaluator> eval-responder ] install-cont-responder
|
||||||
|
|
||||||
|
|
|
@ -38,13 +38,15 @@ USE: parser
|
||||||
"cont-responder.factor" run-file
|
"cont-responder.factor" run-file
|
||||||
"cont-utils.factor" run-file ;
|
"cont-utils.factor" run-file ;
|
||||||
: l2
|
: l2
|
||||||
"cont-examples.factor" run-file ;
|
"cont-examples.factor" run-file
|
||||||
|
"cont-numbers-game.factor" run-file ;
|
||||||
: l3 "todo.factor" run-file ;
|
: l3 "todo.factor" run-file ;
|
||||||
: l4 "todo-example.factor" run-file ;
|
: l4 "todo-example.factor" run-file ;
|
||||||
: l5 "live-updater.factor" run-file ;
|
: l5 "live-updater.factor" run-file ;
|
||||||
! : l6 "eval-responder.factor" run-file ;
|
: l6 "eval-responder.factor" run-file ;
|
||||||
: l7 "live-updater-responder.factor" run-file ;
|
: l7 "live-updater-responder.factor" run-file ;
|
||||||
: l8 "browser.factor" run-file ;
|
: l8 "browser.factor" run-file ;
|
||||||
|
: l9 "cont-testing.factor" run-file ;
|
||||||
: la ;
|
: la ;
|
||||||
: la [ 8888 httpd ] [ dup . flush [ la ] when* ] catch ;
|
: la [ 8888 httpd ] [ dup . flush [ la ] when* ] catch ;
|
||||||
: lb [ la "httpd thread exited.\n" write flush ] in-thread ;
|
: lb [ la "httpd thread exited.\n" write flush ] in-thread ;
|
||||||
|
|
|
@ -0,0 +1,407 @@
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
The 'cont-responder' library is a continuation based web server
|
||||||
|
for writing web applications in Factor. Each 'web application' is a
|
||||||
|
standard Factor httpd responder.
|
||||||
|
|
||||||
|
This document outlines how to write simple web applications using
|
||||||
|
'cont-responder' by showing examples. It does not attempt to go into
|
||||||
|
the technical details of continuation based web applications or how it
|
||||||
|
is implemented in Factor. Instead it uses a series of examples that
|
||||||
|
can be immediately tried at the Factor prompt to get a feel for how
|
||||||
|
things work.
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
===============
|
||||||
|
To get started you will first need to load the 'cont-responder'
|
||||||
|
code. You will need the following as a minimum:
|
||||||
|
|
||||||
|
"cont-responder.factor" run-file
|
||||||
|
"cont-utils.factor" run-file
|
||||||
|
USE: cont-responder
|
||||||
|
USE: cont-utils
|
||||||
|
|
||||||
|
The responders that you will be writing will require an instance of
|
||||||
|
the httpd server to be running. It will be run in a background thread
|
||||||
|
to enable the interactive development of the applications. The
|
||||||
|
following is a simple function to start the server on port 8888 and
|
||||||
|
restart it if an error occurs:
|
||||||
|
|
||||||
|
USE: httpd
|
||||||
|
USE: threads
|
||||||
|
: start-httpd [ 8888 httpd ] [ dup . flush [ start-httpd ] when* ] catch ;
|
||||||
|
[ start-httpd ] in-thread
|
||||||
|
|
||||||
|
Responders
|
||||||
|
==========
|
||||||
|
A 'responder' is a word that is registered with the httpd server that
|
||||||
|
gets run when the client accesses a particular URL. When run that word
|
||||||
|
has 'standard output' bound in such a way that all output goes to the
|
||||||
|
clients web browser.
|
||||||
|
|
||||||
|
In the 'cont-responder' system the word used to set output to go to the web
|
||||||
|
browser and display a page is 'show'. Think of it as 'show a page to
|
||||||
|
the client'. 'show' takes a single item on the stack and that is a
|
||||||
|
'page generation' quotation.
|
||||||
|
|
||||||
|
A 'page generation' quotation is a quotation with stack effect (
|
||||||
|
string -- ). For now we'll ignore the string it receives on the
|
||||||
|
stack. Its purpose will be explained later.
|
||||||
|
|
||||||
|
Hello World 1
|
||||||
|
=============
|
||||||
|
A simple 'hello world' responder would be:
|
||||||
|
|
||||||
|
: hello-world1 ( -- )
|
||||||
|
[
|
||||||
|
drop
|
||||||
|
"<html><head><title>Hello World</title></head>" write
|
||||||
|
"<body>Hello World!</body></html>" write
|
||||||
|
] show drop ;
|
||||||
|
|
||||||
|
When installed this will show a single page which is simple HTML to
|
||||||
|
display 'Hello World!'. The 'show' word returns a namespace, the
|
||||||
|
purpose of which will also be explained later. For now we ignore it
|
||||||
|
and drop it. Notice we also drop the 'URL' that the quotation passed
|
||||||
|
to 'show' receives on the stack.
|
||||||
|
|
||||||
|
The responder is installed using:
|
||||||
|
|
||||||
|
"helloworld1" [ hello-world1 ] install-cont-responder
|
||||||
|
|
||||||
|
The 'install-cont-responder' word has stack effect ( name quot --
|
||||||
|
). It installs a responder with the given name. When the URL for that
|
||||||
|
responder is accessed the 'quot' quotation is run. In this case it is
|
||||||
|
'hello-world1' which displays the single HTML page described
|
||||||
|
previously.
|
||||||
|
|
||||||
|
Accessing the above responder from a web browser is via an URL like:
|
||||||
|
|
||||||
|
http://localhost:8888/responder/helloworld1
|
||||||
|
|
||||||
|
This should display an HTML page showing 'Hello World!".
|
||||||
|
|
||||||
|
HTML Generation
|
||||||
|
===============
|
||||||
|
Generating HTML by writing strings containing HTML can be a bit of a
|
||||||
|
chore. Especially when the content is dynamic requiring concatenation
|
||||||
|
of many pieces of data.
|
||||||
|
|
||||||
|
The 'cont-responder' system uses 'html', a library that allows writing
|
||||||
|
HTML looking output directly in factor. This system developed for
|
||||||
|
'cont-responder' has recently been made part of the standard 'html'
|
||||||
|
library of Factor.
|
||||||
|
|
||||||
|
'html' basically allows you to write HTML-like output in a factor word
|
||||||
|
and it will be automatically output. It can be tested at the console
|
||||||
|
very easily:
|
||||||
|
|
||||||
|
USE: html
|
||||||
|
<p> "This is a paragraph" write </p>
|
||||||
|
=> <p>This is a paragraph</p>
|
||||||
|
|
||||||
|
You can write open and close tags like orginary HTML and anything sent
|
||||||
|
to standard output in between the tags will be enclosed in the
|
||||||
|
specified tags. Attributes can also be used:
|
||||||
|
|
||||||
|
<p style= "text-align: center" p> "More text" write </p>
|
||||||
|
=> <p style='text-align: center'>More text</p>
|
||||||
|
|
||||||
|
The attribute must be seperated from the value of that attribute via
|
||||||
|
whitespace. If you are using attributes the tag must be closed with a
|
||||||
|
'[tagname]>' where the [tagname] is the name of the tag used. See the
|
||||||
|
'<p p>' example above.
|
||||||
|
|
||||||
|
You can use any factor code at any point:
|
||||||
|
|
||||||
|
"text-align: " "red"
|
||||||
|
<p style= 2dup cat2 p>
|
||||||
|
"Using style " write swap write write
|
||||||
|
</p>
|
||||||
|
=> <p style='text-align: red'>Using style text-align: red</p>
|
||||||
|
|
||||||
|
Tags that are not normally closed are written using an XML style
|
||||||
|
(ie. with a trailing slash):
|
||||||
|
|
||||||
|
"One" write <br/> "Two" write <br/> <input type= "text" input/>
|
||||||
|
=> One<br>Two<br><input type='text'>
|
||||||
|
|
||||||
|
Hello World 2
|
||||||
|
=============
|
||||||
|
|
||||||
|
Using the HTML generation library makes writing responders more
|
||||||
|
readable. Here is the hello world example perviously using this
|
||||||
|
system:
|
||||||
|
|
||||||
|
: hello-world2 ( -- )
|
||||||
|
[
|
||||||
|
drop
|
||||||
|
<html>
|
||||||
|
<head> <title> "Hello World" write </title> </head>
|
||||||
|
<body> "Hello World!" write </body>
|
||||||
|
</html>
|
||||||
|
] show drop ;
|
||||||
|
|
||||||
|
Install it using:
|
||||||
|
|
||||||
|
"helloworld2" [ hello-world2 ] install-cont-responder
|
||||||
|
|
||||||
|
Dynamic Data
|
||||||
|
============
|
||||||
|
|
||||||
|
Adding dynamic data to the page is relatively easy. This example pulls
|
||||||
|
a value from the 'room' word which displays the amount of available
|
||||||
|
and free memory in the system.
|
||||||
|
|
||||||
|
: memory-stats1 ( -- )
|
||||||
|
[
|
||||||
|
drop
|
||||||
|
<html>
|
||||||
|
<head> <title> "Memory Statistics" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<table border= "1" table>
|
||||||
|
<tr>
|
||||||
|
<td> "Total Memory" write </td>
|
||||||
|
<td> room unparse write </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> "Free Memory" write </td>
|
||||||
|
<td> unparse write </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show drop ;
|
||||||
|
|
||||||
|
"memorystats1" [ memory-stats1 ] install-cont-responder
|
||||||
|
|
||||||
|
Accessing this page will show a table with the current memory
|
||||||
|
statistics. Hitting refresh will update the page with the latest
|
||||||
|
information.
|
||||||
|
|
||||||
|
The HTML output can be refactored into different words. For example:
|
||||||
|
|
||||||
|
: memory-stats-table ( free total -- )
|
||||||
|
#! Output a table containing the given statistcs.
|
||||||
|
<table border= "1" table>
|
||||||
|
<tr>
|
||||||
|
<td> "Total Memory" write </td>
|
||||||
|
<td> unparse write </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> "Free Memory" write </td>
|
||||||
|
<td> unparse write </td>
|
||||||
|
</tr>
|
||||||
|
</table> ;
|
||||||
|
|
||||||
|
: memory-stats2 ( -- )
|
||||||
|
[
|
||||||
|
drop
|
||||||
|
<html>
|
||||||
|
<head> <title> "Memory Statistics 2" write </title> </head>
|
||||||
|
<body> room memory-stats-table </body>
|
||||||
|
</html>
|
||||||
|
] show drop ;
|
||||||
|
|
||||||
|
"memorystats2" [ memory-stats2 ] install-cont-responder
|
||||||
|
|
||||||
|
Some simple flow
|
||||||
|
================
|
||||||
|
The big advantage with continuation based web servers is being able to
|
||||||
|
write a web application in a standard procedural flow and have it
|
||||||
|
correctly served up in the HTTP request/response model.
|
||||||
|
|
||||||
|
This example demonstates a flow of three pages. Clicking an URL on the
|
||||||
|
first page displays the second. Clicking an URL on the second displays
|
||||||
|
the third.
|
||||||
|
|
||||||
|
When a 'show' call is executed the page generated by the quotation is
|
||||||
|
sent to the client. The computation of the responder is then
|
||||||
|
'suspended'. When the client accesses a special URL computiation is
|
||||||
|
resumed at the point of the end of the 'show' call. In this way
|
||||||
|
procedural flow is maintained.
|
||||||
|
|
||||||
|
This brings us to the 'URL' stack item that is available to the 'page
|
||||||
|
generation' quotation passed to 'show'. This URL is a string that
|
||||||
|
contains a URL that can be embedded in the page. When the user access
|
||||||
|
that URL computation is resumed from the point of the end of the
|
||||||
|
'show' call as described above:
|
||||||
|
|
||||||
|
: flow-example1 ( -- )
|
||||||
|
[
|
||||||
|
<html>
|
||||||
|
<head> <title> "Flow Example 1" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<p> "Page 1" write </p>
|
||||||
|
<p> <a href= a> "Press to continue" write </a> </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show drop
|
||||||
|
[
|
||||||
|
<html>
|
||||||
|
<head> <title> "Flow Example 1" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<p> "Page 2" write </p>
|
||||||
|
<p> <a href= a> "Press to continue" write </a> </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show drop
|
||||||
|
[
|
||||||
|
drop
|
||||||
|
<html>
|
||||||
|
<head> <title> "Flow Example 1" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<p> "Page 3" write </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show drop ;
|
||||||
|
|
||||||
|
"flowexample1" [ flow-example1 ] install-cont-responder
|
||||||
|
|
||||||
|
The 'flow-example1' word contains three 'show' calls in a row. The
|
||||||
|
first two display simple pages with an anchor link to the URL received
|
||||||
|
on the stack. This URL when accessed resumes the computation. The
|
||||||
|
final page just drops the URL.
|
||||||
|
|
||||||
|
When you display this example in the browser you'll be able to click
|
||||||
|
the URL to navigate. You can use the back button to retry the URL's,
|
||||||
|
you can clone the browser window and navigate them independantly, etc.
|
||||||
|
|
||||||
|
The similarity of the functions above could so with some
|
||||||
|
refactoring. The pages are almost exactly the same so we seperate this
|
||||||
|
into a seperate word:
|
||||||
|
|
||||||
|
: show-flow-page ( n bool -- )
|
||||||
|
#! Show a page in the flow, using 'n' as the page number
|
||||||
|
#! to display. If 'bool' is true display a link to the
|
||||||
|
#! next page.
|
||||||
|
[ ( n bool url -- )
|
||||||
|
<html>
|
||||||
|
<head> <title> "Flow Example 1" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<p> "Page " write rot unparse write </p>
|
||||||
|
swap [
|
||||||
|
<p> <a href= a> "Press to continue" write </a> </p>
|
||||||
|
] [
|
||||||
|
drop
|
||||||
|
] ifte
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show 3drop ;
|
||||||
|
|
||||||
|
: flow-example2 ( n -- )
|
||||||
|
#! Display the given number of pages in a row.
|
||||||
|
dup pred [ succ t show-flow-page ] times*
|
||||||
|
f show-flow-page ;
|
||||||
|
|
||||||
|
"flowexample2" [ 5 flow-example2 ] install-cont-responder
|
||||||
|
|
||||||
|
In this example the 'show-flow-age' pulls the page number off the
|
||||||
|
stack. It also gets whether or not to display the link to the next
|
||||||
|
page.
|
||||||
|
|
||||||
|
Notice that after the show that a '3drop' is done whereas
|
||||||
|
previously we've only done a single 'drop'. This is due to a side
|
||||||
|
effect or 'show' using continuations.
|
||||||
|
|
||||||
|
After the 'show' call returns there will be one item on the stack
|
||||||
|
(which we've been dropping and will explain later what it is). The
|
||||||
|
stack will also be set as it was before the show call. So in this case
|
||||||
|
the 'n' and 'bool' remain on the stack even though they were removed
|
||||||
|
during the page generation quotation. This is because we resumed the
|
||||||
|
continuation which, when captured, had those items on the stack. The
|
||||||
|
general rule of thumb is you will need to account for items on the
|
||||||
|
stack before the show call.
|
||||||
|
|
||||||
|
This example also demonstrates using the 'times*' combinator to
|
||||||
|
sequence the page shows. Any Factor code can be called and the
|
||||||
|
continuation based system will sequentially display each page. The
|
||||||
|
back button, browser window cloning, etc will all continue to work.
|
||||||
|
|
||||||
|
You'll notice the URL's in the browser have a number at the end of
|
||||||
|
them. This is the 'continuation identifier' which is like a session id
|
||||||
|
except that it identifies not just the data you have stored but your
|
||||||
|
location within the responder as well.
|
||||||
|
|
||||||
|
Forms and POST data
|
||||||
|
===================
|
||||||
|
The web pages we've generated so far don't accept input from the
|
||||||
|
user. I've mentioned previously that 'show' returns a value on the
|
||||||
|
stack and we've been dropping it in our examples.
|
||||||
|
|
||||||
|
The value returned is a namespace containing the field names and
|
||||||
|
values of any POST data in the request. If no POST data exists then it
|
||||||
|
is the boolean value 'f'.
|
||||||
|
|
||||||
|
To process input from the user just put a form in the HTML with a
|
||||||
|
method of 'POST' and an action set to the URL passed in to the page
|
||||||
|
generation quotation. The show call will then return a namespace
|
||||||
|
containing this data. Here is a simple example:
|
||||||
|
|
||||||
|
: accept-users-name ( -- name )
|
||||||
|
#! Display an HTML requesting the users name. Push
|
||||||
|
#! the name the user input on the stack..
|
||||||
|
[
|
||||||
|
<html>
|
||||||
|
<head> <title> "Please enter your name" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<form action= method= "post" form>
|
||||||
|
<p>
|
||||||
|
"Please enter your name:" write
|
||||||
|
<input type= "text" size= "20" name= "username" input/>
|
||||||
|
<input type= "submit" value= "Ok" input/>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show [
|
||||||
|
"username" get
|
||||||
|
] bind ;
|
||||||
|
|
||||||
|
: post-example1 ( -- )
|
||||||
|
[
|
||||||
|
drop
|
||||||
|
<html>
|
||||||
|
<head> <title> "Hello!" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<p> accept-users-name write ", Good to see you!" write </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show drop ;
|
||||||
|
|
||||||
|
"post-example1" [ post-example1 ] install-cont-responder
|
||||||
|
|
||||||
|
The 'accept-users-name' word displays an HTML form allowing input of
|
||||||
|
the name. When that form is submitted the namespace containing the
|
||||||
|
data is returned by 'show'. We bind to it and retrieve the 'username'
|
||||||
|
field. The name used here should be the same name used when creating
|
||||||
|
the field in the HTML.
|
||||||
|
|
||||||
|
'post-example1' then does something a bit tricky. Instead of first
|
||||||
|
calling 'accept-users-name' to push the name on the stack and then
|
||||||
|
displaying the resulting page we call 'accept-users-name' from within
|
||||||
|
the page itself when we actually need it. The magic of the
|
||||||
|
continuation system causes the 'accept-users-name' to be called when
|
||||||
|
needed displaying that page first. It is certainly possible to do it
|
||||||
|
the other way though:
|
||||||
|
|
||||||
|
: post-example2 ( -- )
|
||||||
|
accept-users-name
|
||||||
|
[ ( name url -- )
|
||||||
|
drop
|
||||||
|
<html>
|
||||||
|
<head> <title> "Hello!" write </title> </head>
|
||||||
|
<body>
|
||||||
|
<p> write ", Good to see you!" write </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
] show 2drop ;
|
||||||
|
|
||||||
|
"post-example2" [ post-example2 ] install-cont-responder
|
||||||
|
|
||||||
|
Either way works. Notice that in the 'post-example2' we had to do a
|
||||||
|
'2drop' instead of a 'drop' at the end of the show to remove the
|
||||||
|
additional 'name' that is on the stack. This wasn't needed in
|
||||||
|
'post-example1' because the 'name' was not on the stack at the time of
|
||||||
|
the 'show' call.
|
|
@ -45,7 +45,7 @@ USE: parser
|
||||||
#! For a string this is everything but the first character.
|
#! For a string this is everything but the first character.
|
||||||
#! For a list this is the cdr.
|
#! For a list this is the cdr.
|
||||||
[
|
[
|
||||||
[ string? ] [ 1 str-tail ]
|
[ string? ] [ 1 swap str-tail ]
|
||||||
[ list? ] [ cdr ]
|
[ list? ] [ cdr ]
|
||||||
] cond ;
|
] cond ;
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ USE: parser
|
||||||
dup str-length pick < [
|
dup str-length pick < [
|
||||||
2drop ""
|
2drop ""
|
||||||
] [
|
] [
|
||||||
swap str-head
|
str-head
|
||||||
] ifte ;
|
] ifte ;
|
||||||
|
|
||||||
: (list-take) ( n list accum -- list )
|
: (list-take) ( n list accum -- list )
|
||||||
|
@ -107,7 +107,7 @@ USE: parser
|
||||||
dup str-length pick < [
|
dup str-length pick < [
|
||||||
2drop ""
|
2drop ""
|
||||||
] [
|
] [
|
||||||
swap str-tail
|
str-tail
|
||||||
] ifte ;
|
] ifte ;
|
||||||
|
|
||||||
: list-drop ( n list -- list )
|
: list-drop ( n list -- list )
|
||||||
|
|
Loading…
Reference in New Issue