More howto. Draft like version.

This commit is contained in:
interp 2004-01-15 03:46:27 +00:00
parent f231780bf0
commit 721fee4e07
2 changed files with 587 additions and 30 deletions

View File

@ -3,12 +3,14 @@
\def\surflet{SUrflet\xspace}
\def\surflets{SUrflets\xspace}
\def\scsh{scsh\xspace}
\def\sunet{SUnet\xspace}
%From sunet/decls.tex
\def\ie{\mbox{\emph{i.e.}} } % \mbox keeps the last period from
\def\Ie{\mbox{\emph{I.e.}} } % looking like an end-of-sentence.
\def\eg{\mbox{\emph{e.g.}} }
\def\Eg{\mbox{\emph{E.g.}} }
\def\ie{\mbox{\emph{i.e.}\xspace}} % \mbox keeps the last period from
\def\Ie{\mbox{\emph{I.e.}\xspace}} % looking like an end-of-sentence.
\def\eg{\mbox{\emph{e.g.}\xspace}}
\def\Eg{\mbox{\emph{E.g.}\xspace}}
%From surflet/decls.tex
{\theoremstyle{break}
@ -17,13 +19,15 @@
\setlength{\theorempreskipamount}{1.5ex plus0.2ex minus0.2ex}
\setlength{\theorempostskipamount}{2ex plus0.5ex minus0.2ex}
% These environments differ from the other definition by the
% positioning of \normalem
\newenvironment{listing}
{\ULforem\begin{proglist}\begin{alltt}\small}
{\end{alltt}\end{proglist}\normalem}
{\ULforem\begin{proglist}\begin{alltt}\small\normalem}
{\end{alltt}\end{proglist}}
\newenvironment{reflisting}[1]
{\ULforem\begin{proglist}[\refinlisting{#1}]\begin{alltt}\small}
{\end{alltt}\end{proglist}\normalem}
{\ULforem\begin{proglist}[\refinlisting{#1}]\begin{alltt}\small\normalem}
{\end{alltt}\end{proglist}}
\newcommand{\contatlisting}[1]{%
{\normalfont\textit{$<$wird fortgesetzt in Listing~\ref{#1}\/$>$}}}
@ -62,5 +66,8 @@
\newcommand{\name}[1]{\breakfuntt{#1}}
\newcommand{\object}[1]{\breakfuntt{#1}}
\newcommand{\file}[1]{\breakfuntt{#1}}
\def\sharpf{\textnormal{\texttt{\#f}}}
\def\sharpt{\textnormal{\texttt{\#t}}}
\newcommand{\codemph}[1]{\emph{#1}}
\makeatother

View File

@ -17,6 +17,9 @@
\begin{document}
\maketitle
\tableofcontents
\sloppy
\section{Introduction}
This report gives a short introduction in how to write a \surflet. It
is concentrated on the practical side rather on describing the
@ -24,6 +27,7 @@ is concentrated on the practical side rather on describing the
surflets. The \surflet API will be described in the SUnet
documentation eventually.
%\marginpar{\surflets are pieces of code for web site scripting.}
For those who don't know it already, \surflets are pieces of code that
can be executed interactively trough a website. There is a \surflet
handler who administrates their execution and suspension and as part
@ -63,7 +67,7 @@ the ability to serve \surflets. This section tells you how to do it.
\begin{itemize}
\item Download Oleg's SSAX package from
\url{http://prdownloads.sourceforge.net/ssax/ssax-sr5rs-plt200-4.9.tar.gz?download}.
\url{http://prdownloads.sf.net/ssax/ssax-sr5rs-plt200-4.9.tar.gz?download}.
\item Uncompress and untar it to any directory. This will create a
directory called \name{SSAX}, to which I will refer to as
@ -410,6 +414,7 @@ sending function is \name{send-html} which just sends a web page.
your \surflet instance.
\subsection{Abbreviations in SXML}
\label{sxml-abbrvs}
The example in subsection ``Several web pages in a row'' wrote down
the link to the next web page explicitly via the ``a''-tag. As
@ -436,7 +441,8 @@ section.
\section{How to write web forms}
The \surflets come along with a libary for easy user interaction. The
following subsections will show how to write web forms.
following subsections will show how to write web forms and how to get
the data the user has entered.
\subsection{Simple web forms}
@ -483,19 +489,21 @@ interaction elements: a text input field and a submit button.
\surflets represent user interaction elements by \name{Input-field}
objects. Thus, user interaction elements are first class values in
\surflet, unlike in many other web scripting languages, \eg Java
surflets, PHP or Microsoft Active Server Pages. You'll see soon what
the advantages of this approach are.
surflets, PHP or Microsoft Active Server Pages, \ie you have a
representation of a user interaction element in your program that you
can pass to functions, receive them as return values, etc. You'll see
soon the advantages of this approach.
\begin{alltt}
(req (send-html/suspend
(lambda (k-url)
`(html
(body
(h1 "Echo")
(surflet-form ,k-url
(p "Please enter something:"
,text-input
,submit-button)))))))
(req (send-html/suspend
(lambda (k-url)
`(html
(body
(h1 "Echo")
(surflet-form ,k-url
(p "Please enter something:"
,text-input
,submit-button)))))))
\end{alltt}
Instead of discarding the return value of \name{send-html/suspend} as
@ -545,7 +553,7 @@ request object. Here we save the user data into the variable
\code{GET} and \code{POST}.
\begin{alltt}
(user-input (input-field-value text-input bindings)))
(user-input (input-field-value text-input bindings)))
\end{alltt}
With the function \name{input-field-value} and the extracted user data
@ -580,6 +588,7 @@ The complete list of functions that create \name{input-fields} can be
found in the API.
\subsection{Return types other than strings}
\label{subsec:input-return}
As the user interaction elements are first class values in a \surflet,
they can return other types than strings. For example the \surflets
@ -587,7 +596,7 @@ come with a number input field, \ie a input field that accepts only
text that can be interpreted as a number. If the user enters
something that is not a number, \name{input-field-value} will return
\sharpf as the value of the number input field. If you'd rather want
an error to be raise, you can use \name{raw-input-field-value}
an error to be raised, you can use \name{raw-input-field-value}
instead.
\subsubsection{Annotated input fields}
@ -595,11 +604,12 @@ instead.
The return value of an input field need not even be a primitive
value. The \surflets library allows you to ``annotate'' your input
fields with values which should be returned indicated by the user's
input. \Eg, Consider this \surflet:
input. \Eg, consider this \surflet:
\begin{alltt}
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
handle-fatal-error
scheme-with-scsh)
(begin
(define (main req)
@ -620,14 +630,17 @@ input. \Eg, Consider this \surflet:
,select-input-field)
,(make-submit-button)))))))
(bindings (get-bindings req))
(price (input-field-value select-input-field bindings)))
(price (input-field-value select-input-field
bindings)))
(send-html/finish
`(html (head (title "Receipt"))
(body
(h2 "Your receipt:")
(p "This costs you \$" ,price "."))))))
))
\end{alltt}
\end{listing}
Let's go through the important part of this \surflet:
\begin{alltt}
(let* ((select-input-field
@ -640,13 +653,550 @@ input. \Eg, Consider this \surflet:
Here we define a select input field (a dropdown list). Instead of
only providing a list of values that shall show up in the dropdown
list and later examining which one was select and looking up the price
for the sweet, we bind the values in the list with price while we
for the sweet, we bind the values in the list with the price while we
create the select input field. When the select input field is shown
in the browser, it will show the names of the sweets. When we lookup
the user's input, we will get the associated price for the sweet.
Again, this works not only with numbers, but with any arbitrary Scheme
value (\eg functions or records).
More to come soon.
\subsection{Sending error messages}
\end{document}
If a user tries to forge a \surflet-URL (\eg by extracting the
continuation URL from the HTML source and editing it), your \surflet
has to deal with unexpected values. Usually, a forged \surflet-URL
will result in an error that is raised in one of the \surflet library
functions. If you don't catch this error, the \surflet handler will
catch it for you, send an error message to the user
\emph{and terminating the current session} as your \surflet obviously
encountered an unexpected error and might be in an invalid state. If
you don't want this behavior, you can catch this error (like any other
error that is raised by \scsh) and send your own error message with
\name{send-error} which is located in the \name{surflets/error}
package. The \name{handle-fatal-error} package can be useful in this
context. Here's an example, that modifies the example from the
previous subsection (modifications emphasized):
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
\codemph{ handle-fatal-error
surflets/error}
scheme-with-scsh)
(begin
(define (main req)
(let* ((select-input-field
(make-select
(map make-annotated-select-option
'("Icecream" "Chocolate" "Candy")
'(1.5 2.0 0.5))))
(req (send-html/suspend
(lambda (k-url)
`(html
(head (title "Sweet Store"))
(body
(h1 "Your choice")
(surflet-form
,k-url
(p "Select the sweet you want:"
,select-input-field)
,(make-submit-button)))))))
(bindings (get-bindings req))
\codemph{ (cost (with-fatal-error-handler
(lambda (condition decline)
(send-error (status-code bad-request)
req
"No such option or internal
error. Please try again."))
(raw-input-field-value select-input-field
bindings))))}
(send-html/finish
`(html (head (title "Receipt"))
(body
(h2 "Your receipt:")
(p "This costs you \$" ,cost "."))))))
))
\end{listing}
Let's examine the important part of this example:
\begin{alltt}
(cost (with-fatal-error-handler
(lambda (condition decline)
(send-error (status-code bad-request)
req
"No such option or internal
error. Please try again."))
(raw-input-field-value select-input-field
bindings))))
\end{alltt}
As mentioned in \ref{subsec:input-return}, this \surflet uses
\name{raw-input-field-value} instead of \name{input-field-value}
because the former raises an error while the latter returns \sharpf in
case of an error.
If a user forges a continuation URL, \name{raw-input-field-value}
might not be able to find a valid value for the
\name{select-input-field} and raises an error. This error is catched
by the error handler which was installed by
\name{with-fatal-error-handler}. The error handler uses
\name{send-error} to send an error message to the browser. Its first
argument is the status code of the error message. See the
documentation of the \sunet webserver for different status codes. The
second argument is the request which was processed while the error
occured. The last argument is a free message text to explain the
cause of the error to the user.
While in the original \surflet the user will still see the resulting
receipt web page with an empty dollar amount and has her session
finished, this modified version will show an error message and won't
finish the session.
It is your choice, which version you choose, \ie if you let the
\surflet handler handle the occuring error automatically or if you
install your own error handlers and use \name{raw-input-field-value}.
However, be careful if you use \name{raw-input-field-value} along with
check boxes. The HTML standard dictates that an unchecked check box
does not appear in the data the browser sends to the server. Thus,
\name{raw-input-field-value} won't find the check box in the data and
raise an error which is not a ``real'' error as you migh expect it.
\subsection{Your own input fields}
The \surflet library contains constructors for all input fields that
are described in the HTML~2.0 standard. See the \surflet API for a
complete list. The \surflet library also allows you to create your
own input fields, \eg an input field that only accepts valid dates as
its input. This subsection gives you a short overview how to do
this. You will find the details in the \surflet API.
Let's have a look at an \surflet that uses its own input field. The
``input field'', called nibble input field, consists of eight check
boxes which represent bits of a nibble (half a byte). The value of
the input field is the number that the check boxes represent. \Eg, if
the user checks the last two checkboxes, the value of the nibble input
field is 3.
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
surflets/my-input-fields
scheme-with-scsh)
(begin
(define (make-nibble-input-fields)
(let ((checkboxes (list (make-annotated-checkbox 8)
(make-annotated-checkbox 4)
(make-annotated-checkbox 2)
(make-annotated-checkbox 1))))
(make-multi-input-field
#f "nibble-input"
(lambda (input-field bindings)
(let loop ((sum 0)
(checkboxes checkboxes))
(if (null? checkboxes)
sum
(loop (+ sum (or (input-field-value (car checkboxes)
bindings)
0))
(cdr checkboxes)))))
'()
(lambda (ignore)
checkboxes))))
(define nibble-input-field (make-nibble-input-fields))
(define (main req)
(let* ((req (send-html/suspend
(lambda (new-url)
`(html (title "Nibble Input Widget")
(body
(h1 "Nibble Input Widget")
(p "Enter your nibble (msb left):")
(surflet-form ,new-url
,nibble-input-field
,(make-submit-button)))))))
(bindings (get-bindings req))
(number (input-field-value nibble-input-field bindings)))
(send-html
`(html (title "Result")
(body
(h2 "Result")
(p "You've entered " ,number "."))))))
))
\end{listing}
Let's go through this \surflet step by step.
\begin{alltt}
(define-structure surflet surflet-interface
(open surflets
surflets/my-input-fields
scheme-with-scsh)
\end{alltt}
If you want to create your own input fields, you have to open the
\name{surflets/my-input-fields} package.
\begin{alltt}
(begin
(define (make-nibble-input-fields)
(let ((checkboxes (list (make-annotated-checkbox 8)
(make-annotated-checkbox 4)
(make-annotated-checkbox 2)
(make-annotated-checkbox 1))))
\end{alltt}
\name{make-nibble-input-fields} is the constructor for our new type of
input field. As mentioned before, we use check boxes to let the user
enter the nibble. We use annotated checkboxes for this purpose whose
value is the value in the nibble.
\begin{alltt}
(make-multi-input-field
#f "nibble-input"
\end{alltt}
The value of our new input field will depend on the value of several
real input fields. Thus we create a multi input field. If the value
depended only on the browser data that is associated to the name of
our input field, we would use \name{make-input-field} instead, which
creates a usual input field. \Eg, if we wanted to create a date input
field that accepts only valid dates as input and used a text input
field for this purpose, we would use \name{make-input-field}.
The first two parameters is the name of the input field and its type.
As we use checkboxes to represent our input field, we don't need the
name field. The type field is meant for debugging purposes, so you
can identify the type of the input field during a debugging session.
\begin{alltt}
(lambda (input-field bindings)
(let loop ((sum 0)
(checkboxes checkboxes))
(if (null? checkboxes)
sum
(loop (+ sum (or (input-field-value (car checkboxes)
bindings)
0))
(cdr checkboxes)))))
\end{alltt}
The next parameter is the so called transformer function.
\name{raw-input-field-value} calls the transformer function to
determine the value of the input field depending on the given
bindings. The transformer function of a multi input field (which our
nibble input field is) gets the input field and the bindings as
parameters. A usual input field would only get the data that is
associated to its name.
The transformer function of our nibble input field goes over each
check box, looks it up in the bindings and adds its value to a sum, if
\name{input-field-value} can find it. If it can't find it, a zero is
added instead. The value of our nibble input field is the resulting
sum.
The rest of the \surflet is straight forward and not repeated here
again. We create, use and evaluate the nibble input field as we do
with every other input field.
\section{Program flow control}
With the techniques shown so far it is rather difficult to create a
web page that has several different successor webpages rather than
only one web page. This section will show you how to do this with the
\surflets. Basically, there are two different methods how to perform
this task. One method is to mark each link in some way and evaluate
the mark after \name{send-html/suspend} has returned. The other
method is to bind a callback function to each link that is called when
the user selects the link. This section shows both methods.
\subsection{Dispatching to more than one successor web page}
The basic idea of dispatching is to add a mark to a link and evaluate
it after the user has clicked on a link and \name{send-html/suspend}
returned. Let's have a look at an example. It shows an entry page at
which the user states the language in which she wants to be greeted:
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
scheme-with-scsh)
(begin
(define (main req)
(let* ((english (make-address))
(german (make-address))
(req (send-html/suspend
(lambda (k-url)
`(html
(head (title "Multi-lingual"))
(body
(h2 "Select your language:")
(ul
(li (url ,(english k-url) "English")
(li (url ,(german k-url) "Deutsch")))))))))
(bindings (get-bindings req)))
(case-returned-via bindings
((english) (result-page "Hello, how are you?"))
((german) (result-page "Hallo, wie geht es Ihnen?")))))
(define (result-page text)
(send-html/finish
`(html
(head (title "Greeting"))
(body
(h2 ,text)))))
))
\end{listing}
Let's see how \name{main} presents the different options:
\begin{alltt}
(define (main req)
(let* ((english (make-address))
(german (make-address))
\end{alltt}
Of course you don't have to worry about adding the mark to the links.
Instead, we create the links with \name{make-address}.
\begin{alltt}
(req (send-html/suspend
(lambda (k-url)
`(html
(head (title "Multi-lingual"))
(body
(h2 "Select your language:")
(ul
(li (url ,(english k-url) "English")
(li (url ,(german k-url) "Deutsch")))))))))
\end{alltt}
\name{make-address} returns a function you can call to create the
link as we did here with
\begin{alltt}
(li (url ,(english k-url) "English")
\end{alltt}
This creates a list item which contains a hyperlink labeled
``English''. The hyperlink is created by the SXML abbreviation
\name{url} as shown in \ref{sxml-abbrvs}. Instead of just passing
the continuation URL \name{k-url} to \name{url}, we create the marked
link by calling the function \name{make-adddress} gave us.
\begin{alltt}
(bindings (get-bindings req)))
(case-returned-via bindings
((english) (result-page "Hello, how are you?"))
((german) (result-page "Hallo, wie geht es Ihnen?")))))
\end{alltt}
After \name{send-html/suspend} has returned, we can evaluate which
link the user has clicked by using \name{case-returned-via}.
\name{case-returned-via} works similar to the regular \name{case} of
Scheme. It evaluates the body of the form whose initial list contains
the address that the user used to leave the website. \Eg, if the user
has selected ``German'' as her preferred language and thus clicked on
the link we have named \name{german} in our \surflet,
\name{case-returned-via} will evaluate its second form and the
\surflet will display the greeting in German.
\name{case-returned-via} is syntactic sugar like the regular
\name{case}. However, instead of \name{equal?} it uses
\name{returned-via}. \name{returned-via} takes the bindings and
and an address and returns \sharpt, if the user left the web page via
this address (\ie, via the link that is represented by this address)
and \sharpf otherwise. \name{returned-via} does not end with a
question mark as it might return other values as well as we will see
shortly. Of course, it is your choice if you want to use
\name{case-returned-via} or explicitly \name{returned-via}.
\subsection{Annotated dispatching}
The approach shown in the previous subsection has one major drawback:
the meaning of an address becomes clear only when you look at the
dispatching section of \name{case-returned-via}. This subsection
shows you how to link the meaning and the representation of an address
closer together.
We modify the previous code example slightly to this \surflet
(differences emphasized):
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
scheme-with-scsh)
(begin
(define (main req)
(let* (\codemph{(language (make-annotated-address))}
(req (send-html/suspend
(lambda (k-url)
`(html
(head (title "Multi-lingual"))
(body
(h2 "Select your language:")
(ul
(li (url ,\codemph{(language k-url
"Hello, how are you?")}
"English")
(li (url ,\codemph{(language k-url
"Hallo, wie geht es Ihnen?")}
"Deutsch")))))))))
(bindings (get-bindings req)))
(case-returned-via bindings
\codemph{ ((language) => result-page))))}
(define (result-page text)
(send-html/finish
`(html
(head (title "Greeting"))
(body
(h2 ,text)))))
))
\end{listing}
Let's look at the differing parts:
\begin{alltt}
(let* ((language (make-annotated-address))
\end{alltt}
To link the meaning with the address itself, we use an annotated
address. As we can annotate the address now, we don't need two
distinct addresses anymore.
\begin{alltt}
(li (url ,(language k-url
"Hello, how are you?")
"English")
(li (url ,(language k-url
"Hallo, wie geht es Ihnen?")
"Deutsch")))))))))
\end{alltt}
In addition to the continuation URL \name{k-url} we also annotate the
address with a value. Here we use the different greetings as the
annotation. The address can be annotated with any arbitrary Scheme
value, \eg functions or records.
\begin{alltt}
(case-returned-via bindings
((language) => result-page))))
\end{alltt}
\name{case-returned-via} has an extended syntax similar to \name{cond}
that it useful with annotated address. The arrow \name{=>} calls the
following function with the annotation of the address via which the
user has left the web page. You can extract the annotation yourself
with \name{returned-via} like this:
\begin{alltt}
(result-page (returned-via language bindings))
\end{alltt}
This will call \name{result-page} with the annotation of the address
via which the user has left the web page. \name{returned-via} returns
\sharpf, if the user didn't leave the web page via one of the links
created with this address (which is not really possible in this
example).
\subsection{Callbacks}
The other method to lead to different successor web pages is using
callbacks. A callback is a function that is called if the user leaves
the web page via an associated link. This is different from the
dispatch method where \name{send-html/suspend} returns. You can
create a web page that only uses callbacks to lead to successor web
page and thus you don't have to use \name{send-html/suspend}.
Instead, you can use \name{send-html}.
Although it is possible to use several different callbacks in a single
web page, this is not recommended. The reason lies in the
implementation of a callback, which saves the current continuation.
Several different callbacks would result in the storage of the several
slightly different continuations. This is unnecessary, as you can
annotate the callbacks with the arguments for the callback function.
Let's see an example which is a variation of the previous examples
(important parts emphasized):
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
\codemph{surflets/callbacks}
scheme-with-scsh)
(begin
(define (main req)
(let ((language \codemph{(make-annotated-callback result-page)}))
(\codemph{send-html}
`(html
(head (title "Multi-lingual"))
(body
(h2 "Select your language:")
(ul
(li (url ,\codemph{(language "Hello, how are you?")}
"English")
(li (url ,\codemph{(language "Hallo, wie geht es Ihnen?")}
"Deutsch")))))))))
(define \codemph{(result-page req text)}
(send-html/finish
`(html
(head (title "Greeting"))
(body
(h2 ,text)))))
))
\end{listing}
The differences to the dispatch method are the following: you have to
open the \name{surflets/callbacks} package to use callbacks, you don't
use the continuation URL to create the callback link, and the
callbacked function must accept the request from the browser as the
first argument. Furthermore, you don't have to use
\name{send-html/suspend}, if a user can only leave your web page via
callbacks. However, it can be sensible to combine the dispatch and
the callback method, so you have to use \name{send-html/suspend}.
\section{Outlook}
More to come soon about data management, \surflets containing of
different parts and individual SXML.
%\section{Data management}
%
%There are different kinds of data that float around in a \surflet,
%actually in any serve side scripted program: There is local data that
%is associated to each session, \ie each session has its own copy of
%this data and if one session modifies this data it does not change the
%value of the data in other sessions. There is also global data, \ie
%data that is shared among sessions and modification of this data in
%one session is visible in other sessions. Both types of data are
%realizable in a \surflet.
%
%Local data is managed automatically by the \surflet library. When you
%call \name{send-html/suspend}, your local data is saved in a
%continuation. After the user has proceeded in your web site, this
%continuation and thus your local data is restored.
%
%As all data is stored in a continuation, there are no copies of your
%data saved. This means that if you change data via side effects, \eg
%via \name{set!}, this changes remain effective after the continuation
%has been restored. Thus, using side effects to change data, for
%example with \name{set!} is a way to realize global data.
%
%Sometimes it is desirable to be able to change local data via side
%effects.
%
%\end{document}
%session data
%surflets containing of different parts
%my sxml