diff --git a/contrib/cont-responder/tutorial.txt b/contrib/cont-responder/tutorial.txt deleted file mode 100644 index fd1e9ef53d..0000000000 --- a/contrib/cont-responder/tutorial.txt +++ /dev/null @@ -1,684 +0,0 @@ -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 - "Hello World" write - "Hello World!" 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 output as correct HTML. It can be tested at the console -very easily: - - USE: html -

"This is a paragraph" write

- =>

This is a paragraph

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

"More text" write

- =>

More text

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

' example above. - -You can use any factor code at any point: - - "text-align: " "red" -

- "Using style " write swap write write -

- =>

Using style text-align: red

- -Tags that are not normally closed are written using XML style closed -tag (ie. with a trailing slash): - - "One" write
"Two" write
- => One
Two
- -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 - - "Hello World" write - "Hello World!" write - - ] 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 -information from the 'room' word which displays memory details about -the running Factor system. It also uses 'room.' which outputs these -details to standard output and this is wrapped in a
 tag so it is
-formatted correctly.
-
-  : memory-stats1 ( -- )
-    [
-      drop
-      
-          "Memory Statistics" write  
-        
-          
-             
-              
-              
-            
-             
-              
-              
-            
-             
-              
-              
-            
-             
-              
-              
-            
-	  
"Total Data Memory" write room unparse write
"Free Data Memory" write unparse write
"Total Code Memory" write unparse write
"Free Code Memory" write unparse write
- -
 room. 
- - ] 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 statistics. - - - - - - - - - -
"Total Data Memory" write unparse write
"Free Data Memory" write unparse write
; - - : memory-stats2 ( -- ) - [ - drop - - "Memory Statistics 2" write - room memory-stats-table 2drop - - ] 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, computation 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 an 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 ( -- ) - [ - - "Flow Example 1" write - -

"Page 1" write

-

"Press to continue" write

- - - ] show drop - [ - - "Flow Example 1" write - -

"Page 2" write

-

"Press to continue" write

- - - ] show drop - [ - drop - - "Flow Example 1" write - -

"Page 3" write

- - - ] 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 shows that some refactoring -would be useful. 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 -- ) - - "Flow Example 1" write - -

"Page " write rot unparse write

- swap [ -

"Press to continue" write

- ] [ - drop - ] ifte - - - ] 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 of '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 an 'id' query parameter with -a number as its value. 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.. - [ - - "Please enter your name" write - -
-

- "Please enter your name:" write - - -

-
- - - ] show [ - "username" get - ] bind ; - - : post-example1 ( -- ) - [ - drop - - "Hello!" write - -

accept-users-name write ", Good to see you!" write

- - - ] 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 - - "Hello!" write - -

write ", Good to see you!" write

- - - ] 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. - -Associating URL's with words -============================ -A web page can contain URL's that when clicked perform some -action. This may be to display other pages, or to affect some server -state. - -The 'cont-responder' system enables an URL to be associated with any -Factor quotation. This quotation will be run when the URL is -clicked. When that quotation exits control is returned to the page -that contained the call. - -The word that enables this is 'quot-href'. It takes two items on the -stack. One is the text to display for the link. The other is the -quotation to run when the link is clicked. This quotation should have -stack effect ( -- ). - -This example displays a number which can be incremented or -decremented. - -0 "counter" set - -: counter-example1 ( - ) - #! Display a global counter which can be incremented or decremented - #! using anchors. - #! - #! We don't need the 'url' argument - [ - drop - - - "Counter: " write "counter" get unparse dup write - - -

"Counter: " write write

-

"++" [ "counter" get succ "counter" set ] quot-href - "--" [ "counter" get pred "counter" set ] quot-href -

- - - ] show - drop ; - - "counter-example1" [ counter-example1 ] install-cont-responder - -Accessing this example from the web browser will display a count of -zero. Clicking '++' or '--' will increment or decrement the count -respectively. This is done by calling a quotation that either -increments or decrements the count when the URL's are clicked. - -Because the count is 'global' in this example, if you clone the -browser window with the count set to a specific value and increment -it, and then refresh the original browser window you will see the most -recent incremented state. This gives you 'shopping cart' like state -whereby using the back button or cloning windows shows a view of a -single global value that can be modified by all browser -instances. ie. The state is not backtracked when the back button is -used. - -You'll notice that when you visit the root URL for the responder that -the count is reset back to zero. This is because when the responder -was installed the value of zero was in the namespace stack. This stack -is copied when the responder is installed resulting in initial -accesses to the URL having the starting value. This gives you 'server -side session data' for free. - -Local State -=========== -You can also have a counter value with 'local' state. That is, cloning -the browser window will give you a new independant state value that -can be incremented. Going to the original browser window and -refreshing will show the original value which can be incremented or -decremented seperately from that value in the cloned window. With this -type of state, using the back button results in 'backtracking' the -state value. - -A way to get 'local' state is to store values on the stack itself -rather than a namespace: - -: counter-example2 ( count - ) - [ ( count URL -- ) - drop - - - "Counter: " write dup unparse write - - -

"Counter: " write dup unparse write

-

"++" over [ succ counter-example2 ] cons quot-href - "--" swap [ pred counter-example2 ] cons quot-href -

- - - ] show - drop ; - - "counter-example2" [ 0 counter-example2 ] install-cont-responder - -This example works by taking the value of the counter and consing it -to a code quotation that will increment or decrement it then call the -responder again. So if the counter value is '5' the two 'quot-href' -calls become the equivalent of: - - "++" [ 5 succ counter-example2 ] cons quot-href - "--" [ 5 pred counter-example2 ] cons quot-href - -Because it calls itself with the new count value the state is -remembered for that page only. This means that each page has an -independant count value. You can clone or use the back button and all -browser windows have an independant value. - -Calling 'Subroutines' -===================== -Being able to call other page display functions from 'quot-href' gives -you subroutine like functionality in your web pages. A simple menu -that displays a sequence of pages and returns back to the main page is -very easy: - - : show-page ( n -- ) - #! Show a page in the flow, using 'n' as the page number - #! to display. - [ ( n url -- ) - - "Page " write over unparse write - -

"Page " write swap unparse write

-

"Press to continue" write

- - - ] show 2drop ; - - : show-some-pages ( n -- ) - #! Display the given number of pages in a row. - [ succ show-page ] times* ; - - : subroutine-example1 ( -- ) - [ - - "Subroutine Example 1" write - -

"Please select:" write -

    -
  1. "Flow1" [ 1 show-some-pages ] quot-href
  2. -
  3. "Flow2" [ 2 show-some-pages ] quot-href
  4. -
  5. "Flow3" [ 3 show-some-pages ] quot-href
  6. -
-

- - - ] show drop ; - - "subroutine-example1" [ subroutine-example1 ] install-cont-responder - -Each item in the ordered list is an anchor. When pressed they will -call a quotation that displays a certain number of pages in a -row. When that quotation finishes via dropping off the end the main -menu page is displayed again. - -Simple Testing -============== -Sometimes it is useful to test the responder words from the console -instead of accessing it via a web browser. This enables you to step -through or quickly check to see if a word is generating HTML -correctly. - -Because the responders require some state associated with them to keep -track of continuation id's and other things you can't usually just run -them and expect them to work. The 'show' call for example will fail as -it expects some continuations to in the continuation table for that -responder. - -The 'cont-testing.factor' file contains some simple words that -maintains this state for you in such a way that you can test the words -from the console: - - "cont-testing.factor" run-file - -For this example we'll call the 'subroutine-example1' responder from -above. First we need to put a 'testing state' object on the stack. All -the testing functions expect this on the stack and return it after -they have been called. We then put a quotation on the stack which -calls the code we want to test and call the 'test-cont-function' word: - - [ subroutine-example1 ] test-cont-function - => - HTTP/1.1 302 Document Moved - Location: ?id=8209741119458310 - Content-Length: 0 - Content-Type: text/plain - -The first request is often a 'Document Moved' as above. This is -because by default the 'cont-responder' system does the -'Post-Refresh-Get' pattern which results in a redirect after each -request. This can be disabled but we'll work through the example with -it enabled. - -We can see the continuation id where we are 'moved' to in the -'Location' header. To access this we use the 'test-cont-click' -function. Think of this as manually clicking the -URL. 'test-cont-click' has stack effect -( state url post -- state). 'post' is a hashtable of post data to pass -along with the request. We use 'f' here because we have no post -data. Remember that our previous 'test-cont-function' call left the -state on the stack: - - "8209741119458310" f test-cont-click - => - HTTP/1.0 200 Document follows - Content-Type: text/html - Subroutine Example 1 -

Please select: -

  1. Flow1
  2. -
  3. Flow2
  4. -
  5. Flow3
  6. -
-

- - - -We can continue to drill down using 'test-cont-click' using the URL's -above to see the HTML for each 'click'. - -Here's an example using post data. We'll test the 'post-example1' word -written previously: - - [ post-example1 ] test-cont-function - => - HTTP/1.1 302 Document Moved - Location: ?id=5829759941409535 - Content-Length: 0 - Content-Type: text/plain - -Again we skip past the forward: - - "5829759941409535" f test-cont-click - => - HTTP/1.0 200 Document follows - Content-Type: text/html - - Please enter your name - -
-

Please enter your name: - - -

-
- - - -Now we submit the post data along to the 'action' url: - - "5456539333180428" [ [ "username" | "Chris" ] ] alist>hash test-cont-click - => - HTTP/1.0 200 Document follows - Content-Type: text/html - - - Hello! - -

Chris, Good to see you!

- - - -As you can see the post data was sent correctly. \ No newline at end of file