7 Web Programming

7.8 Advanced Web Programming

This section discusses some further features which are useful for writing web applications in Curry. Cookies are useful to store information about the client between different web scripts. URL parameters can be exploited to write generic web scripts. Style sheets can be used to modify and add new presentation styles for web documents.

7.8.1 Cookies

Cookies are small pieces of information (represented by strings) that are stored on the client’s machine when a client communicates to a web server via his browser. The web server can sent cookies to the client together with a requested web document. If the client wants to retrieve the same or another document from the web server, the client’s browser sends the stored cookies together with the request for a document to the browser. Thus, cookies can be used to identify the client during a longer interaction with the web server (also across various web scripts stored on the same web browser). Cookies are another approach to handle intermediate state in web applications. The technique presented in Section 7.6.2 is only useful inside the same web script whereas cookies can be used as a link between different web scripts. However, cookies need special support on the browser’s side and the client must enable cookies in his web browser. Fortunately, most web browsers support cookies since they are used in many web sites.

Basically, a cookie has a name and a value. Both parameters are of type string. Cookies can also have additional parameters to control their lifetime, validity for different web servers or regions on a web server etc (see definition of datatype “CookieParam” in the library HTML.Base) which we will not describe here. As the default, a cookie is a valid during the client’s browser session for all documents in the same directory or a subdirectory in which the cookie was set.

The library HTML.Base provides two functions to set and retrieve cookies. As described above, a cookie is set by sending it with some web document. For doing so, there is the function

cookieForm :: String -> [(String,String)] -> [HtmlExp] -> HtmlForm

which behaves similarly to the function “form” but takes an additional parameter: a list of cookies, i.e., name/value pairs. These cookies are submitted with the form to the client’s browser. To retrieve cookies (that are previously sent with a “cookieForm”), there is an I/O action

getCookies :: IO [(String,String)]

that returns the list of all cookies (i.e., name/value pairs) sent from the browser for the current CGI script.

As a simple example, we want to use cookies to write a web application where a user must identify himself and this identification is used in another independent script. The identification is done by setting a cookie of the form ("LOGINNAME",<name>) where <name> is the user’s name. We implement a “login form” that sets this cookie as follows [Browse Program][Download Program]:

loginForm = return $ form "Login"
  [htxt "Enter your name: ", textfield tref "",
   hrule,
   button "Login" handler
  ]
 where
   tref free
   handler env = return $
     cookieForm "Logged In"
                [("LOGINNAME",env tref)]
                [h2 [htxt \$ env tref ++ ": thank you for visiting us"]]

The first form asks the user for his name. The cookie is set together with the acknowledgment form (function “handler”).

Now we can write another web script that uses this cookie. This script shows the user’s name or the string "Not yet logged in" if the user has not used the login form to set the cookie. Using the function getCookies, the implementation is quite simple (the function lookup, defined in the prelude, searches for a name in a name/value list; it returns “Nothing” of the name was not found and “Just v” if the first occurrence of the name in the list has the associated value v; the prelude function maybe processes these two cases) [Browse Program][Download Program]:

getNameForm = do
  cookies <- getCookies
  return $ form "Hello" $
   maybe [h1 [htxt "Not yet logged in"]]
         (\n -> [h1 [htxt $ "Hello, " ++ n]])
         (lookup "LOGINNAME" cookies)

As mentioned above, cookies need special support on the client’s side, i.e., the web browser of the client must support cookies. If cookies are essential for an application, one should check whether the client allows the setting of cookies. This can be done by trying to set a cookie and by checking whether this was successful. For instance, one can modify the above login script as folllows. The first form immediately sets a cookie with name “SETCOOKIE”. Then the handler checks whether this cookie has been sent by the client’s browser. If this cookie is not received, it returns a form with the message “Sorry, can’t set cookies.” instead of the acknowledgment form which sets the cookie “LOGINNAME[Browse Program][Download Program]:

loginForm = return $ cookieForm "Login" [("SETCOOKIE","")]
  [htxt "Enter your name: ", textfield tref "",
   hrule,
   button "Login" handler
  ]
 where
   tref free
   handler env = do
     cookies <- getCookies
     return $
       if lookup "SETCOOKIE" cookies == Nothing
         then form "No cookies" [h2 [htxt "Sorry, can't set cookies."]]
         else cookieForm "Logged In"
                         [("LOGINNAME", env tref)]
                         [h2 [htxt $ env tref ++ ": thank you for visiting us"]]

7.8.2 URL Parameters

In some situations it is preferable to have generic web scripts that can be applied in various situations described by parameters. For instance, if we want to write a web application that allows the navigation through a hierarchical structure, one does not want to write a different script for each different level of the structure but it is preferable to write a single script that can be applied to different points in the structure. This is possible by attaching a parameter (a string) to the URL of a script. For instance, a URL can have the form “http://myhost/script.cgi?parameter” where “http://myhost/script.cgi” is the URL of the web script and “parameter” is an optional parameter that is passed to the script. A URL parameter can be retrieved inside a script by the I/O action

getUrlParameter :: IO String

which returns the part of the URL following the character “?”. Note that an URL parameter should be “URL encoded” to avoid the appearance of characters with a special meaning. The library HTML.Base provides the functions urlencoded2string and string2urlencoded to decode and encode such parameters, respectively.

As a simple example, we want to write a web script to navigate through a directory structure. The current directory is the URL parameter for this script. The script extracts this parameter by the use of getUrlParameter and shows all entries as a HTML list [Browse Program][Download Program] (the prelude function mapM applies a mapping from elements into actions to all elements of a list and collect all results in a list):

showDirForm = do
  param <- getUrlParameter
  let dir = if param=="" then "." else urlencoded2string param
  entries <- getDirectoryContents dir
  hexps <- mapM (entry2html dir) entries
  return $ form "Browse Directory"
                [h1 [htxt $ "Directory: " ++ dir], ulist hexps]

The I/O action getDirectoryContents is defined in the system library Directory and returns the list of all entries in a directory. The function entry2html checks for an entry whether it is a directory. If this is the case, it returns a link to the same web script but with an extended parameter, otherwise it simply returns the entry name as an HTML text (doesDirectoryExist is defined in the library Directory and returns True if the argument is the name of a directory):

entry2html :: String -> String -> IO [HtmlExp]
entry2html dir e = do
  direx <- doesDirectoryExist (dir ++ "/" ++ e)
  if direx
   then return [href ("browsedir.cgi?" ++ string2urlencoded (dir ++ "/" ++ e))
                     [htxt e]]
   else return [htxt e]

7.8.3 Style Sheets

[To be completed.]