Skip to content

Commit bd7ad41

Browse files
committed
Enable deep strict evaluation of Response objects
Consider this server: it attempts to return a string with status 200, however the string's value is `undefined`. import Control.Monad.Catch (SomeException, handle) main :: IO () main = do let sendResponse = ok $ toResponse (fromJust Nothing :: String) simpleHTTP nullConf (handle errorPage sendResponse) where errorPage :: SomeException -> ServerPart Response errorPage _ = (internalServerError $ toResponse "Custom error page!") On each request, this server replies with "200 OK" with no content (even though philosophically there was a clear internal server error, and furthermore there is an error handler present). At least it outputs "HTTP request failed with: Maybe.fromJust: Nothing" to stderr. This patch makes it possible to deeply strictly evaluate Response objects before sending the HTTP response. Then errors like these can be caught and an error page shown. The application can be changed to something like this: import Control.DeepSeq (deepseq) import Control.Monad.Catch (SomeException, handle) handleServerPartError :: ServerPart Response -> ServerPart Response handleServerPartError s = handle errorPage $ do res <- s deepseq res (return res) where errorPage :: SomeException -> ServerPart Response errorPage _ = (internalServerError $ toResponse "Custom error page!") main :: IO () main = do let sendResponse = ok $ toResponse (fromJust Nothing :: String) simpleHTTP nullConf (handleServerPartError sendResponse) Which deeply, strictly evaluates the Response, revealing the `undefined` that is present and displaying the custom error page. Not all requests (streaming videos, for example) can or should be deeply strictly evaluated before being sent to the client. However, for requests like those, the client often knows not to fully trust the 200 status code of the response, and likely has some error handling built in if the response data does not stream in in the expected format. For most HTTP requests, the status code can be trusted, and client applications should not need to have any special handling.
1 parent 8d41110 commit bd7ad41

File tree

2 files changed

+8
-5
lines changed

2 files changed

+8
-5
lines changed

happstack-server.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Library
7979
blaze-html >= 0.5 && < 0.10,
8080
bytestring,
8181
containers,
82+
deepseq,
8283
directory,
8384
exceptions,
8485
extensible-exceptions,

src/Happstack/Server/Internal/Types.hs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{-# LANGUAGE TypeSynonymInstances, DeriveDataTypeable, FlexibleInstances, RankNTypes #-}
1+
{-# LANGUAGE TypeSynonymInstances, DeriveAnyClass, DeriveDataTypeable, DeriveGeneric, FlexibleInstances, RankNTypes #-}
22

33
module Happstack.Server.Internal.Types
44
(Request(..), Response(..), RqBody(..), Input(..), HeaderPair(..),
@@ -20,6 +20,7 @@ module Happstack.Server.Internal.Types
2020
) where
2121

2222

23+
import Control.DeepSeq (NFData)
2324
import Control.Exception (Exception, SomeException)
2425
import Control.Monad.Error (Error(strMsg))
2526
import Control.Monad.Trans (MonadIO(liftIO))
@@ -40,6 +41,7 @@ import Data.List
4041
import Data.Word (Word, Word8, Word16, Word32, Word64)
4142
import qualified Data.Text as Text
4243
import qualified Data.Text.Lazy as Lazy
44+
import GHC.Generics (Generic)
4345
import Happstack.Server.SURI
4446
import Data.Char (toLower)
4547
import Happstack.Server.Internal.RFC822Headers ( ContentType(..) )
@@ -155,7 +157,7 @@ data HeaderPair = HeaderPair
155157
{ hName :: ByteString -- ^ header name
156158
, hValue :: [ByteString] -- ^ header value (or values if multiple occurances of the header are present)
157159
}
158-
deriving (Read,Show)
160+
deriving (Read,Show,Generic,NFData)
159161

160162
-- | a Map of HTTP headers
161163
--
@@ -171,12 +173,12 @@ data Length
171173
= ContentLength -- ^ automatically add a @Content-Length@ header to the 'Response'
172174
| TransferEncodingChunked -- ^ do not add a @Content-Length@ header. Do use @chunked@ output encoding
173175
| NoContentLength -- ^ do not set @Content-Length@ or @chunked@ output encoding.
174-
deriving (Eq, Ord, Read, Show, Enum)
176+
deriving (Eq, Ord, Read, Show, Enum, Generic, NFData)
175177

176178
-- | Result flags
177179
data RsFlags = RsFlags
178180
{ rsfLength :: Length
179-
} deriving (Show,Read,Typeable)
181+
} deriving (Show,Read,Typeable,Generic,NFData)
180182

181183
-- | Default RsFlags: automatically use @Transfer-Encoding: Chunked@.
182184
nullRsFlags :: RsFlags
@@ -222,7 +224,7 @@ data Response
222224
, sfOffset :: Integer -- ^ offset to start at
223225
, sfCount :: Integer -- ^ number of bytes to send
224226
}
225-
deriving (Typeable)
227+
deriving (Generic, NFData, Typeable)
226228

227229
instance Show Response where
228230
showsPrec _ res@Response{} =

0 commit comments

Comments
 (0)