Sunday, April 19, 2009

Learning Happstack

I've been trying to shoehorn Haskell into my job for a while now. Occasionally I'll use it for small command-line tools, but usually it doesn't save any time in such a scenario, so it's hard to justify.

But we do have some internal webapps that need updating. Some of them do horrible things. For instance, to create a customer account, a webapp will write a bunch of raw shell commands to a database where they will be later pulled out by a daemon and run directly. I dunno why this was done, poor-man's threading perhaps.

I'm not touching that particular webapp, since I'm afraid modifying it will unravel more than I hope to correct, but I'm hoping I could shoehorn in some happstack apps in a few other places.

So let's jump right into some code I wrote to try out a simple templated app:


> import Happstack.Server
>    (ServerPartT, Response, dir, toResponse, simpleHTTP, Conf (Conf))
> import Happstack.Helpers (HtmlString (HtmlString))
> import Control.Monad.Trans (lift)
> import Control.Monad (msum, liftM)


If you're going to use happstack, it seems you have to be pretty comfortable with navigating between different levels in composite monads.


> import Text.StringTemplate.Helpers (renderTemplateGroup)
> import Text.StringTemplate (directoryGroup)


It seems that HStringTemplate is the standard way to template HTML in happstack. I think that using '$' as brackets is pretty ugly in HTML. Ideally the template language should mix well with HTML. I've wondered if this couldn't be done with a modified version of html comments (<!--* template code *-->). It's rather verbose, but it makes for valid HTML before the template is rendered. But this is a small complaint.


> exampleMethod :: IO [(String, String)]
> exampleMethod = return $ [("hello", "Hello, World.")]
>
> methodsAssList :: [(String, IO [(String, String)])]
> methodsAssList = [("exampleMethod", exampleMethod)]


I wanted to show in this example how requests for different paths could be handled by different methods. In this example that handling is very simplistic. I think a web framework should have a standard way of handling requests, like breaking the request into a path parameter, a request method and a map of string request variables. I haven't discovered such a thing in happstack yet.


> makeMethods :: IO [(String, IO String)]
> makeMethods = do
>    templates <- directoryGroup "templates"
>    let
>       makeRenderTemplateFunction (name, assListM) = let
>             renderFun = do
>                assList <- assListM
>                return $ renderTemplateGroup templates assList name
>          in (name, renderFun)
>
>    return . map makeRenderTemplateFunction $ methodsAssList


This function is used because the HStringTemplate library can load a group of templates in a single directory at once with "directoryGroup". Then we can translate each method name to a name of a template (i.e. templates/exampleMethod.st)

So all the exampleMethod written above does, is give an association list of variables to replace in the template, and their values.


> fillRequest :: (Monad m) => String -> m String -> ServerPartT m Response
> fillRequest name method =
>    dir name . lift .
>       liftM (toResponse . HtmlString) $ method


This simply associates a method with a path from the root.


> responses :: IO (ServerPartT IO Response)
> responses = liftM (msum . map (uncurry fillRequest)) makeMethods


Now we make a response for each path request to its method, and group these
responses together in one response.


> main = responses >>= simpleHTTP (Conf 8080 Nothing)


finally, we set up the server with some basic settings (i.e. the server responds on port 8080), and set the response.

The only other thing necessary is writing the template at "templates/exampleMethod.st":

<html><body><h2> $ hello $ </h2></body></html>