fullyEvaluateBody
This commit is contained in:
parent
6c834ec0cc
commit
f91ff4fde2
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user