Happstack’s “Helpful” Exception Handler: A Better Solution

Recently I wrote about Happstack’s mismanagement of exceptions. I offered up a solution thab basically amounted to swallowing the exception and returning mzero. As a follow-up to that post, I’d like to offer a better solution.

I believe Happstack is on the right track. An uncaught exception does in fact represent an internal server error, not a file not found, however Happstack misses the mark in a few ways. First, it prints the exception message, which for reasons I went into last week, is bad. And second, it doesn’t provide a mechanism to override this behavior.

It was suggested to me that I could place a monad on top of the ServerPartT transformer stack that re-implements fail. This represents a few issues for me. If I were to do this, I wouldn’t be able to use the basic ServerPart monad, greatly complicating my code as this locks me out of all library functions that take this type. Secondly, it’s an unnessesary level of indirection. Why should I add a monad to the stack that does nothing except re-implement fail? Why can’t I configure Happstack to use a custom exception response?

So, What Are You Going To Do About It?

Good question. I’ve come up with a solution that I’m fairly satisfied with. At the heart of this issue are IO actions that throw exceptions. To perform an IO action inside of a ServerPart, you must lift ServerPart‘s IO monad. This is done like so:

foo <- liftIO $ bar

This snippet executes the IO action bar and binds its result to foo. Since, typically speaking, only IO actions throw, this represents a good place to start working. We can insert a call to try here, but this can get tedious given enough IO actions. Why don’t we create a function?

tryIO :: Maybe (SomeException -> ServerPart Response) -> IO a -> ServerPart a tryIO mf io = do result <- liftIO $ try io case (result) of Right good -> return good Left exception -> handle exception mf where handle exception (Just handler) = escape $ handler exception handle _ Nothing = mzero

Meet my tryIO function. This function wraps liftIO and try into one nice and neat function. It even takes an optional response generator. Let’s take a look at this bit-by-bit.

tryIO :: Maybe (SomeException -> ServerPart Response) -> IO a -> ServerPart a

This function takes a function that takes a SomeException, and returns a ServerPart Response. This function is wrapped in a Maybe. The function also takes an IO action, and returns a ServerPart a. Basically, this works like a specialized version of liftIO. Unlike a vanilla liftIO, it can take an optional exception handler.

tryIO mf io = do result <- liftIO $ try io case (result) of Right good -> return good Left exception -> handle exception mf

Here we have the meat of this function. First, the IO action is wrapped in a call to try. Next this is promoted to a ServerPart via the call to liftIO. The result of this will be Either a SomeException, or a valid result. We use a case statement to examine the result, and either return the good result, or call handle and pass it the exception and the exception handler.

where handle exception (Just handler) = escape $ handler exception handle _ Nothing = mzero

Finally, we handle the exception. If an exception handler was passed in, it’s called, and then the response is returned to Happstack via escape. If no handler was passed in, the function returns mzero.

The function escape is very useful here. This function is basically like returning mzero, only instead of the response handler failing, it returns a ServerPart Response immediately. This allows you to break out of a deeply nested function easily without having to manually return from each function.

The result

The result of this new function is that I can use tryIO as I would have used liftIO in a ServerPart function. I can either call…

foo <- tryIO Nothing bar

…and have my response handler fail with mzero if an exception is thrown, or I can call…

foo <- tryIO (Just baz) bar

…and have my response handler fail and return the result of baz. baz could even do some cleanup and try to call the initial response handler over again if it wanted to! Currently, when an IO action fails in my application, users see this lovely error message:

none_of_your_business

Much better. Unless you’re a hacker!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: