From 8baad2666fdc8d385f11339ea3687f24afe49e27 Mon Sep 17 00:00:00 2001 From: interp Date: Fri, 21 Nov 2003 19:09:36 +0000 Subject: [PATCH] Initial version of How To Use SUrflets. Needs still some additions. --- scheme/httpd/surflets/howto.tex | 652 ++++++++++++++++++++++++++++++++ 1 file changed, 652 insertions(+) create mode 100644 scheme/httpd/surflets/howto.tex diff --git a/scheme/httpd/surflets/howto.tex b/scheme/httpd/surflets/howto.tex new file mode 100644 index 0000000..c4f63cc --- /dev/null +++ b/scheme/httpd/surflets/howto.tex @@ -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} +

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}