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 effects
cvs
Chris Double 2004-11-20 21:32:02 +00:00
parent cf75abc247
commit e3e434e649
7 changed files with 655 additions and 23 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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