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
IO monad. This is done like so:
foo <- liftIO $ bar
This snippet executes the
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
tryIO function. This function wraps
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
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
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 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 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:
Much better. Unless you’re a hacker!