Simple Haskell Web Programming with HAppS

Motivation

There are two or three CGI libraries for Haskell. But if you ask the cognoscenti (to be found on the #haskell IRC channel) for Haskell web programming recommendations they will almost invariably point you at HAppS.

HAppS may have many virtues but it’s documentation and associated tutorial do not number among them.

Copying the first Rails screencast, Creating a Blog in Fifteen Minutes, I figured that a good example web application is a blogging system. Leaving aside that blogging is now pass´┐Ż, here is my HAppS tutorial.

Please note that I am a fairly inexperienced Haskell programmer and almost all the advice I give will be wrong. Some of what I say will necessarily be vague because I don’t understand it completely myself. But the advantage to my tutorial is that it will at least be comprehensible! I hope that some Haskell gurus will read this and find my mistakes and update me in the comments. Thanks!

Installation

For installation of HAppS, please see my earlier post.

Hello, World

First, let’s get HAppS working and see something in our browser. Save the following listing to Hello.hs:

import HAppS
import HAppS.Protocols.SimpleHTTP2
 
main = stdHTTP [debugFilter, noState, h () GET $ ok $ val "Hello, World"]

If you compile that with:

ghc --make Hello.hs -o hello

And then run the resulting “hello” binary (./hello under Unix, hello under Windows). If you visit http://localhost:8000/ in your browser you should see “Hello, World” displayed.

So what have we actually done there? Well, I don’t really know. But certainly we’ve passed a list of directives to the stdHTTP function. One of which (the bit starting “h”) says: “if you get a GET request of any sort, respond with a page containing the text ‘Hello, World'”. Let’s just leave it at that for now.

Blog System

Weren’t we meant to building a blogging system? Yes! OK, let’s start with the URL scheme. We want the following paths in our system:

GET  /new 		=> Show the "create new post" form
POST /new	 	=> Create a new post using the submitted values
GET /post/$id 	=> Display the post with id $id
GET /			=> Dispaly the last 5 posts

The way to describe this scheme to HAppS is through the first and second arguments to the h request-handling function. The first argument is the path as a list (each element in the list representing a directory) and the second argument is the type of request (GET, POST and so on). So for displaying the “create new post form” we use h ["new"] GET.

Our GET /post/$id path is slightly tricky in that we don’t want to write out a very long list of post ids. We want to do some pattern matching. You can do this in HAppS with the re function. This tells HAppS to interpret the paths in the list as regular expressions. So for GET /post/$id we can use h ["post", "(0-9 )"] GET.

Putting that all together gives us:

import HAppS
import HAppS.Protocols.SimpleHTTP2
 
main = stdHTTP $ debugFilter : noState : 
           [h ["new"]                           GET  $ ok $ val "Show create post form"
           ,h ["new"]                           POST $ ok $ val "Save post and forward to display post"
           ,h (re ["post", "([0-9] )"])  GET  $ ok $ \\(id:_) () -> respond ("Display post "    id)
           ,h [""]                              GET  $ ok $ val "Display five most recent posts"
           ]

HTML in Haskell

Those calls to val are just placeholders. What we really want to return is some HTML.

The most common way to get HTML in Haskell seems to be to use one of the HTML combinator libraries such as Text.XHTL.Strict. This is only tangentially related to HAppS so I’ll just point you at this simple example and these fairly comprehensible notes and move on.

Now we have:

import HAppS
import HAppS.Protocols.SimpleHTTP2
import Text.XHtml.Strict

main :: IO ()
main = stdHTTP $ debugFilter : noState : 
           [h ["new"]                           GET  $ ok $ val showCreatePostPage
           ,h ["new"]                           POST $ ok $ val "Save post and forward to display post"
           ,h (re ["post", "([0-9] )"])  GET  $ ok $ \\(id:_) () -> respond ("Display post "    id)
           ,h [""]                              GET  $ ok $ val "Display five most recent posts"
           ]

-- Handlers

showCreatePostPage :: String
showCreatePostPage = showHtml getCreatePostPage

-- HTML Functions

getCreatePostPage :: Html
getCreatePostPage = mkHeader "New Post"
        body < < [
        (h1 << "New Post") 
            getCreatePostForm
        ]

getCreatePostForm :: Html
getCreatePostForm = (form ! [action "/new", method "post"] << (fieldset << [
        label ! [thefor "title"] << "Post Title", br,
        input ! [name "title", identifier "title", thetype "text"], br, br,
        label ! [thefor "body"] << "Post Body", br,
        textarea ! [name "body", identifier "body", rows "10", cols "40"] << "", br, br,
        submit "create" "Create Post"
    ]))

mkHeader :: String -> Html
mkHeader title = (header < < (thetitle << (title    " - HBlog")))

State

Of course I've chickened out above. Yes, I've provided some HTML that never changes. But what about the pages that depend on state? What about the display of the posts themselves? It's time to get rid of that odd-looking noState directive and start storing some blog posts. This is where it gets a little bit hairy.

While you could use a relational database with HAppS, it actually has it's own in-built haskell type-based storage system. It has lots of whizzy features that I'm not concerned about here to do with atomicicity and all that kind of shenanigans. But let's stay focused - we need a way of storing some blog posts. That means we need to define a type that derives Show and Read.

data Posts = Posts [Post] deriving (Read, Show) 
data Post = Post { pTitle :: String, pBody :: String } deriving (Read, Show)

What’s more we have to give it some instances so that HAppS can start it up, and know how to store it.

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

instance StartState Posts where
    startStateM = return $ Posts []

OK, so now we know where we are going to store our posts. But how do we construct them in the first place? Well, Post is a record type, I will assume you know how to use record types. But what about extracting the values from the post body to slot into the record?

Strings we can get with:

v < - lookMb return req "fieldName"

where req is the Request, the second parameter passed to the function after ok in our request handlers. This code must appear in a do block.

Ints we can get with:

v < - lookMb readM req "fieldName"

Quite what the meaning of this distinction is, I don't know. So please leave a comment if you understand this better than me.

The state of the application itself (in our case a Posts, or list of Post) you can retrieve with get and update with put as long as you are within the appropriate Monad. This probably makes more sense in the context of a full example:

module Main where

import Control.Monad.State (get, put)
import Debug.Trace
import HAppS
import HAppS.Protocols.SimpleHTTP2
import Text.XHtml.Strict

main :: IO ()
main = stdHTTP $ debugFilter : 
           [h ["new"]                           GET  $ ok $ val showCreatePostPage
           ,h ["new"]                           POST $ ok createPost
           ,h (re ["post", "([0-9] )"])         GET  $ ok showPost
           ,h [""]                              GET  $ ok showPosts
           ]

-- State

data Posts = Posts [Post] deriving (Read, Show) 
data Post = Post { pTitle :: String, pBody :: String } deriving (Read, Show)

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

instance StartState Posts where
    startStateM = return $ Posts []

-- Handlers

showCreatePostPage = showHtml getCreatePostPage

showPost (rawId:_) () = do
    (Posts posts) < - get
    let postId = read rawId :: Int
        post = posts !! postId 
    respond $ showHtml $ getPostsHtml [post]

showPosts () () = do
    (Posts posts) <- get
    respond $ showHtml $ getPostsHtml $ take 5 $ reverse posts 

createPost () req = do
    title <- lookMb return req "title"
    body  <- lookMb return req "body"
    let p = Post { pTitle = title, pBody = body }
    (Posts ps) <- get
    put (Posts (ps    [p]))
    respond $ "Post created"

-- HTML Functions

getCreatePostPage :: Html
getCreatePostPage = mkHeader "New Post"
        body << [
        (h1 << "New Post") 
            getCreatePostForm
        ]

getCreatePostForm :: Html
getCreatePostForm = (form ! [action "/new", method "post"] << (fieldset << [
        label ! [thefor "title"] << "Post Title", br,
        textfield "title", br, br,
        label ! [thefor "body"] << "Post Body", br,
        textarea ! [name "body", identifier "body", rows "10", cols "40"] << "", br, br,
        submit "create" "Create Post"
    ]))

getPostsHtml :: [Post] -> Html
getPostsHtml posts = foldl (   ) noHtml (map getPostHtml posts)

getPostHtml :: Post -> Html
getPostHtml post = 
    thediv 
        < < h2 << (pTitle post)
            p << (pBody post)

mkHeader :: String -> Html
mkHeader title = (header < < (thetitle << (title    " - HBlog")))

Once your HAppS application has state then wherever you run it it will create directories to store state information. If you change your state type, you must delete these directories (you will lose existing state of your app) to prevent errors like: *** Exception: user error (decodeStringM: parsing length field failed @ "").

Conclusion

So there you have a working blog publishing system in HAppS. It's more a proof of concept than a useable application. But hopefully it shows you how to create a simple application in HAppS without boiling your brain too much. There are zillions of improvements that could be made: add comments; make Posts a sequence and not a list; add error handling for empty fields; redirect to post display after creation; handle error when post id is greater than the number of posts; don't number posts starting from 0; allow deletion of posts; and many many others.

I must credit Eric Mertens for his sterling work on HPaste whose source code I read to make sense of much of what HAppS was doing.

Please do correct all my terrible misunderstandings and mistakes in the comments!

10 thoughts on “Simple Haskell Web Programming with HAppS

  1. Jonathan Aquino

    Ah shoot, it stripped out my code – anyway I just pasted in the snippet from http://js-kit.com/comments/

  2. manu

    this snippet :

      main = stdHTTP $ debugFilter : noState :              [h ["new"]                           GET  $ ok $ val "Show create post form"             ,h ["new"]                           POST $ ok $ val "Save post and forward to display post"             ,h (re ["post", "([0-9]+)"])  GET  $ ok $ (id:_) () -> respond ("Display post " ++ id)             ,h [""]                              GET  $ ok $ val "Display five most recent posts"             ]  

    refuse to compile :

      [1 of 1] Compiling Main             ( Hello.hs, Hello.o )  

    Hello.hs:9:63: parse error on input `->’

  3. Thomas David Baker

    Sorry Manu bit of an escape character failure there. This line:

      ,h (re ["post", "([0-9]+)"])  GET  $ ok $ (id:_) () -> respond ("Display post " ++ id)  

    should be:

      ,h (re ["post", "([0-9]+)"])  GET  $ ok $ \\(id:_) () -> respond ("Display post " ++ id)  

    (that’s a haskell lambda expression for a function with two arguments the first of which is a list and the second is null). I’ve corrected it in the article. Thanks!

  4. manu

    oops it got chomped A few more correction to get the last example to compile : line 35 : < – should be <- line 76 & 80 : < < should be << otherwise it works, thanks for the tutorial !

  5. Thomas David Baker

    Thanks Manu, I’ve corrected those too. Apparently wordpress doesn’t like less than symbols.

  6. Jerry

    Thanks for a nice example. I have been very intrigued by HAppS for a while but have constantly been put off by the confusing, missing, outdated, and sometimes wrong documentation. The questions that get asked on the HAppS mailing list go flying over my head. I’ve been search for something between broken tutorial and esoteric questions, and your refreshing example fits the bill nicely! Thanks!

  7. Pingback: How to Install HAppS - bluebones.net

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>