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.
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.