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.
The application model in HAppS is to help separate state, application logic, wire formats, protocols, and presentation layer:
State is just a haskell data type you define. ACID [2] Consistency enforced by Haskell's type system. ACID Durability is handled by MACID write-ahead logging and checkpointing.
Incoming events are gathered in individual haskell threads and then pushed onto a single application queue for processing. The queue model gives you ACID Atomicity and Isolation and lets your app be simply a set of functions with types like:
SomeInputType -> MACID SomeOutputType
The MACID monad lets you update your state and *schedule* side-effects. To be clear, MACID is not in the IO monad so you cannot execute side effects, you can only schedule them. The framework takes care of making sure they are executed at-least-once (if they can be completed by a deadline you specify).
Since your app consists of a set of functions with various haskell input and output types, somewhere you need a place to convert between those internal haskell types and external protocol event types; e.g. from URL Encoded HTTP requests to SomeInputType and from SomeOutputType to XML encoded HTTP responses.
HAppS currently provides support for HTTP Requests/Responses and SMTP Envelopes. To be clear HAppS provides ACID Atomicity at the protocol event level. So if you write a protocol with SMTP envelopes being the arriving event type then your app will have atomicity in processing incoming SMTP envelopes. If you write a protocol with SMTP commands being the arriving event type, then your app will have atomicity at the level of individual smtp commands.
If your application outputs XML as its wire format, HAppS provides a lot of support for using XSLT to transform it for presentation purposes. For example, you can send XML mail and HAppS will take care of applying the relevant XSLT stylesheet before it is delivered. If you output XML HTTP responses, HAppS takes care of applying the XSLT stylesheet server side for user-agents that don't support doing so on the client. The value here is that you can have designer types who know XSLT modify presentation stuff without touching your application code.
This chapter will run you through some first simple programs written in HAppS. For other programs have a look at the directory named 'examples'.
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.
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.
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.
HAppS offers some easy ways to read and write XML and apply XSLT transformations to the generated 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.