Initial version of How To Use SUrflets.

Needs still some additions.
This commit is contained in:
interp 2003-11-21 19:09:36 +00:00
parent 52e0a557e2
commit 8baad2666f
1 changed files with 652 additions and 0 deletions

View File

@ -0,0 +1,652 @@
\documentclass{article}
\usepackage[latin1]{inputenc}
\usepackage{fontenc}
\usepackage{alltt}
\usepackage{url,xspace}
\usepackage{tabularx}
\usepackage{theorem,ulem,float,afterpage}
\normalem %usually, don't use ulem
\input{decls}
\title{Howto write \surflets}
\author{Andreas Bernauer}
\begin{document}
\maketitle
\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
\surflet API in detail to give you instant succes in running your own
surflets. The \surflet API will be described in the SUnet
documentation eventually.
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
of the SUnet webserver. \surflets ease the implementation of web
applications in two ways, compared to other server-side scripting
tools like Java\texttrademark Servlets or Microsoft\textregistered
Active Server Pages or PHP:
\begin{enumerate}
\item \surflets have an automatic program flow control like any
other usual program, \ie the web designer doesn't have to care about
session management at all. The sequence of the web pages result from
their appearance in the program like the print statements in any other
usual program.
\item \surflets come along with a library for robust user
interaction. \surflets represent interaction elements of the web page
like text input fields or dropdown lists in the \surflet program by
specific objects. A web designer can plug in these objects into a
website and use them to read out the user input.
\end{enumerate}
The following sections probably assume that you have basic knowledge
of the SUnet webserver and scsh. The environment variable
\code{\$sunet} refers to the top level directory of your sunet
installation. On my system this is \name{/home/andreas/sw/sunet}.
\section{How to run the SUnet webserver that handles \surflets}
The following sections will show pieces of \surflet code you might
want to try out. Therefore you need the SUnet webserver running with
the ability to serve \surflets. This section tells you how to do it.
\begin{enumerate}
\item You need Oleg's SSAX package (for scsh), to be able to use
\surflets:
\begin{itemize}
\item Download Oleg's SSAX package from
\url{http://prdownloads.sourceforge.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
\code{\$SSAX}.
\item Unfortunately, this distribution (plt200-4.9) has a typo in
the package definition for scsh. Apply the patch that comes with
the \surflets distribution to get rid of this typo:
\begin{itemize}
\item \code{cd \$SSAX}
\item \code{patch -p1 < \$sunet/httpd/surflets/SSAX-goodhtml-patch}
\end{itemize}
\end{itemize}
\item You can start the SUnet webserver along with the
\surflet-handler now. The \surflets distribution comes with a script that
does this for you:
\begin{itemize}
\item \code{cd \$sunet/httpd/surflets}
\item \code{SSAX=\$SSAX ./start-surflet-server}
\end{itemize}
Please be patient, scsh has to load a lot of libraries. If the
loading succeeds you will see something like this:
\begin{alltt}
[andreas@hgt surflets]\$ ssax=/home/andreas/sw/SSAX ./start-surflet-server
Loading...
reading options: ()
Going to run SUrflet server with:
htdocs-dir: /home/andreas/sw/sunet/httpd/surflets/web-server/root/htdocs
surflet-dir: /home/andreas/sw/sunet/httpd/surflets/web-server/root/surflets
images-dir: /home/andreas/sw/sunet/httpd/surflets/web-server/root/img
port: 8008
log-file-name: /home/andreas/sw/sunet/httpd/surflets/web-server/httpd.log
a maximum of 5 simultaneous requests, syslogging activated,
and home-dir-handler (public_html) activated.
NOTE: This is the SUrflet server. It does not support cgi.
\end{alltt}
This means the server is up and running. Try to connect to
\url{http://localhost:8008} with your browser and you will see the
welcome page of the \surflets. You can also already try out some of
\surflets that come with the distribution.
You will probably notice a long response time the first time you load
the first \surflet. This is because the server has to load the
\surflet libraries. The server handles further requests to \surflets
faster.
If the port the \surflet server tries to use is occupied use, you will
see an error message similar to this one:
\begin{alltt}
Error: 98
"Address already in use"
#{Procedure 11701 (\%bind in scsh-level-0)}
4
2
(0 . 8008)
\end{alltt}
In this case, pass another port number to the script, \eg 8000:
\codex{ssax=\$SSAX ./start-surflet-server -p 8000}
The \code{--help} option will show you more parameters that you can
adjust, but you won't need them for this howto.
\end{enumerate}
\section{How to send web pages}
This section will discuss some of the various ways in which you can
send a web page to a browser that contacted your \surflet.
\subsection{My first \surflet}
\label{sec:first-surflet}
Traditionally, your first program in any programming language prints
something like ``Hello, World!''. We follow this tradition:
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
scheme-with-scsh)
(begin
(define (main req)
(send-html/finish
'(html (body (h1 "Hello, world!")))))
))
\end{listing}
You can either save a file with that content in the \surflets
directory the server mentioned at startup or you can use the file
\name{howto/hello.scm} that comes along with the \surflets
distribution and which is located in the server's standard \surflets
directory. Let's go through the small script step by step:
\begin{alltt}
(define-structure surflet surflet-interface
\end{alltt}
This defines a module named \name{surflet} which implements the
interface \name{surflet-interface}. \name{surflet-interface} just
states that the module exports a function named \name{main} to which
we will come shortly. For those of you who know about the scsh module
system: Yes, \surflets are basically scsh modules that are loaded
dynamically during run time.
\begin{alltt}
(open surflets
scheme-with-scsh)
\end{alltt}
The \name{open} form lists all the modules the \surflet needs. You
will probably always need the two modules that are stated here
(namely \name{surflets} and \name{scheme-with-scsh}). If you need
other modules, like \name{srfi-13} for string manipulation, this is
the place where you want to state it.
\begin{alltt}
(begin
\end{alltt}
This just opens the body of the \surflet. All your \surflet code goes
here.\footnote{If you know about scsh modules, you probably also know
that there is a \name{file} clause that you could use to place the
code in a file instead or along with the \name{begin} clause.}
\begin{alltt}
(define (main req)
\end{alltt}
Here is the \name{main} function that the interface declared this
\surflet will implement. The \name{main} function is the entry point
to your \surflet: The server calls this function every time a user
browses to your \surflet the first time. The server calls \name{main}
with one argument: a representation of the inital request of the
browser. We don't have to worry about that at this point.
\begin{alltt}
(send-html/finish
'(html (body (h1 "Hello, world!")))))
))
\end{alltt}
\name{send-html/finish} is one of three function you will regularly
use to send web pages to the browser. The other two functions are
\name{send-html} and \name{send-html/suspend}.
\name{send-html/finish} -- as the name already suggests -- sends a
HTML page to the browser and finishes the \surflet. \name{send-html}
just sends the HTML page and does not return and
\name{send-html/suspend} sends the HTML page and suspends the
\surflet, \ie it waits until the user continues with the \surflet,
\eg by submitting a webform. We will discuss \name{send-html} and
\name{send-html/suspend} in detail later. I will refer to these three
functions as the \emph{sending functions}.
In a \surflet, HTML pages are represented as lists, or, to be more
precise, as SXML (S-expression based XML). The first element of a
SXML list is a symbol stating the HTML tag. The other elements of a
SXML list are the contents that are enclosed by this HTML tag. The
contents can be other SXML list, too. Here are some examples of SXML
lists and how they translate to HTML:
\newcommand{\tag}[1]{$\mathtt{<}$#1$\mathtt{>}$}
\begin{tabbing}
HTML: \medskip\=\kill
SXML: \> \texttt{'(p "A paragraph.")} \\
HTML: \> \texttt{\tag{p}A paragraph.\tag{/p}}\\
\\
SXML: \> \texttt{'(p "A paragraph." (br) "With break line.")} \\
HTML: \> \texttt{\tag{p}A paragraph.\tag{br}With break line.\tag{/p}}\\
\\
SXML: \> \texttt{'(p "Nested" (p "paragraphs"))}\\
HTML: \> \texttt{\tag{p}Nested\tag{p}paragraphs\tag{/p}\tag{/p}}\\
\end{tabbing}
Attributes are stated by a special list whose first element is the
at-symbol. The attribute list must be the second element in the list:
\begin{tabbing}
HTML: \medskip\=\kill
SXML: \> \texttt{'(a (@ (href "attr.html")) "Attributed HTML tags.")} \\
HTML: \> \texttt{\tag{a href="attr.html"}Attributed HTML tags.\tag{/a}}\\
\\
SXML: \> \texttt{'(a (@ (href "attr2.html") (target "\_blank")) "2
attributes.")} \\
HTML: \> \texttt{\tag{a href="attr2.html" target="\_blank"}2
attributes.\tag{/a}}
\end{tabbing}
As you see from the \surflet example, \name{send-html/finish} expects
as an argument SXML. In the example, the SXML translates to the
following HTML code:
\begin{alltt}
<html><body><h1>Hello, world!</h1>
</body>
</html>
\end{alltt}
Please note, that there is no check for valid HTML or even XHTML
here. The only thing the translation process takes care of are
special characters in strings like the ampersand (\code{\&}). The
translation process replaces them by their HTML representation (\eg,
\code{\&amp;}) so you don't have to worry about that when you use
strings. Everything else like using valid HTML tags or valid
attributes is your responsibility.
\subsection{Dynamic content}
Let's extend our first \surflet example by some dynamic content, \eg
by displaying the current time using scsh's \name{format-date}
function. As the HTML page is basically represented as a list, this
can be done like this:
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
scheme-with-scsh)
(begin
(define (main req)
(send-html/finish
`(html (body (h1 "Hello, world!")
(p "The current date and time is "
,(format-date "~H:~M:~S ~p ~m/~d/~Y"
(date)))))))
))
\end{listing}
This \surflet can be found in \name{howto/hello-date.scm}. The
beginning of this \surflet is the same as in the previous example.
The difference lies in the argument to \name{send-html/finish}. Note
that the argument starts with a backquote (\code{`}) rather than with
a regular quote (\code{'}) as in the previous example.
Instead of passing a ``static'' list, \ie a list whose contents are
given before execution, this \surflet uses the quasiquote and unquote
feature of Scheme to create a ``dynamic'' list, \ie list whose
contents are given only during execution. A ``dynamic'' list is
introduced by a backquote (\code{`}) and its dynamic contents are
noted by commata (\code{,}). Thus, if the \surflet is executed while
I am writing this howto, the argument to \name{send-html/finish} above
is translated to
\begin{alltt}
'(html (body (h1 "Hello, world!")
(p "The current date and time is "
"13:09:03 PM 11/18/2003")))))
\end{alltt}
\noindent\emph{before} it is passed to \name{send-html/finish}. Thus, using
dynamic content can be easily done with Scheme's quasiquote and
unquote feature. Of course, you can build your list in any way you
want; the quasiquote notation is just a convenient way to do it.
\subsection{Several web pages in a row}
The previous example \surflets only showed one page and finished
afterwards. Here, we want to present to web pages in a row. We use
the previously mentioned function \name{send-html/suspend}, which
suspends after it has send the page and continues when the user
clicked for the next page. In contrast to \name{send-html/finish},
that expected SXML, \name{send-html/suspend} expects a function that
takes an argument and returns SXML. The parameter the function gets
(here: \name{k-url} is the URL that points to the next
page:\footnote{In the API this URL is called the \emph{continuation
URL}.}
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
scheme-with-scsh)
(begin
(define (main req)
(send-html/suspend
(lambda (k-url)
`(html (body (h1 "Hello, world!")
(p (a (@ (href ,k-url)) "Next page -->"))))))
(send-html/finish
'(html (body (h1 "Hello, again!")))))
))
\end{listing}
This \surflet can be found in \name{howto/hello-twice.scm}. This
example first displays a web page with the message ``Hello, world!''
and a link to the next page labeled with ``Next page --$>$''. When the
user clicks on the provided link, \name{send-html/suspend} returns and
the the next statement after the call to \name{send-html/suspend} is
executed. Here it is \name{send-html/finish} which shows a web page
with the message ``Hello, again!''.
When \name{send-html/suspend} returns, (almost) the complete context
of the running \surflet is restored. Thus, every variable in the
\surflet will retain its value during suspension. The consequence is
that you don't have to worry about sessions, sesssion variables and
alike. The user can freely use the back button of her browser or
clone a window while the \surflet will keep on responding in the
expected way. This is all automatically managed by the
\surflet-handler.
The only exception are variables whose values are changed by side
effects, \eg if you change a variable via \name{set!}. These
variables keep their modified values, allowing communication between
sessions of the same \surflet.\footnote{If you want to change a
variable via side effects but you don't want to interfere with other
session, you can use \name{set-session-data!} and
\name{get-session-data}. See the API documentation for further
information.}
\subsection{Begin and end of sessions}
So far I don't have mentioned too much details about sessions. The
reason is, as mentioned before, that the \surflet handler takes of the
session automatically as described in the previous paragraph.
%, \ie it starts the session automatically when an
%instance of your \surflet starts and takes care of the saving and
%restoring of all variable values during suspensions of your \surflet
%instance, except for \code{set!}ed values.
The only thing you have to worry about is when your session
\emph{ends}. As long as your session hasn't been finished by
\name{send-html/finish}, the user can move freely between the web
pages your \surflet provides. Once you've finished the session via
\name{send-html/finish}, this freedom ends. As the session is over,
the user will get an error message when he tries to recall some web
page from the server. The server will tell the user about the
possible reasons for the error (namely that most likely the session
was finished) and provides a link to the beginning of a new session.
Thus, \name{send-html/suspend} suspends the current execution of a
\surflet, returning with the request for the next web page of your
\surflet and \name{send/finish} finishes the session. The third
sending function is \name{send-html} which just sends a web page.
\name{send-html} does not return and does not touch the session of
your \surflet instance.
\subsection{Abbreviations in SXML}
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
websites contain a lot of links, the sending functions (like
\name{send-html/finish}) allow an abbreviation. The following SXML
snippets are equivalent:
\begin{alltt}
(a (@ (href ,k-url)) "Next page -->")
(url ,k-url "Next page -->")
\end{alltt}
\name{url} expects the target address as the next element and includes
every text afterwards as part of the link.
There are also some other abbreviations. \code{(nbsp)} inserts
`\code{\&nbsp;}' into the HTML, \code{(*COMMENT* \dots)} inserts a
comment, and with \code{(plain-html \dots)} you can insert arbitrary
HTML code (\ie strings) directly , without any string conversions.
The last abbreviation, \name{surflet-form}, is discussed in the next
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.
\subsection{Simple web forms}
Let's write a \surflet that reads user input and prints it out on the
next page:
\begin{listing}
(define-structure surflet surflet-interface
(open surflets
scheme-with-scsh)
(begin
(define (main req)
(let* ((text-input (make-text-field))
(submit-button (make-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)))))))
(bindings (get-bindings req))
(user-input (input-field-value text-input bindings)))
(send-html/finish
`(html (body
(h1 "Echo result")
(p "You've entered: '" ,user-input "'."))))))
))
\end{listing}
Here are the details to the code in \name{main}:
\begin{alltt}
(define (main req)
(let* ((text-input (make-text-field))
(submit-button (make-submit-button))
\end{alltt}
\name{make-text-field} and \name{make-submit-button} define two user
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.
\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)))))))
\end{alltt}
Instead of discarding the return value of \name{send-html/suspend} as
in the examples of the previous section, this time we'll save the
return value, as it will contain the data the user has entered in our
text input field.
The definition of the website is as described in the previous section
except for the new abbreviation \name{surflet-form}.
\name{surflet-form} creates the HTML code for a web form and expects
as its next value the URL to the next webpage as provided by
\name{send-html/suspend}, here named
\name{k-url}. The remaining arguments constitute the content of the
web form. Thus, the code above is equal to the following SXML:
\begin{alltt}
(form (@ (action ,k-url) (method "GET"))
(p "Please enter something:"
,text-input
,submit-button))
\end{alltt}
If you want to use the POST method instead of the default GET method,
add the symbol \name{'POST} after the URL:
\begin{alltt}
(surflet-form ,k-url
POST
(p "Please enter something:"
,text-input
,submit-button))
\end{alltt}
The web page \name{send-html/suspend} sends to the browser looks like
in figure \ref{fig:user1-1}. After the user has entered his data into
the web form, \name{send-html/suspend} returns with the request object
of the browser for the next page. This request object contains the
data the user has entered.
\begin{alltt}
(bindings (get-bindings req))
\end{alltt}
With the function \name{get-bindings} we pull out the user data of the
request object. Here we save the user data into the variable
\name{bindings}. \name{get-bindings} works for both request methods
\code{GET} and \code{POST}.
\begin{alltt}
(user-input (input-field-value text-input bindings)))
\end{alltt}
With the function \name{input-field-value} and the extracted user data
we can read the value for an \name{input-field}. Here, we want to
know what the user has entered into the \name{text-input-field}.
\begin{alltt}
(send-html/finish
`(html (body
(h1 "Echo result")
(p "You've entered: '" ,user-input "'."))))))
\end{alltt}
After we have extracted what the user has entered into the text field,
we can show the final page of our \surflet and echo her input.
The scheme for user interaction is thus about the following:
\begin{itemize}
\item Create the user interaction elements, \name{input-field}s, you
want to use in your web page.
\item Send the web page with \name{send-html/suspend} to the browser.
Plug in the \name{input-field}s in the web page as if they were usual
values. Save the return value of \name{send-html/suspend}.
\item Extract the user data from the return value of
\name{send-html/suspend}.
\item Read the values of each \name{input-field} out of the extracted
user data with \name{input-field-value}.
\end{itemize}
The complete list of functions that create \name{input-fields} can be
found in the API.
\subsection{Return types other than strings}
As the user interaction elements are first class values in a \surflet,
they can return other types than strings. For example the \surflets
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}
instead.
\subsubsection{Annotated input fields}
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:
\begin{alltt}
(define-structure surflet surflet-interface
(open surflets
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))
(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}
\begin{alltt}
(let* ((select-input-field
(make-select
(map make-annotated-select-option
'("Icecream" "Chocolate" "Candy")
'(1.5 2.0 0.5))))
\end{alltt}
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
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.
\end{document}