From 721fee4e0749540b43a79134cdadd176701d09a2 Mon Sep 17 00:00:00 2001 From: interp Date: Thu, 15 Jan 2004 03:46:27 +0000 Subject: [PATCH] More howto. Draft like version. --- scheme/httpd/surflets/decls.tex | 23 +- scheme/httpd/surflets/howto.tex | 594 ++++++++++++++++++++++++++++++-- 2 files changed, 587 insertions(+), 30 deletions(-) diff --git a/scheme/httpd/surflets/decls.tex b/scheme/httpd/surflets/decls.tex index 565c891..d7335d4 100644 --- a/scheme/httpd/surflets/decls.tex +++ b/scheme/httpd/surflets/decls.tex @@ -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 diff --git a/scheme/httpd/surflets/howto.tex b/scheme/httpd/surflets/howto.tex index c4f63cc..931b3dc 100644 --- a/scheme/httpd/surflets/howto.tex +++ b/scheme/httpd/surflets/howto.tex @@ -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