Wednesday, October 20, 2010

Recompile your Haskell-based templates faster than you can hit F5.

There are two main classes of templating solutions in Happstack:

1. DSLs/libraries such as BlazeHtml, HSP, Hamlet, etc, where your templates are written in Haskell and compiled at compile time.

2. Libraries like heist, HStringTemplate, etc, where your templates are written in some external template file and read at runtime by the server.

Each method has strengths and weaknesses -- and so each project needs to pick the solution that works best for them.

For my projects I love using HSP. I like having the full expressive power of Haskell in my templates, and the added safety that the type checker provides. But I hate having to recompile, relink, and restart my app server dozens and dozens of times when I am developing my templates. And, so it is with great pleasure that I present the triumphant return of happstack-plugins!


happstack-plugins leverages the recently revived plugins package so that individual page templates can be automatically recompiled and reloaded into a running happstack application. happstack-plugins uses hinotify to watch the haskell source files containing your page templates. Whenever you save changes, the page is automatically recompiled and reloaded into the running server. Typically this happens fast enough that by the time you switch to the browser and hit reload, the updated page is already available.

You can see a demo of happstack-plugins in action here:

How to use happstack-plugins

Using happstack-plugins is very straight-forward. First you need to install the happstack-plugins library which is currently only available in the happstack darcs repository:

darcs get

For best performance you should put each page template in its own module so that it can be recompiled and reloaded faster.

The templates themselves require no special modifications. Here is a simple helloPage template:

> module HelloPage where
> import Happstack.Server
> helloPage :: String -> ServerPart Response
> helloPage noun = ok (toResponse $ "hello, " ++ noun)

This template takes a single String argument and returns a text/plain page which says, "hello, <string>". We could just as well use BlazeHTML, HSP, etc, but using String keeps this example short and simple.

As I mentioned, there is nothing new going on here, it just a normal happstack ServerPart.

The interesting changes are in the Main module. There are only 3 simple changes required to support templates. But first, some boring stuff at the top of the module:

> {-# LANGUAGE CPP, TemplateHaskell #-}
> module Main where
> import Control.Monad (msum)
> import Happstack.Server

1. Here we #ifdef some module imports. These two modules provide the same interface. The Dynamic version actually does page recompilation and reloading. The Static version just links things in the normal way. This makes it easy to use dynamic loading during development but static linking for the live server by simply defining or undefining PLUGINS.

> #ifdef PLUGINS
> import Happstack.Server.Plugins.Dynamic
> #else
> import Happstack.Server.Plugins.Static
> #endif
> import HelloPage

2. In main we call initPlugins which starts the recompiler/reloader and hinotify. If you import Happstack.Server.Plugins.Static, initPlugins is a 'noop', so we do not have to add any extra #ifdefs.

> main :: IO ()
> main =
> do ph <- initPlugins
> simpleHTTP nullConf $ pages ph

3. Here is where we actually specify a template to load dynamically:

> pages :: PluginHandle -> ServerPart Response
> pages ph =
> msum [ $(withServerPart 'helloPage) ph $ \helloPage ->
> (helloPage "hello")
> ]

Normally we would just have:

> pages :: PluginHandle -> ServerPart Response
> pages ph =
> msum [ helloPage "world"
> ]

So the new part is the template haskell function withServerPart which effectively takes three arguments:

1. the name of the symbol to dynamically load
2. the PluginHandle which initPlugins returned
3. a function which will use the loaded symbol

so, withServerPart effectively has the type:

> withServerPart :: (MonadIO m, ServerMonad m) => Name -> PluginHandle -> (a -> m b) -> m b

Even though we are dynamically reloaded the page at runtime, the compiler will still check that the types are correct when will compile the main application.

If we change helloPage "hello" to helloPage 1 and try to build Main.lhs we will get the error.

No instance for (Num String)
arising from the literal `1' at Main.lhs:50:28
Possible fix: add an instance declaration for (Num String)
In the first argument of `helloPage', namely `1'
In the expression: (helloPage 1)
In the second argument of `($)', namely
`\ helloPage -> (helloPage 1)'
Failed, modules loaded: HelloPage.

What's left to do?

There are two big features on the TODO list. If you think happstack-plugins is cool, I encourage you to work on them!

1. The underlying plugins library is broken when it comes to hierarchical modules. Ideally I would put all the pages in Pages.*. For example Pages.HelloPage. But, that does not work. As a hack, you can modify and comment out output in the let flags = ... declaration. This fixes hierachical modules, but requires you to run your app with its working directory set to the root directory of your project. That is fine for happstack app development, but not an ideal solution for all users of the plugins library. If someone could fix hierarchical module support in plugins, that would be great for everyone.

2. hinotify is only supported under Linux. However, it should not be that hard to make hinotify support optional (via a compile time flag). With out hinotify, we would just do a quick stat() everytime the template is invoked and see if a recompilation is needed. When a compilation is needed, you will have to wait for that page to recompile and reload -- but it will still be much faster than rebuilding and restarting the whole server.

Wednesday, October 13, 2010

Is the RqData monad still needed?

cdsmitch recently asked if RqData is really needed in Happstack. The answer is, "no, but it is still useful sometimes."

I can say "no" with certainty because in the darcs version of Happstack, it is already optional.

The new and improved RqData

Functions like look now work in any monad which is an instance of HasRqData:

> look :: (Functor m, Monad m, HasRqData m) => String -> m String

Since there is a HasRqData instance for ServerPart, we effectively have the function:

> look :: String -> ServerPart String

Here is an example of using look with out having to jump through any hoops:

> module Main where
> import Happstack.Server (ServerPart, look, nullConf, simpleHTTP, ok)
> helloPart :: ServerPart String
> helloPart =
> do greeting <- look "greeting"
> noun <- look "noun"
> ok $ greeting ++ ", " ++ noun
> main :: IO ()
> main = simpleHTTP nullConf $ helloPart

Now if we visit http://localhost:8000/?greeting=hello&noun=rqdata, we will get the message hello, rqdata


But why keep RqData around?

Using look in the ServerPart monad is simple. But when it fails, it just calls mzero. That can be very frustrating if you are debugging your forms or debugging calls to your web service API. Instead of an error telling you what parameter was missing, you simply get a generic 404 error.

Using the RqData monad/applicative functor gives you the option to provide detailed error messages when something goes wrong:

> module Main where
> import Control.Applicative ((<$>), (<*>))
> import Happstack.Server (ServerPart, badRequest, nullConf, ok, simpleHTTP)
> import Happstack.Server.RqData (RqData, look, getDataFn)
> helloRq :: RqData (String, String)
> helloRq =
> (,) <$> look "greeting" <*> look "noun"
> helloPart :: ServerPart String
> helloPart =
> do r <- getDataFn helloRq
> case r of
> (Left e) ->
> badRequest $ unlines e
> (Right (greet, noun)) ->
> ok $ greet ++ ", " ++ noun
> main :: IO ()
> main = simpleHTTP nullConf $ helloPart

If you visit http://localhost:8000/?greeting=hello&noun=world, you will get the familiar greeting hello, world.

But if you leave off the query parameters http://localhost:8000/, you will get a list of errors:

Parameter not found: greeting
Parameter not found: noun

This is really nice when you are debugging your code.

Now with more composability!

Since RqData and ServerPart are instances of Applicative and Alternative you can now reuse many functions from those libraries. For example, if a query parameter is optional, you can simply write:

>     do greet <- optional $ look "greeting"

There is also a new combinator checkRq which can be used to validate query parameters, or to convert a query parameter to another type:

> checkRq :: (Monad m, HasRqData m) => m a -> (a -> Either String b) -> m b

If you are curious be sure to check out the Happstack Crash Course where the new RqData module is documented in detail with many working examples.

I would love to hear feedback on the new and improved RqData module, and any suggestions for improvement!

Also, be on the look out for a future blog post about the RqData Arrow. :)