fullyEvaluateBody

This commit is contained in:
Michael Snoyman 2012-07-24 17:49:17 +03:00
parent 6c834ec0cc
commit f91ff4fde2
3 changed files with 70 additions and 8 deletions

View File

@ -146,6 +146,7 @@ import qualified Data.Text.Lazy as TL
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.ByteString as S import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Network.Wai.Parse (parseHttpAccept) import Network.Wai.Parse (parseHttpAccept)
import Yesod.Content import Yesod.Content
@ -158,7 +159,7 @@ import qualified Data.ByteString.Char8 as S8
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.CaseInsensitive (CI) import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import Blaze.ByteString.Builder (toByteString) import Blaze.ByteString.Builder (toByteString, toLazyByteString, fromLazyByteString)
import Data.Text (Text) import Data.Text (Text)
import Yesod.Message (RenderMessage (..)) import Yesod.Message (RenderMessage (..))
@ -394,8 +395,9 @@ 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' = runHandler handler mrender sroute tomr master sub upload log' toEval =
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
@ -438,7 +440,10 @@ runHandler handler mrender sroute tomr master sub upload log' =
case contents of case contents of
HCContent status a -> do HCContent status a -> do
(ct, c) <- liftIO $ a cts (ct, c) <- liftIO $ a cts
return $ YARPlain status (appEndo headers []) ct c finalSession ec' <- if toEval then liftIO $ evaluateContent c else return (Right c)
case ec' of
Left e -> handleError e
Right c' -> return $ YARPlain status (appEndo headers []) ct c' finalSession
HCError e -> handleError e HCError e -> handleError e
HCRedirect status loc -> do HCRedirect status loc -> do
let hs = Header "Location" (encodeUtf8 loc) : appEndo headers [] let hs = Header "Location" (encodeUtf8 loc) : appEndo headers []
@ -458,6 +463,15 @@ runHandler handler mrender sroute tomr master sub upload log' =
finalSession finalSession
HCWai r -> return $ YARWai r HCWai r -> return $ YARWai r
evaluateContent :: Content -> IO (Either ErrorResponse Content)
evaluateContent (ContentBuilder b mlen) = Control.Exception.handle f $ do
let lbs = toLazyByteString b
L.length lbs `seq` return (Right $ ContentBuilder (fromLazyByteString lbs) mlen)
where
f :: SomeException -> IO (Either ErrorResponse Content)
f = return . Left . InternalError . T.pack . show
evaluateContent c = return (Right c)
safeEh :: ErrorResponse -> YesodApp safeEh :: ErrorResponse -> YesodApp
safeEh er = YesodApp $ \_ _ _ session -> do safeEh er = YesodApp $ \_ _ _ session -> do
liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
@ -792,6 +806,7 @@ 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)
@ -800,11 +815,11 @@ 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' toMasterRoute render errorHandler rr murl sessionMap h = handlerToYAR y s upload log' toEval 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' ya = runHandler h render murl toMasterRoute y s upload log' toEval
eh' er = runHandler (errorHandler' er) render murl toMasterRoute y s upload log' eh' er = runHandler (errorHandler' er) render murl toMasterRoute y s upload log' toEval
types = httpAccept $ reqWaiRequest rr types = httpAccept $ reqWaiRequest rr
errorHandler' = localNoCurrent . errorHandler errorHandler' = localNoCurrent . errorHandler

View File

@ -343,6 +343,23 @@ $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
@ -415,7 +432,8 @@ 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
yar <- handlerToYAR master sub (fileUpload master) log' toMasterRoute toEval = maybe True (fullyEvaluateBody master) (fmap toMasterRoute murl)
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

@ -1,4 +1,5 @@
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-} {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module YesodCoreTest.ErrorHandling module YesodCoreTest.ErrorHandling
( errorHandlingTest ( errorHandlingTest
, Widget , Widget
@ -11,6 +12,7 @@ import Network.Wai.Test
import Text.Hamlet (hamlet) import Text.Hamlet (hamlet)
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Char8 as S8
import Control.Exception (SomeException, try)
data App = App data App = App
@ -19,9 +21,13 @@ mkYesod "App" [parseRoutes|
/not_found NotFoundR POST /not_found NotFoundR POST
/first_thing FirstThingR POST /first_thing FirstThingR POST
/after_runRequestBody AfterRunRequestBodyR POST /after_runRequestBody AfterRunRequestBodyR POST
/error-in-body ErrorInBodyR GET
/error-in-body-noeval ErrorInBodyNoEvalR GET
|] |]
instance Yesod App instance Yesod App where
fullyEvaluateBody _ ErrorInBodyNoEvalR = False
fullyEvaluateBody _ _ = True
getHomeR :: Handler RepHtml getHomeR :: Handler RepHtml
getHomeR = do getHomeR = do
@ -54,11 +60,19 @@ postAfterRunRequestBodyR = do
_ <- error $ show $ fst x _ <- error $ show $ fst x
getHomeR getHomeR
getErrorInBodyR = do
let foo = error "error in body 19328" :: String
defaultLayout [whamlet|#{foo}|]
getErrorInBodyNoEvalR = getErrorInBodyR
errorHandlingTest :: Spec errorHandlingTest :: Spec
errorHandlingTest = describe "Test.ErrorHandling" errorHandlingTest = describe "Test.ErrorHandling"
[ it "says not found" caseNotFound [ it "says not found" caseNotFound
, it "says 'There was an error' before runRequestBody" caseBefore , it "says 'There was an error' before runRequestBody" caseBefore
, it "says 'There was an error' after runRequestBody" caseAfter , it "says 'There was an error' after runRequestBody" caseAfter
, it "error in body == 500" caseErrorInBody
, it "error in body, no eval == 200" caseErrorInBodyNoEval
] ]
runner :: Session () -> IO () runner :: Session () -> IO ()
@ -98,3 +112,18 @@ caseAfter = runner $ do
} }
assertStatus 500 res assertStatus 500 res
assertBodyContains "bin12345" res assertBodyContains "bin12345" res
caseErrorInBody :: IO ()
caseErrorInBody = runner $ do
res <- request defaultRequest { pathInfo = ["error-in-body"] }
assertStatus 500 res
assertBodyContains "error in body 19328" res
caseErrorInBodyNoEval :: IO ()
caseErrorInBodyNoEval = do
eres <- try $ runner $ do
_ <- request defaultRequest { pathInfo = ["error-in-body-noeval"] }
return ()
case eres of
Left (_ :: SomeException) -> return ()
Right _ -> error "Expected an exception"