DontFullyEvaluate (Felipe's code)

This commit is contained in:
Michael Snoyman 2012-07-25 08:54:43 +03:00
parent f91ff4fde2
commit c009067b11
4 changed files with 34 additions and 36 deletions

View File

@ -28,6 +28,8 @@ module Yesod.Content
, typeOctet , typeOctet
-- * Utilities -- * Utilities
, simpleContentType , simpleContentType
-- * Evaluation strategy
, DontFullyEvaluate (..)
-- * Representations -- * Representations
, ChooseRep , ChooseRep
, HasReps (..) , HasReps (..)
@ -68,6 +70,7 @@ import Data.Conduit (Source, ResourceT, Flush)
data Content = ContentBuilder Builder (Maybe Int) -- ^ The content and optional content length. data Content = ContentBuilder Builder (Maybe Int) -- ^ The content and optional content length.
| ContentSource (Source (ResourceT IO) (Flush Builder)) | ContentSource (Source (ResourceT IO) (Flush Builder))
| ContentFile FilePath (Maybe FilePart) | ContentFile FilePath (Maybe FilePart)
| ContentDontEvaluate Content
-- | Zero-length enumerator. -- | Zero-length enumerator.
emptyContent :: Content emptyContent :: Content
@ -235,3 +238,15 @@ formatRFC1123 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %X %Z"
-- | Format as per RFC 822. -- | Format as per RFC 822.
formatRFC822 :: UTCTime -> T.Text formatRFC822 :: UTCTime -> T.Text
formatRFC822 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %z" formatRFC822 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %z"
-- | Prevents a response body from being fully evaluated before sending the
-- request.
--
-- Since 1.1.0
newtype DontFullyEvaluate a = DontFullyEvaluate a
instance HasReps a => HasReps (DontFullyEvaluate a) where
chooseRep (DontFullyEvaluate a) = fmap (fmap (fmap ContentDontEvaluate)) $ chooseRep a
instance ToContent a => ToContent (DontFullyEvaluate a) where
toContent (DontFullyEvaluate a) = ContentDontEvaluate $ toContent a

View File

@ -395,9 +395,8 @@ runHandler :: HasReps c
-> sub -> sub
-> (Word64 -> FileUpload) -> (Word64 -> FileUpload)
-> (Loc -> LogLevel -> LogStr -> IO ()) -> (Loc -> LogLevel -> LogStr -> IO ())
-> Bool -- ^ to eval body?
-> YesodApp -> YesodApp
runHandler handler mrender sroute tomr master sub upload log' toEval = runHandler handler mrender sroute tomr master sub upload log' =
YesodApp $ \eh rr cts initSession -> do YesodApp $ \eh rr cts initSession -> do
let toErrorHandler e = let toErrorHandler e =
case fromException e of case fromException e of
@ -440,7 +439,7 @@ runHandler handler mrender sroute tomr master sub upload log' toEval =
case contents of case contents of
HCContent status a -> do HCContent status a -> do
(ct, c) <- liftIO $ a cts (ct, c) <- liftIO $ a cts
ec' <- if toEval then liftIO $ evaluateContent c else return (Right c) ec' <- liftIO $ evaluateContent c
case ec' of case ec' of
Left e -> handleError e Left e -> handleError e
Right c' -> return $ YARPlain status (appEndo headers []) ct c' finalSession Right c' -> return $ YARPlain status (appEndo headers []) ct c' finalSession
@ -806,7 +805,6 @@ handlerToYAR :: (HasReps a, HasReps b)
-> sub -- ^ sub site foundation -> sub -- ^ sub site foundation
-> (Word64 -> FileUpload) -> (Word64 -> FileUpload)
-> (Loc -> LogLevel -> LogStr -> IO ()) -> (Loc -> LogLevel -> LogStr -> IO ())
-> Bool -- ^ to eval body?
-> (Route sub -> Route master) -> (Route sub -> Route master)
-> (Route master -> [(Text, Text)] -> Text) -- route renderer -> (Route master -> [(Text, Text)] -> Text) -- route renderer
-> (ErrorResponse -> GHandler sub master a) -> (ErrorResponse -> GHandler sub master a)
@ -815,28 +813,31 @@ handlerToYAR :: (HasReps a, HasReps b)
-> SessionMap -> SessionMap
-> GHandler sub master b -> GHandler sub master b
-> ResourceT IO YesodAppResult -> ResourceT IO YesodAppResult
handlerToYAR y s upload log' toEval toMasterRoute render errorHandler rr murl sessionMap h = handlerToYAR y s upload log' toMasterRoute render errorHandler rr murl sessionMap h =
unYesodApp ya eh' rr types sessionMap unYesodApp ya eh' rr types sessionMap
where where
ya = runHandler h render murl toMasterRoute y s upload log' toEval ya = runHandler h render murl toMasterRoute y s upload log'
eh' er = runHandler (errorHandler' er) render murl toMasterRoute y s upload log' toEval eh' er = runHandler (errorHandler' er) render murl toMasterRoute y s upload log'
types = httpAccept $ reqWaiRequest rr types = httpAccept $ reqWaiRequest rr
errorHandler' = localNoCurrent . errorHandler errorHandler' = localNoCurrent . errorHandler
yarToResponse :: YesodAppResult -> [(CI ByteString, ByteString)] -> W.Response yarToResponse :: YesodAppResult -> [(CI ByteString, ByteString)] -> W.Response
yarToResponse (YARWai a) _ = a yarToResponse (YARWai a) _ = a
yarToResponse (YARPlain s hs _ c _) extraHeaders = yarToResponse (YARPlain s hs _ c _) extraHeaders =
case c of go c
ContentBuilder b mlen ->
let hs' = maybe finalHeaders finalHeaders' mlen
in W.ResponseBuilder s hs' b
ContentFile fp p -> W.ResponseFile s finalHeaders fp p
ContentSource body -> W.ResponseSource s finalHeaders body
where where
finalHeaders = extraHeaders ++ map headerToPair hs finalHeaders = extraHeaders ++ map headerToPair hs
finalHeaders' len = ("Content-Length", S8.pack $ show len) finalHeaders' len = ("Content-Length", S8.pack $ show len)
: finalHeaders : finalHeaders
go (ContentBuilder b mlen) =
W.ResponseBuilder s hs' b
where
hs' = maybe finalHeaders finalHeaders' mlen
go (ContentFile fp p) = W.ResponseFile s finalHeaders fp p
go (ContentSource body) = W.ResponseSource s finalHeaders body
go (ContentDontEvaluate c') = go c'
httpAccept :: W.Request -> [ContentType] httpAccept :: W.Request -> [ContentType]
httpAccept = parseHttpAccept httpAccept = parseHttpAccept
. fromMaybe mempty . fromMaybe mempty

View File

@ -343,23 +343,6 @@ $doctype 5
| size > 50000 = FileUploadDisk tempFileBackEnd | size > 50000 = FileUploadDisk tempFileBackEnd
| otherwise = FileUploadMemory lbsBackEnd | otherwise = FileUploadMemory lbsBackEnd
-- | Whether or not to fully evaluate response bodies before sending.
--
-- By fully evaluating, you will be forcing the contents into memory, which
-- will negatively impact performance. However, it means that if any
-- exceptions are thrown from pure code, they will be caught before sending
-- the response to the client, resulting in a proper 500 error page instead
-- of just getting an empty response.
--
-- In general, it's recommend to leave the default value in place. However,
-- if you have a route that generates large responses, and you are
-- confident that no exceptions are thrown from pure code, you can safely
-- turn this off for that route.
--
-- Default: On for all routes.
fullyEvaluateBody :: a -> Route a -> Bool
fullyEvaluateBody _ _ = True
formatLogMessage :: IO ZonedDate formatLogMessage :: IO ZonedDate
-> Loc -> Loc
-> LogLevel -> LogLevel
@ -432,8 +415,7 @@ defaultYesodRunner logger handler master sub murl toMasterRoute msb req
let sessionMap = Map.fromList . filter ((/=) tokenKey . fst) $ session let sessionMap = Map.fromList . filter ((/=) tokenKey . fst) $ session
let ra = resolveApproot master req let ra = resolveApproot master req
let log' = messageLogger master logger let log' = messageLogger master logger
toEval = maybe True (fullyEvaluateBody master) (fmap toMasterRoute murl) yar <- handlerToYAR master sub (fileUpload master) log' toMasterRoute
yar <- handlerToYAR master sub (fileUpload master) log' toEval toMasterRoute
(yesodRender master ra) errorHandler rr murl sessionMap h (yesodRender master ra) errorHandler rr murl sessionMap h
extraHeaders <- case yar of extraHeaders <- case yar of
(YARPlain _ _ ct _ newSess) -> do (YARPlain _ _ ct _ newSess) -> do

View File

@ -25,9 +25,7 @@ mkYesod "App" [parseRoutes|
/error-in-body-noeval ErrorInBodyNoEvalR GET /error-in-body-noeval ErrorInBodyNoEvalR GET
|] |]
instance Yesod App where instance Yesod App
fullyEvaluateBody _ ErrorInBodyNoEvalR = False
fullyEvaluateBody _ _ = True
getHomeR :: Handler RepHtml getHomeR :: Handler RepHtml
getHomeR = do getHomeR = do
@ -60,11 +58,13 @@ postAfterRunRequestBodyR = do
_ <- error $ show $ fst x _ <- error $ show $ fst x
getHomeR getHomeR
getErrorInBodyR :: Handler RepHtml
getErrorInBodyR = do getErrorInBodyR = do
let foo = error "error in body 19328" :: String let foo = error "error in body 19328" :: String
defaultLayout [whamlet|#{foo}|] defaultLayout [whamlet|#{foo}|]
getErrorInBodyNoEvalR = getErrorInBodyR getErrorInBodyNoEvalR :: Handler (DontFullyEvaluate RepHtml)
getErrorInBodyNoEvalR = fmap DontFullyEvaluate getErrorInBodyR
errorHandlingTest :: Spec errorHandlingTest :: Spec
errorHandlingTest = describe "Test.ErrorHandling" errorHandlingTest = describe "Test.ErrorHandling"