HAppS Tutorial

Installing

To install HAppS the following packages are needed:

All modules except fps and HaXml are included with ghc and hugs.

To install do the following:

runhaskell Setup.hs configure
runhaskell Setup.hs build

and then as root:

runhaskell Setup.hs install

On Mac OS X one might need to compile Setup.hs first due to a bug in GHC/Cabal. "ghc --make Setup.hs -o setup" and use "./setup" instead "runhaskell Setup.hs". (as of 2006-04)

As an alternative you could consider using SearchPath. For details how to use see [4].

If you want to use XSLT on the server side, you have to install libxslt [8] which contains the xsltproc program. This is used to apply your xslt stylesheets to the XML files.

Overview

The application model in HAppS is to help separate state, application logic, wire formats, protocols, and presentation layer:

First Steps

This chapter will run you through some first simple programs written in HAppS. For other programs have a look at the directory named 'examples'.

Hello World

As usual with some programming languages (not Haskell) we will begin with a 'hello world' program.

Copy the following code into a file hello_world.hs:

module Main where

import HAppS
import Control.Monad.State

data MyState = MyState String deriving (Read, Show)

instance StartState MyState where
	startStateM = return $ MyState "Hello World!"
instance Serialize MyState where
	typeString _ = "MyState"
	encodeStringM = defaultEncodeStringM
	decodeStringM = defaultDecodeStringM

app :: Method -> Host -> [String] -> Ev MyState Request Result
app _ _ _ = do
	MyState message <- get
	sresult 200 message

main = stdMain $ simpleHTTP "" [] app :*: End

Now run the code. With ghc you can do:

ghc --make -fallow-overlapping-instances -o hello_world hello_world.hs
./hello_world

If all goes well you will see your first HAppS application starting. Point your browser at http://localhost:8000/ and you should see the line "Hello World!".

Stop the application by typing 'e' and hitting enter. You will notice that a new directory with the name 'hello_world_state' has been created. It contains the data needed by your application to run (or to continue it's operation when it's started anew, but more of this later).

Let's observe the simple program we used to generate the "Hello World!" line:

module Main where

import HAppS
import Control.Monad.State

The first three lines are the usual definition of the Main module and the needed imports. HAppS gives us access to the most common functions. The State Monad [7] is used for the state keeping (in this example it is barely used).

data MyState = MyState String deriving (Read, Show)

With this line the state for this program is defined. Currently it is very simple and contains only a single string. The state derives Read and Show to simplify the serialization of it.

instance StartState MyState where
	startStateM = return $ MyState "Hello World!"

To initialize a HAppS application, a start state has to be defined. By deriving from the StartState class this can easily be done. Here we set the content of our state to "Hello World!". This content will be read by our application quite soon.

instance Serialize MyState where
	typeString _ = "MyState"
	encodeStringM = defaultEncodeStringM
	decodeStringM = defaultDecodeStringM

To be able to save the application state to a file, the user defined state has to be able to be serialized. This is achieved by deriving from the Serialize class. The given code shows an easy way how to do this when the state derives Read and Show.

app :: Method -> Host -> [String] -> Ev MyState Request Result

The app function contains the application. Currently we ignore all the parameters. We will have a look at them a little later. As you can see, the application runs in the Ev Monad. This Monad guarantees the ACID properties you might already know from your favourite database system. For more information on what this means read [1] and [2].

app _ _ _ = do
	MyState message <- get
	sresult 200 message

The actual application is rather simple. The get function (from the State Monad) is used to access the actual content of the state. With the sresult function, a return code 200 (OK, see [3]) and the message itself is returned.

main = stdMain $ simpleHTTP "" [] app :*: End

To run the entire application this main function is provided. simpleHTTP is called with the application app as a parameter. Ignore the rest of the line for the moment.

Adding dynamic behaviour

Until now, we printed always the same string. For some applications this could be useful but for the vast majority of applications a little more dynamic behaviour would help.

In the following example, the state can be modified through a HTML-Form. As in the example before, copy the following program in a file (e.g. change_state.hs), compile it and run it.

module Main where

import HAppS
import Text.Html
import Control.Monad.State

data MyState = MyState String deriving (Read,Show)

instance StartState MyState where
	startStateM = return $ MyState "Hello World!"
instance Serialize MyState where
	typeString _ = "MyState"
	encodeStringM = defaultEncodeStringM
	decodeStringM = defaultDecodeStringM

app :: Method -> Host -> [String] -> Ev MyState Request Result
app GET _ ["change"] = do
	sresult 200 $ renderHtml page
	where
		page = header
			<< thetitle << "Change State"
			+++ h1 << "Change State"
			+++ gui "/change"
			<< ((textfield "state") +++ submit "change" "change")
app POST _ ["change"] = do
	rq <- getEvent
	put $ MyState (lookS 100 rq "state")
	redirect "/"
app _ _ _ = do
	MyState message <- get
	sresult 200 message

main = stdMain $ simpleHTTP "" [] app :*: End

When you point your browser at http://localhost:8000/ you will see the same as before. 'Hello World! on an otherwise empty page. You can now point your browser at http://localhost:8000/change and enter a new String which will be stored in the state.

Let's have a look at what happens in this program:

The first few lines are almost identical to the first program. The only change is the fact, that the module Text.Html [6] is imported. This is used for the generation of the HTML form later.

app :: Method -> Host -> [String] -> Ev MyState Request Result

The type definition stayed also the same but we will now use some of the parameters. The first parameter identifies the HTTP-Method [5] that has been called. The third parameter contains the path components. The second parameter contains information about the host that issued the request but this parameter will not be used in this example.

app GET _ ["change"] = do
	sresult 200 $ renderHtml page
	where
		page = header
			<< thetitle << "Change State"
			+++ h1 << "Change State"
			+++ gui "/change"
			<< ((textfield "state") +++ submit "change" "change")

The first partial definition of the application handles the case where a HTTP-Get request arrives. The path component we are interested in is the string "change". This definition will be executed when someone wants to see the page located at http://localhost:8000/changes. (For the URL http://localhost:8000/foo/bar/baz, the component list would look like ["foo", "bar", "baz"])

The HTML code generated by the 'page' function - which defines a simple HTML-page that contains a title and a form - is rendered to a String with the renderHtml function (all from the Text.Html module).

app POST _ ["change"] = do
	rq <- getEvent
	put $ MyState (lookS 100 rq "state")
	redirect "/"

This part of the application handles the POST-request that is issued by the web browser when the user submits the form.

The getEvent function allows to get the request information out of the Ev monad. It contains all the information about the request like the complete query string, browser information, submitted variables and more.

To access the variables stored in the request, the function lookS is used. It takes three parameters, a number that indicates the maximum length of the string to read, the request and the name of the variable to read. If the string in the variable is longer than the given length, it is truncated.

The 'put' function is the counterpart of the get function we already used. We use it to write back the new state.

After the state is written back (so that others can now access it), the web browser is redirected to the root path "/" of this application. This is done by sending the code 302 (undefined redirect [3]) to the browser.

app _ _ _ = do
	MyState message <- get
	sresult 200 message

This last part handles the printing of the state for all the queries that do not satisfy one of the patterns used before.

The last line is the same as in the first example.

Adding Side Effects

Doing stuff that changes the world around the program (like printing a string or writing something into a database) is something that crosses your way sooner or later. As you probably know, such operations are said to produce side effects and have to be treated specially in a pure functional language.

Side effects can easily be added to a HAppS program with the function 'addSideEffect'. It takes a time in seconds and an IO function as parameters. The time in seconds is a timeout. If the computation takes longer than the given time in seconds, it is aborted.

A simple example can be seen here:

module Main where
import HAppS

data MyState = MyState deriving (Read,Show)

instance StartState MyState where
	startStateM = return $ MyState
instance Serialize MyState where
	typeString _ = "MyState"
	encodeStringM = defaultEncodeStringM
	decodeStringM = defaultDecodeStringM

app :: Method -> Host -> [String] -> Ev MyState Request Result
app _ _ _ = do
	addSideEffect 10 $ putStrLn "Hello World!"
	sresult 200 "Hello World!"

main = stdMain $ simpleHTTP "" [] app :*: End

This simple program returns the string "Hello World!" to the browser and prints it on the console where you run it.

XML and XSLT

HAppS offers some easy ways to read and write XML and apply XSLT transformations to the generated XML.

Creating XML

The following example shows how to generate XML from a datatype and how to serialize it. Additionally a XSLT [9] stylesheet is used to transform the XML into HTML. If you don't know how XSLT works, try a result from [10] to learn it, it is an easy way to separate your data from the representation.

To run the example, copy the following code into a file and compile it. The ToElement instance of Books requires the use of the -fglasgow-exts compile flag.

module Main where
import HAppS

data MyState = MyState deriving (Read,Show)

instance StartState MyState where
	startStateM = return $ MyState
instance Serialize MyState where
	typeString _ = "MyState"
	encodeStringM = defaultEncodeStringM
	decodeStringM = defaultDecodeStringM

type Books = [Book]
instance ToElement Books where
	toElement bs = listElem "Books" [] $ map toElement bs

data Book = Book {author::String, title::String, content::String}
instance ToElement Book where
	toElement b = textElem "Book"
		[("title", title b)
		,("author", author b)] $ content b

app :: Method -> Host -> [String] -> Ev MyState Request Result
app GET _ ["books"] = do
	toMessageM $ XML (XSL "/static/books.xsl") $ toElement
		[(Book "Haskell Curry" "Functions" "lambda")
		,(Book "Many" "Haskell Report" "String -> IO ()")]

main = stdMain $ simpleHTTP "static/books.xsl" [("/static/", "static")] app :*: End

Make a new directory called 'static' and insert the code below into a file called 'static/books.xsl'. This directory has to be at the same place where you run the program.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
	<body>
		<h2>Books</h2>
		<table border="1">
			<tr bgcolor="#C0C0C0">
				<th align="left">Title</th>
				<th align="left">Author</th>
			</tr>
			<xsl:for-each select="Books/Book">
			<tr>
				<td><xsl:value-of select="@title"/></td>
				<td><xsl:value-of select="@author"/></td>
			</tr>
			</xsl:for-each>
		</table>
	</body>
</html>
</xsl:template>
</xsl:stylesheet>

Now you can point your browser at http://localhost:8000/books and you will see a list of books. Depending on your browser, you will see the XML code or HTML code when you examine the source of the document.

Let's examine this program.

module Main where
import HAppS

data MyState = MyState deriving (Read,Show)

instance StartState MyState where
	startStateM = return $ MyState
instance Serialize MyState where
	typeString _ = "MyState"
	encodeStringM = defaultEncodeStringM
	decodeStringM = defaultDecodeStringM

For this example we use a very minimal state which contains no information.

type Books = [Book]
instance ToElement Books where
	toElement bs = listElem "Books" [] $ map toElement bs

The type Books is nothing but a list of single books. The derivation of the class ToElement allows us later on to call the toElement function to get a XML representation of this book list.

The toElement function is written with the listElem function which is provided from the HAppS.Protocols.MinHaXML module (automatically imported when you import HAppS). It creates a XML element called "Books" with no attributes (the empty list). This element contains the XML representation of the books in the list as child elements. These are generated by applying the toElement function to the list of books.

data Book = Book {author::String, title::String, content::String}
instance ToElement Book where
	toElement b = textElem "Book"
		[("title", title b)
		,("author", author b)] $ content b

The datatype Book defines a book itself. It has an author, a title and a content. As before with the Books type, it derives from the ToElement class.

Instead of the listElem function we use now textElem. It allows to create an element that contains text in its body. Additionally we define two attributes of this element, title and author. The content of the book is written as text.

app :: Method -> Host -> [String] -> Ev MyState Request Result
app GET _ ["books"] = do
	toMessageM $ XML (XSL "/static/books.xsl") $ toElement
		[(Book "Haskell Curry" "Functions" "lambda")
		,(Book "Many" "Haskell Report" "String -> IO ()")]

The message to send to the client is constructed by a call to the toElement function. The XML constructor takes an element and information to the stylesheet to be used as parameters. In this case, an XSL stylesheet is used.

The toMessageM finally converts the XML into a message with header information that can be sent to the client. It is predefined for XML and some few other types. You can derive from the ToMessage class for any type you want.

Please note that the XSLT handling can happen on server or on client side. If for example the content field is very big and you rely on the XSLT to remove it from the HTML (as it is the case in this example). The whole content will be transferred to the client if the client supports XSLT.

main = stdMain $ simpleHTTP "static/books.xsl" [("/static/", "static")] app :*: End

The main function is now extended with an indication to serve static pages from the 'static' directory under the path "/static/". The list contains tuples of URL locations to directory location mappings.

Reading XML

References

[1] The HAppS Readme
[2] Wikipedia on ACID
[3] HTTP Status Codes
[4] Searchpath
[5] HTTP Methods
[6] Text.Html
[7] The State Monad
[8] libxslt
[9] XSLT specification
[10] XSLT tutorials