Search code examples
haskellexceptionhappstack

Catch program errors in Happstack request handler


I'm kind of a noob at Haskell, so I'm not entirely sure if this is a Happstack question or a general Haskell question.

Here's an example of the difficulty I'm having. This code "theoretically" renders some content, but actually throws an error:

throwsError :: String
throwsError = fromJust Nothing

-- no error page
main :: IO ()
main = do
  simpleHTTP nullConf $ do
    decodeBody (defaultBodyPolicy "/tmp/" 4096 4096 4096)
    ok $ toResponse throwsError

This error does not crash the whole program. Fortunately, Happstack catches any errors thrown while handling the request, like a web server should. However, unfortunately it does not display any kind of error page to the user. It responds with status code 200 and empty content.

Now, if I simply output the error string first:

-- yes error page
main :: IO ()
main = do
  simpleHTTP nullConf $ do
    decodeBody (defaultBodyPolicy "/tmp/" 4096 4096 4096)
    lift $ putStrLn throwsError -- added this line
    ok $ toResponse throwsError

Happstack returns status 500 and displays an error page. Why is Happstack behaving like this?

The ServerPart monad implements MonadThrow, so I tried importing Control.Monad.Catch (handle) and writing this, but it didn't do what I expected; it again returned 200 with no content:

showErrorPage :: SomeException -> ServerPart Response
showErrorPage _ = internalServerError $ toResponse "Error"

-- also no error page
main :: IO ()
main = do
  simpleHTTP nullConf $ handle showErrorPage $ do
    decodeBody (defaultBodyPolicy "/tmp/" 4096 4096 4096)
    ok $ toResponse throwsError

In case it isn't clear, I would like to handle all errors thrown, so I can log them and display a custom error page. (Except, of course, for errors that are thrown while logging and displaying the error page). Guidance would be appreciated.


Solution

  • I found the answer, by the way. Basically it has to do with lazy evaluation - the Response object is only evaluated to WHNF until after Happstack has internally started streaming data into the response body, at which point it is too late to change the status code from 200 to 500.

    This is the correct behavior if the response is a video stream, since strictly evaluating the response to make sure it does not contain any undefined values before sending the HTTP response would be unwieldy in that case. However, it is decidedly inconvenient for known-small responses that are practical to deeply, strictly evaluate to eliminate any possibility of bottom values. Furthermore, HTTP clients (mostly browsers) implicitly assume that a 200 response code means the request was a success - and it is only in specific cases such as streaming videos that the 200 response is in any way "not trusted" and the code consuming the HTTP response looks deeper into it to make sure that it is well-formatted and well-formed before processing and displaying it to the end user - indeed as seen here in my question as the browser displays an empty page and the network tab shows 200 with no content even though there very much was an internal server error.

    Here is a pull request of mine against Happstack that simply adds some typeclasses to the Response object so that it is possible to deeply, strictly evaluate it before returning it into the ServerPart monad. That particular PR has had a troubled history (it used to do something a bit larger in scope) and so I might close it and open a fresh one. If so, that one should be marked as closed and have a link to the fresh one.