7 Web Programming

7.6 Further Examples for Web Server Programming

Now we have seen all elements for writing CGI programs in Curry. In this section we will show by various examples how to use this programming interface. We will see that this programming model (i.e., logic variables for CGI references, associated event handlers depending on CGI environments) is sufficient to solve typical problems in web server programming in an appropriate way, like handling sequences of interactions or holding intermediate states between interactions.

7.6.1 Interaction Sequences

In the previous example, the interaction between the client and the web server is quite simple: the client sends a request by filling a form which is answered by the server with an HTML document containing the requested information. In realistic applications, it is often the case that the interaction is not finished by sending back the requested information but the client requests further (e.g., more detailed) information based on the received results. Thus, one has to deal with sequences of longer interactions between the client and the server.

Our programming model provides a direct support for interaction sequences. Since the answer provided by the event handler is an HTML form rather than an HTML expression, this answer can also contain further input elements and associated event handlers. By nesting event handlers, it is straightforward to implement bounded sequences of interactions. An example for this technique is shown in the next section.

A more interesting question is how to implement other control abstractions like arbitrary loops. For this purpose, we show the implementation of a simple number guessing game: the client has to guess a number known by the server (here: 42), and for each number entered by the client the server responds whether this number is right, smaller or larger than the number to be guessed. If the guess is not right, the answer form contains an input field where the client can enter the next guess. Moreover, the number of guesses should also be counted and shown at the end.

As the typical approach in declarative languages, we implement looping constructs by recursion. Thus, the event handler computing the answer for the client contains a recursive call to the initial form which implements the interaction loop. The entire implementation of this number guessing game is as follows [Browse Program][Download Program]:

guessForm :: IO HtmlForm
guessForm = return \$ form "Number Guessing" (guessInput 1)
guessInput :: Int -> [HtmlExp]
guessInput n =
  [htxt "Guess a natural number: ", textfield nref "",
   button "Check" (guessHandler n nref)]   where nref free
guessHandler :: Int -> CgiRef -> (CgiRef -> String) -> IO HtmlForm
guessHandler n nref env =
  return $ form "Answer" $
    case reads (env nref) of
      [(nr,"")] ->
            if nr==42
              then [h1 [htxt $ "Right! You needed "++show n++" guesses!"]]
              else [h1 [htxt $ if nr<42 then "Too small!"
                                        else "Too large!"],
                    hrule] ++ guessInput (n+1)
      _ -> [h1 [htxt "Illegal input, try again!"]] ++ guessInput n

guessInput n” is an HTML expression corresponding to the initial form which contains an input field for entering the client’s guess. “guessHandler” is the associated event handler where the number of guesses and the CGI reference to the input field are the first and the second argument of the handler, respectively. It checks the number entered by the client (reads is defined in the standard prelude and used to parse a string into a value) and returns the different answers depending on the client’s guess. If the guess is not right, the guessInput is appended to the answer which implements the recursive call.

7.6.2 Handling Intermediate States

A nasty problem in many CGI applications is the handling of intermediate states due to the fact that HTTP is a stateless protocol. For instance, in electronic commerce applications, the clients have shopping baskets where the already selected items are stored, and the contents of these baskets must be kept between the interactions. Storing this information on the server side has several drawbacks. For instance, the client wants to identify himself only after he really orders the items, i.e., during the selection phase the server cannot uniquely associate the selections to a client. Furthermore, the client might not proceed with his selections so that the server does not know whether the basket information can be deleted (which is necessary at some point to avoid a memory overflow). Therefore, it is often better to store such client-dependent information on the client side. For this purpose, one can have HTML forms with input elements of type “hidden” which have no visual representation but can be used to pass client-dependent information between interactions. “Raw” HTML/CGI programmers must explicitly handle these fields which is awkward and a source of many programming problems.

Our programming model offers a much simpler solution to this problem. By nesting event handlers (which is allowed in languages with lexical scoping like Curry), one can directly refer to input elements in previous forms. As a concrete example, we consider a sequence of HTML forms where the client enters his first name in the first form and his last name in the second form. The complete name is returned in the third form. This example can be implemented as follows [Browse Program][Download Program]:

nameForm = return $ form "First Name Form"
  [htxt "Enter your first name: ", textfield firstref "",
   button "Continue" fhandler]
 where
   firstref free
   fhandler _ = return $ form "Last Name Form"
                  [htxt "Enter your last name: ", textfield lastref "",
                   button "Continue" lhandler]
     where
       lastref free
       lhandler env = return $ form "Answer"
                        [htxt $ "Hi, " ++ env firstref ++ " " ++ env lastref]

Due to lexical scoping, the variable “firstref” is visible in the event handler “lhandler” without explicitly passing it as an argument.

7.6.3 Storing Information on the Server

We have seen how we can retrieve information from the server by CGI programs. This is possible by performing I/O actions on the server before computing the HTML form as the response to the client. In many applications, clients also want to store or update information on the server, e.g., by putting orders for books, flight tickets, etc. In this section we will see a small example that demonstrates how this can be done using the already known techniques.

Consider the implementation of a web form that counts and shows the number of visitors. Thus, each visitor updates the current visitor counter on the server. This can be easily implemented by storing the current visitor number in a file. For this purpose, we define an I/O action “incVisitNumber” that reads the number stored in this file, increments it, stores the incremented number in the file, and returns the incremented number (doesFileExist, removeFile, and renameFile are I/O operations defined in the library Directory to check the existence of a file, delete a file, and rename a file, respectively):

incVisitNumber :: IO Int
incVisitNumber = do
 existnumfile <- doesFileExist visitFile
 if existnumfile
   then do vfcont <- readFile visitFile
           overwriteVisitFile (read vfcont + 1)
   else do writeFile visitFile "1"
           return 1
overwriteVisitFile :: Int -> IO Int
overwriteVisitFile n = do
  writeFile (visitFile++".new") (show n)
  removeFile visitFile
  renameFile (visitFile ++ ".new") visitFile
  return n
visitFile :: String
visitFile = "numvisit"  -- file to store the current visitor number

Note the definition of overwriteVisitFile: it does not directly write the incremented number into the visitFile but it writes it into another file that is subsequently renamed to the visitFile. This is necessary to avoid the overlapping of reading and writing actions on the same file due to the lazy evaluation of readFile.

Now the visitor form is simply obtained by calling incVisitNumber before generating the form [Browse Program][Download Program]:

visitorForm = do
  visitnum <- incVisitNumber
  return $ form "Access Count Form"
           [h1 [htxt $ "You are the " ++ show visitnum ++ ". visitor!"]]

7.6.4 Ensuring Exclusive Access

Since CGI programs are executed whenever a client accesses them, one has not much control on the order of their execution. In particular, the same CGI program can be executed in parallel if two clients accessing them simultaneously. This can cause a problem if both update the same information. For instance, an access to the visitor form above reads the current visitor number from the global visitFile and write the incremented number back. If the script is simultaneously executed by two clients, it may be the case that one update is lost (if both read the same number and write the same incremented number).

Multiple simultaneous accesses or updates can be avoided by ensuring the exclusive access to a resource on the web server between different processes running on the server. Although Curry has no direct features to support this,99It could be implemented in Curry by the use of ports but this will be discussed later. it can be implemented by the use of the underlying operating system. For instance, Unix/Linux systems offer the command “lockfile” to ensure an exclusive access to a resource of the system. lockfile tries to create a given file (the argument to lockfile). If the file cannot be created (since it has been already created by another process), the lockfile command waits and retries after some time. Using lockfile, we can implement a generic function “exclusiveIO” that takes a name for a global lock file and exclusively executes an I/O action (the second parameter), i.e., it ensures that two processes using the same lock file do not execute the action at the same time:

exclusiveIO :: String -> IO a -> IO a
exclusiveIO lockfile action = do
  system ("lockfile " ++ lockfile)
  actionResult <- action
  system ("rm -f " ++ lockfile)
  return actionResult

Now it is straightforward to extend our visitor form in order to ensure the exclusive update of the visitor counter. This is done by replacing the expression incVisitNumber in the definition of visitorForm by the following expression [Browse Program][Download Program]:

exclusiveIO (visitFile ++ ".lock") incVisitNumber

7.6.5 Example: A Web Questionnaire

Figure 7.2: A web questionnaire

This section shows an example for web programming where the formerly discussed techniques are applied. Consider the implementation of a web-based questionnaire which allows the clients to vote on a particular topic. Figure 7.2 shows an example of such a questionnaire. The votes are stored on the web server. The current votings are shown after a client submits a vote (see Figure 7.3).

Figure 7.3: Answer to the web questionnaire

In order to provide an implementation that is easy to maintain, we define the main question and the choices for the answers as constants in our program so that they can be easily adapted to other questionnaires:

question = "Who is your favorite actress?"
choices = ["Doris Day", "Jodie Foster", "Marilyn Monroe",
           "Julia Roberts", "Sharon Stone", "Meryl Streep"]

The current votes are stored in a file on the web server. We define the name of this file as a constant in our program:

voteFile = "votes.data"

For the sake of simplicity, this file is a simple text file. If there are n choices for voting, the file has n lines where each line contains the textual representation of the number of votes for the corresponding choice. Thus, the following function defines an action that reads the vote file and returns the list of numbers in this file (the prelude function lines breaks a string into a list of lines where lines are separated by newline characters):

readVoteFile :: IO [Int]
readVoteFile = do
  vfcont <- readFile voteFile
  return (map read (lines vfcont))

To initialize the vote file, we define an action initVoteFile that initializes the vote file with n zeros if it does not exist. The prelude function unlines is the opposite of lines and concatenates a list of strings into one string by inserting newline characters.

initVoteFile :: Int -> IO ()
initVoteFile n = do
  existnumfile <- doesFileExist voteFile
  unless existnumfile $
    writeFile voteFile (unlines (map show (take n (repeat 0))))

To update the vote file, we define overwriteVoteFile that writes a list of numbers into the vote file. Similarly to the definition of overwriteVisitFile in Section 7.6.3, the numbers are written into a new file that is moved to the vote file in order to avoid an overlapping between reading and writing the same file.

overwriteVoteFile :: [Int] -> IO ()
overwriteVoteFile nums = do
  writeFile (voteFile ++ ".new") (unlines (map show nums))
  removeFile voteFile
  renameFile (voteFile ++ ".new") voteFile

When a client submits a vote, we have to increment to corresponding number in the vote file. This can be easily done by a sequence of actions that initialize the vote file (if necessary), read the current votes and write the votes that are incremented by the function incNth:

incNumberInFile :: Int -> IO ()
incNumberInFile n = do
  initVoteFile (length choices)
  nums <- readVoteFile
  overwriteVoteFile (incNth nums n)
incNth :: [Int] -> Int -> [Int]
incNth []     _ = []
incNth (x:xs) n | n==0      = (x+1) : xs
                | otherwise = x : incNth xs (n-1)

Now we have all auxiliary definitions that are necessary to define the web script. First, we show the definition of the HTML form “evalForm” that shows the current votes (which produces the result shown in Figure 7.3). Note that we ensure the exclusive access to the vote file by the use of the function “exclusiveIO” defined in Section 7.6.4 (the prelude function “zip” joins two lists into one list of pairs of corresponding elements):

evalForm :: IO HtmlForm
evalForm = do
  votes <- exclusiveIO (voteFile ++ ".lock") readVoteFile
  return $ form "Evaluation"
   [h1 [htxt "Current votes:"],
    table (map (\(s,v) -> [[htxt s], [htxt $ show v]])
               (zip choices votes))]

Now we can define our main form that allows the user to submit a vote (see Figure 7.2). It uses radio buttons as input elements. Radio buttons are lists of buttons where exactly one button can be turned on. Thus, all buttons have the same CGI reference but different values. When a form is submitted, the CGI environment maps the CGI reference to the value of the selected radio button. A complete radio button suite consists always of a main button (radio_main) which is initially on and some further buttons with the same CGI reference as the main button (radio_others) that are initially off. In our example, we associate to each button the index of the corresponding choice as a value. The event handler questHandler increments he appropriate vote number and returns the current votes with the form evalForm [Browse Program][Download Program]:

questForm = return $ form "Vote Form" $
  [h1 [htxt question],
   radio_main vref "0", htxt (' ':head choices), breakline] ++
  concatMap (\(i,s) -> [radio_other vref (show i), htxt (' ':s), breakline])
            (zip [1..] (tail choices)) ++
  [hrule, button "submit" questHandler]
 where
   vref free
   questHandler env = do
     exclusiveIO (voteFile ++ ".lock") (incNumberInFile (read (env vref)))
     evalForm