Skip to content

Commit e23b4b2

Browse files
committed
Deep strict evaluation of Response
Without this change, Happstack's `failResponse` is not always sent. For instance: main :: IO () main = do let sendResponse = ok $ toResponse (fromJust Nothing :: String) simpleHTTP nullConf sendResponse This server tries to send a string, but can't because the string's value is bottom. Before this change, the server replies with status 200 and empty content, while outputting "HTTP request failed with: Maybe.fromJust: Nothing" to stderr. This is because the response is only evaluated into WHNF inside the exception handler in Handler.hs, and the exception is caught by the surrounding catch block in Listen.hs. After this change, the server replies with status 500 and the `failResponse` page. It's possible that this slows down the Happstack server. Some parts of the Response object may be evaluated but not needed. Strictly evaluating the response at a finer level might be necessary. Additionally, with NFData Response, clients can call `deepseq` themselves on the response object in order to catch any bottom values that it contains, log them and display a custom error page: 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) Let me know if you want to merge in this patch. I'll be using it in my own applications for sure, unless it proves to cause any problems. :)
1 parent 8d41110 commit e23b4b2

File tree

3 files changed

+10
-6
lines changed

3 files changed

+10
-6
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/Handler.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import qualified Paths_happstack_server as Paths
1010
import qualified Data.Version as DV
1111
import Control.Applicative (pure)
1212
import Control.Concurrent (newMVar, newEmptyMVar, tryTakeMVar)
13+
import Control.DeepSeq (deepseq)
1314
import Control.Exception.Extensible as E
1415
import Control.Monad
1516
import Data.List(elemIndex)
@@ -83,7 +84,7 @@ rloop timeoutIO mlog host handler inputStr
8384
let req = Request (toSecure timeoutIO) m (pathEls (path u)) (path u) (query u)
8485
(queryInput u) bodyInputRef cookies v headers bodyRef host
8586

86-
let ioseq act = act >>= \x -> x `seq` return x
87+
let ioseq act = act >>= \x -> x `deepseq` return x
8788

8889
(res, handlerKilled) <- ((, False) `liftM` ioseq (handler req))
8990
`E.catches` [ Handler $ \(e::EscapeHTTP) -> throwIO e -- need to handle this higher up

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)