7 Web Programming

7.9 Advanced Web Programming

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

7.9.1 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 a 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):

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

The I/O action getDirectoryContents is defined in the library System.Directory (in package 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 System.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 ("?" ++ string2urlencoded (dir ++ "/" ++ e))
                     [htxt e]]
   else return [htxt e]

7.9.2 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). Hence, cookies are used to implement session data as described in Section 7.6. In this section, we describe more details about cookies if one is interested to implement his own session handling.

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 adding it with some web page. For doing so, there is the function

addCookies :: [(String,String)] -> HtmlPage -> HtmlPage

which adds a list of cookies, i.e., name/value pairs, to a web page. These cookies are submitted with the page to the client’s browser. To retrieve cookies (that are previously sent), 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 HTML page.

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 :: HtmlFormDef ()
loginForm = simpleFormDef
  [htxt "Enter your name: ", textField tref "",
   hrule,
   button "Login" handler
  ]
 where
   tref free
   handler env = return $
     addCookies [("LOGINNAME", env tref)] $
       page "Logged In"
            [h2 [htxt $ env tref ++ ": thank you for visiting us"]]
-- A web page containing the login form:
loginPage :: IO HtmlPage
loginPage = return $ page "Login" [formElem loginForm]

First, the form asks the user for his name. The cookie is set together with the acknowledgment page (defined in 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]:

getNamePage :: IO HtmlPage
getNamePage = do
  cookies <- getCookies
  return $ page "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 page 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 page with the message “Sorry, can’t set cookies.” instead of the acknowledgment page which sets the cookie “LOGINNAME[Browse Program][Download Program]:

loginPage :: IO HtmlPage
loginPage = return $
  addCookies [("SETCOOKIE","")] $ page "Login" [formElem loginForm]
loginForm :: HtmlFormDef ()
loginForm = simpleFormDef
  [htxt "Enter your name: ", textField tref "",
   hrule,
   button "Login" handler
  ]
 where
   tref free
   handler env = do
     cookies <- getCookies
     return $
       maybe (page "No cookies" [h2 [htxt "Sorry, cant set cookies."]])
             (\_ -> addCookies [("LOGINNAME",env tref)] $ page "Logged In"
                    [h2 [htxt $ env tref ++ ": thank you for visiting us"]])
             (lookup "SETCOOKIE" cookies)

7.9.3 Style Sheets

The library HTML.Base provides various operations to add style sheets to web pages (pageCSS) or to add style classes to individual elements (e.g., style, textstyle, blockstyle). The package html2 also contains libraries to use Bootstrap renderings in web pages, see library HTML.Styles.Bootstrap4 for more details.