\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}

Hello, world!

\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{\&}) 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{\ }' 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}