forked from doublec/factor-articles
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcontresponder.tex
726 lines (600 loc) · 23.8 KB
/
contresponder.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
\chapter{Continuation Based Web Apps}\label{contresponder}
\section{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.
\section{Getting Started}
To get started you will first need to use the `cont-responder'
vocabulary:
\begin{verbatim}
USE: cont-responder
\end{verbatim}
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:
\begin{verbatim}
USE: httpd
USE: threads
: start-httpd [ 8888 httpd ] in-thread ;
start-httpd
\end{verbatim}
\section{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 there are two words used to set output
to go to the web browser and display a page. They are `show' and
`show-final'. Think of them as `show a page to the client'. `show' and
`show-final' both take a single item on the stack and that is a `page
generation' quotation.
A `page generation' quotation is a quotation which when called will
output HTML to stdout. In the httpd system, stdout is bound to the
socket connection to the clients web browser.
The `page generation' quotation passed to `show' should have stack
effect ( string -- ) while that for `show-final' has stack effect
( -- ). The two words have very similar uses.
The big difference is with `show'. It provides an URL to the page
generation quotation that when requested will proceed with execution
immediately following the `show', and any POST request data will be on
the stack. With `show-final' no URL is passed so it is not possible to
`resume' computation. This is explained more fully later.
\section{Hello World 1}
A simple `hello world' responder would be:
\begin{verbatim}
: hello-world1 ( -- )
[
"<html><head><title>Hello World</title></head>" write
"<body>Hello World!</body></html>" write
] show-final ;
\end{verbatim}
When installed this will show a single page which is simple HTML to
display `Hello World!`.
The responder is installed using:
\begin{verbatim}
"helloworld1" [ hello-world1 ] install-cont-responder
\end{verbatim}
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:
\begin{verbatim}
http://localhost:8888/responder/helloworld1
\end{verbatim}
This should display an HTML page showing ``Hello World!''.
\section{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:
\begin{verbatim}
USE: html
<p> "This is a paragraph" write </p>
=> <p>This is a paragraph</p>
\end{verbatim}
You can write open and close tags like ordinary HTML and anything sent
to standard output in between the tags will be enclosed in the
specified tags. Attributes can also be used:
\begin{verbatim}
<p "text-align: center" =style p> "More text" write </p>
=> <p style='text-align: center'>More text</p>
\end{verbatim}
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:
\begin{verbatim}
"text-align: " "red"
<p 2dup cat2 =style p>
"Using style " write swap write write
</p>
=> <p style='text-align: red'>Using style text-align: red</p>
\end{verbatim}
Tags that are not normally closed are written using XML style closed
tag (ie. with a trailing slash):
\begin{verbatim}
"One" write <br/> "Two" write <br/> <input "text" =type input/>
=> One<br>Two<br><input type='text'>
\end{verbatim}
\section{Hello World 2}
Using the HTML generation library makes writing responders more
readable. Here is the hello world example perviously using this
system:
\begin{verbatim}
: hello-world2 ( -- )
[
<html>
<head> <title> "Hello World" write </title> </head>
<body> "Hello World!" write </body>
</html>
] show-final ;
\end{verbatim}
Install it using:
\begin{verbatim}
"helloworld2" [ hello-world2 ] install-cont-responder
\end{verbatim}
\section{Dynamic Data}
Adding dynamic data to the page is relatively easy. This example pulls
information from the `room' word which returns memory details about
the running Factor system. It also uses `room.' which outputs these
details to standard output and this is wrapped in a $<$pre$>$ tag so it is
formatted correctly.
\begin{verbatim}
: memory-stats1 ( -- )
[
<html>
<head> <title> "Memory Statistics" write </title> </head>
<body>
<table "1" =border table>
<tr>
<td> "Total Data Memory" write </td>
<td> room unparse write </td>
</tr>
<tr>
<td> "Free Data Memory" write </td>
<td> unparse write </td>
</tr>
<tr>
<td> "Total Code Memory" write </td>
<td> unparse write </td>
</tr>
<tr>
<td> "Free Code Memory" write </td>
<td> unparse write </td>
</tr>
</table>
</body>
<pre> room. </pre>
</html>
] show-final ;
"memorystats1" [ memory-stats1 ] install-cont-responder
\end{verbatim}
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:
\begin{verbatim}
: memory-stats-table ( free total -- )
#! Output a table containing the given statistics.
<table "1" =border table>
<tr>
<td> "Total Data Memory" write </td>
<td> unparse write </td>
</tr>
<tr>
<td> "Free Data Memory" write </td>
<td> unparse write </td>
</tr>
</table> ;
: memory-stats2 ( -- )
[
<html>
<head> <title> "Memory Statistics 2" write </title> </head>
<body> room memory-stats-table 2drop </body>
</html>
] show-final ;
"memorystats2" [ memory-stats2 ] install-cont-responder
\end{verbatim}
\section{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:
\begin{verbatim}
: 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
[
<html>
<head> <title> "Flow Example 1" write </title> </head>
<body>
<p> "Page 3" write </p>
</body>
</html>
] show-final ;
"flowexample1" [ flow-example1 ] install-cont-responder
\end{verbatim}
The `flow-example1' word contains two `show' calls in a row, followed
by a `show-final'. The `show' calls display simple pages with an anchor
link to the URL received on the stack. This URL when accessed resumes
the computation. The final page doesn't require resumption of the
computation so `show-final' is used. We could have used `show' and
dropped the URL passed to the quotation and the result following the
`show' but using `show-final' is more efficient.
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:
\begin{verbatim}
: 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 1 - [ dup 1 + t show-flow-page ] repeat
f show-flow-page ;
"flowexample2" [ 5 flow-example2 ] install-cont-responder
\end{verbatim}
In this example the `show-flow-page' 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 `repeat' 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 sequence of characters 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.
\section{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:
\begin{verbatim}
: 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 "post" =method form>
<p>
"Please enter your name:" write
<input "text" =type "20" =size "username" =name input/>
<input "submit" =type "Ok" =value input/>
</p>
</form>
</body>
</html>
] show [
"username" get
] bind ;
: post-example1 ( -- )
[
<html>
<head> <title> "Hello!" write </title> </head>
<body>
<p> accept-users-name write ", Good to see you!" write </p>
</body>
</html>
] show-final ;
"post-example1" [ post-example1 ] install-cont-responder
\end{verbatim}
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:
\begin{verbatim}
: post-example2 ( -- )
accept-users-name
[ ( name url -- )
<html>
<head> <title> "Hello!" write </title> </head>
<body>
<p> write ", Good to see you!" write </p>
</body>
</html>
] show-final ;
"post-example2" [ post-example2 ] install-cont-responder
\end{verbatim}
\section{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.
\begin{verbatim}
0 "counter" set
: counter-example1 ( - )
#! Display a global counter which can be incremented or decremented
#! using anchors.
[
<html>
<head>
<title> "Counter: " write "counter" get unparse dup write </title>
</head>
<body>
<h2> "Counter: " write write </h2>
<p> "++" [ "counter" get 1 + "counter" set ] quot-href
"--" [ "counter" get 1 - "counter" set ] quot-href
</p>
</body>
</html>
] show-final ;
"counter-example1" [ counter-example1 ] install-cont-responder
\end{verbatim}
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.
\section{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:
\begin{verbatim}
: counter-example2 ( count - )
[ ( count URL -- )
<html>
<head>
<title> "Counter: " write dup unparse write </title>
</head>
<body>
<h2> "Counter: " write dup unparse write </h2>
<p> "++" over [ 1 + counter-example2 ] cons quot-href
"--" swap [ 1 - counter-example2 ] cons quot-href
</p>
</body>
</html>
] show-final ;
"counter-example2" [ 0 counter-example2 ] install-cont-responder
\end{verbatim}
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:
\begin{verbatim}
"++" [ 5 1 + counter-example2 ] cons quot-href
"--" [ 5 1 - counter-example2 ] cons quot-href
\end{verbatim}
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.
\section{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:
\begin{verbatim}
: show-page ( n -- )
#! Show a page in the flow, using 'n' as the page number
#! to display.
[ ( n url -- )
<html>
<head> <title> "Page " write over unparse write </title> </head>
<body>
<p> "Page " write swap unparse write </p>
<p> <a =href a> "Press to continue" write </a> </p>
</body>
</html>
] show 2drop ;
: show-some-pages ( n -- )
#! Display the given number of pages in a row.
[ dup 1 + show-page ] repeat ;
: subroutine-example1 ( -- )
[
<html>
<head> <title> "Subroutine Example 1" write </title> </head>
<body>
<p> "Please select:" write
<ol>
<li> "Flow1" [ 1 show-some-pages ] quot-href </li>
<li> "Flow2" [ 2 show-some-pages ] quot-href </li>
<li> "Flow3" [ 3 show-some-pages ] quot-href </li>
</ol>
</p>
</body>
</html>
] show-final ;
"subroutine-example1" [ subroutine-example1 ] install-cont-responder
\end{verbatim}
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.
\section{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 (in the contrib/cont-responder
directory) contains some simple words that maintains this state for
you in such a way that you can test the words from the console:
\begin{verbatim}
"/contrib/cont-testing/load.factor" run-resource
\end{verbatim}
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:
\begin{verbatim}
<cont-test-state> [ subroutine-example1 ] test-cont-function
=>
HTTP/1.1 302 Document Moved
Location: ?id=8209741119458310
Content-Length: 0
Content-Type: text/plain
\end{verbatim}
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:
\begin{verbatim}
"8209741119458310" f test-cont-click
=>
HTTP/1.0 200 Document follows
Content-Type: text/html
<html><head><title>Subroutine Example 1</title></head>
<body><p>Please select:
<ol><li><a href='?id=7687398605200513'>Flow1</a></li>
<li><a href='?id=7856272029924613'>Flow2</a></li>
<li><a href='?id=4909116160485714'>Flow3</a></li>
</ol>
</p>
</body>
</html>
\end{verbatim}
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:
\begin{verbatim}
<cont-test-state> [ post-example1 ] test-cont-function
=>
HTTP/1.1 302 Document Moved
Location: ?id=5829759941409535
Content-Length: 0
Content-Type: text/plain
\end{verbatim}
Again we skip past the forward:
\begin{verbatim}
"5829759941409535" f test-cont-click
=>
HTTP/1.0 200 Document follows
Content-Type: text/html
<html><head><title>Please enter your name</title></head>
<body>
<form action='?id=5456539333180428' method='post'>
<p>Please enter your name:
<input type='text'size='20'name='username'>
<input type='submit'value='Ok'>
</p>
</form>
</body>
</html>
\end{verbatim}
Now we submit the post data along to the `action' url:
\begin{verbatim}
"5456539333180428" [ [[ "username" "Chris" ]] ] alist>hash test-cont-click
=>
HTTP/1.0 200 Document follows
Content-Type: text/html
<html>
<head><title>Hello!</title></head>
<body>
<p>Chris, Good to see you!</p>
</body>
</html>
\end{verbatim}
As you can see the post data was sent correctly.