Separate ErrorResponse and SpecialResponse; added SendFile
This commit is contained in:
parent
405fb3ac25
commit
309757c22d
@ -50,6 +50,7 @@ import Control.Monad (liftM, ap)
|
|||||||
|
|
||||||
import System.IO
|
import System.IO
|
||||||
import Data.Object.Html
|
import Data.Object.Html
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
|
||||||
import Yesod.Parameter
|
import Yesod.Parameter
|
||||||
|
|
||||||
@ -59,8 +60,8 @@ newtype Handler yesod a = Handler {
|
|||||||
-> IO ([Header], HandlerContents a)
|
-> IO ([Header], HandlerContents a)
|
||||||
}
|
}
|
||||||
data HandlerContents a =
|
data HandlerContents a =
|
||||||
forall e. Exception e => HCError e
|
HCSpecial SpecialResponse
|
||||||
| HCSpecial ErrorResult
|
| HCError ErrorResponse
|
||||||
| HCContent a
|
| HCContent a
|
||||||
|
|
||||||
instance Functor (Handler yesod) where
|
instance Functor (Handler yesod) where
|
||||||
@ -82,7 +83,7 @@ instance Monad (Handler yesod) where
|
|||||||
instance MonadIO (Handler yesod) where
|
instance MonadIO (Handler yesod) where
|
||||||
liftIO i = Handler $ \_ -> i >>= \i' -> return ([], HCContent i')
|
liftIO i = Handler $ \_ -> i >>= \i' -> return ([], HCContent i')
|
||||||
instance Exception e => Failure e (Handler yesod) where
|
instance Exception e => Failure e (Handler yesod) where
|
||||||
failure e = Handler $ \_ -> return ([], HCError e)
|
failure e = Handler $ \_ -> return ([], HCError $ InternalError $ show e)
|
||||||
instance RequestReader (Handler yesod) where
|
instance RequestReader (Handler yesod) where
|
||||||
getRawRequest = Handler $ \(rr, _, _) -> return ([], HCContent rr)
|
getRawRequest = Handler $ \(rr, _, _) -> return ([], HCContent rr)
|
||||||
invalidParams = invalidArgs . map helper where
|
invalidParams = invalidArgs . map helper where
|
||||||
@ -95,7 +96,7 @@ instance HasTemplateGroup (Handler yesod) where
|
|||||||
getTemplateGroup = Handler $ \(_, _, tg) -> return ([], HCContent tg)
|
getTemplateGroup = Handler $ \(_, _, tg) -> return ([], HCContent tg)
|
||||||
|
|
||||||
runHandler :: Handler yesod RepChooser
|
runHandler :: Handler yesod RepChooser
|
||||||
-> (ErrorResult -> Handler yesod RepChooser)
|
-> (ErrorResponse -> Handler yesod RepChooser)
|
||||||
-> RawRequest
|
-> RawRequest
|
||||||
-> yesod
|
-> yesod
|
||||||
-> TemplateGroup
|
-> TemplateGroup
|
||||||
@ -104,43 +105,50 @@ runHandler :: Handler yesod RepChooser
|
|||||||
runHandler (Handler handler) eh rr y tg cts = do
|
runHandler (Handler handler) eh rr y tg cts = do
|
||||||
(headers, contents) <- Control.Exception.catch
|
(headers, contents) <- Control.Exception.catch
|
||||||
(handler (rr, y, tg))
|
(handler (rr, y, tg))
|
||||||
(\e -> return ([], HCError (e :: Control.Exception.SomeException)))
|
(\e -> return ([], HCError $ InternalError $ show
|
||||||
let contents' =
|
(e :: Control.Exception.SomeException)))
|
||||||
case contents of
|
case contents of
|
||||||
HCError e -> Left $ InternalError $ show e
|
HCError e -> do
|
||||||
HCSpecial e -> Left e
|
Response _ hs ct c <- runHandler (eh e) safeEh rr y tg cts
|
||||||
HCContent a -> Right a
|
let hs' = headers ++ hs
|
||||||
case contents' of
|
|
||||||
Left e -> do
|
|
||||||
Response _ hs ct c <- runHandler (eh e) specialEh rr y tg cts
|
|
||||||
let hs' = headers ++ hs ++ getHeaders e
|
|
||||||
return $ Response (getStatus e) hs' ct c
|
return $ Response (getStatus e) hs' ct c
|
||||||
Right a -> do
|
HCSpecial (Redirect rt loc) -> do
|
||||||
|
let hs = Header "Location" loc : headers
|
||||||
|
return $ Response (getRedirectStatus rt) hs TypePlain $ cs ""
|
||||||
|
HCSpecial (SendFile ct fp) -> do
|
||||||
|
-- FIXME do error handling on this, or leave it to the app?
|
||||||
|
-- FIXME avoid lazy I/O by switching to WAI
|
||||||
|
c <- BL.readFile fp
|
||||||
|
return $ Response 200 headers ct $ Content c
|
||||||
|
HCContent a -> do
|
||||||
(ct, c) <- a cts
|
(ct, c) <- a cts
|
||||||
return $ Response 200 headers ct c
|
return $ Response 200 headers ct c
|
||||||
|
|
||||||
specialEh :: ErrorResult -> Handler yesod RepChooser
|
safeEh :: ErrorResponse -> Handler yesod RepChooser
|
||||||
specialEh er = do
|
safeEh er = do
|
||||||
liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
|
liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
|
||||||
return $ chooseRep $ toHtmlObject "Internal server error"
|
return $ chooseRep $ toHtmlObject "Internal server error"
|
||||||
|
|
||||||
------ Special handlers
|
------ Special handlers
|
||||||
errorResult :: ErrorResult -> Handler yesod a
|
specialResponse :: SpecialResponse -> Handler yesod a
|
||||||
errorResult er = Handler $ \_ -> return ([], HCSpecial er)
|
specialResponse er = Handler $ \_ -> return ([], HCSpecial er)
|
||||||
|
|
||||||
|
errorResponse :: ErrorResponse -> Handler yesod a
|
||||||
|
errorResponse er = Handler $ \_ -> return ([], HCError er)
|
||||||
|
|
||||||
-- | Redirect to the given URL.
|
-- | Redirect to the given URL.
|
||||||
redirect :: String -> Handler yesod a
|
redirect :: RedirectType -> String -> Handler yesod a
|
||||||
redirect = errorResult . Redirect
|
redirect rt = specialResponse . Redirect rt
|
||||||
|
|
||||||
-- | Return a 404 not found page. Also denotes no handler available.
|
-- | Return a 404 not found page. Also denotes no handler available.
|
||||||
notFound :: Handler yesod a
|
notFound :: Handler yesod a
|
||||||
notFound = errorResult NotFound
|
notFound = errorResponse NotFound
|
||||||
|
|
||||||
permissionDenied :: Handler yesod a
|
permissionDenied :: Handler yesod a
|
||||||
permissionDenied = errorResult PermissionDenied
|
permissionDenied = errorResponse PermissionDenied
|
||||||
|
|
||||||
invalidArgs :: [(ParamName, ParamValue)] -> Handler yesod a
|
invalidArgs :: [(ParamName, ParamValue)] -> Handler yesod a
|
||||||
invalidArgs = errorResult . InvalidArgs
|
invalidArgs = errorResponse . InvalidArgs
|
||||||
|
|
||||||
------- Headers
|
------- Headers
|
||||||
-- | Set the cookie on the client.
|
-- | Set the cookie on the client.
|
||||||
|
|||||||
@ -109,8 +109,9 @@ authOpenidForward = do
|
|||||||
let complete = authroot ++ "/openid/complete/"
|
let complete = authroot ++ "/openid/complete/"
|
||||||
res <- runAttemptT $ OpenId.getForwardUrl oid complete
|
res <- runAttemptT $ OpenId.getForwardUrl oid complete
|
||||||
attempt
|
attempt
|
||||||
(\err -> redirect $ "/auth/openid/?message=" ++ encodeUrl (show err))
|
(\err -> redirect RedirectTemporary
|
||||||
redirect
|
$ "/auth/openid/?message=" ++ encodeUrl (show err))
|
||||||
|
(redirect RedirectTemporary)
|
||||||
res
|
res
|
||||||
|
|
||||||
authOpenidComplete :: Handler y HtmlObject
|
authOpenidComplete :: Handler y HtmlObject
|
||||||
@ -118,12 +119,13 @@ authOpenidComplete = do
|
|||||||
gets' <- rawGetParams <$> getRawRequest
|
gets' <- rawGetParams <$> getRawRequest
|
||||||
dest <- runRequest $ cookieParam "DEST"
|
dest <- runRequest $ cookieParam "DEST"
|
||||||
res <- runAttemptT $ OpenId.authenticate gets'
|
res <- runAttemptT $ OpenId.authenticate gets'
|
||||||
let onFailure err = redirect $ "/auth/openid/?message="
|
let onFailure err = redirect RedirectTemporary
|
||||||
|
$ "/auth/openid/?message="
|
||||||
++ encodeUrl (show err)
|
++ encodeUrl (show err)
|
||||||
let onSuccess (OpenId.Identifier ident) = do
|
let onSuccess (OpenId.Identifier ident) = do
|
||||||
deleteCookie "DEST"
|
deleteCookie "DEST"
|
||||||
header authCookieName ident
|
header authCookieName ident
|
||||||
redirect $ fromMaybe "/" dest
|
redirect RedirectTemporary $ fromMaybe "/" dest
|
||||||
attempt onFailure onSuccess res
|
attempt onFailure onSuccess res
|
||||||
|
|
||||||
rpxnowLogin :: YesodAuth y => Handler y HtmlObject
|
rpxnowLogin :: YesodAuth y => Handler y HtmlObject
|
||||||
@ -146,7 +148,7 @@ rpxnowLogin = do
|
|||||||
ident <- Rpxnow.authenticate apiKey token
|
ident <- Rpxnow.authenticate apiKey token
|
||||||
header authCookieName $ Rpxnow.identifier ident
|
header authCookieName $ Rpxnow.identifier ident
|
||||||
header authDisplayName $ getDisplayName ident
|
header authDisplayName $ getDisplayName ident
|
||||||
redirect dest
|
redirect RedirectTemporary dest
|
||||||
|
|
||||||
-- | Get some form of a display name, defaulting to the identifier.
|
-- | Get some form of a display name, defaulting to the identifier.
|
||||||
getDisplayName :: Rpxnow.Identifier -> String
|
getDisplayName :: Rpxnow.Identifier -> String
|
||||||
@ -170,7 +172,7 @@ authLogout :: YesodAuth y => Handler y HtmlObject
|
|||||||
authLogout = do
|
authLogout = do
|
||||||
deleteCookie authCookieName
|
deleteCookie authCookieName
|
||||||
ar <- getApproot
|
ar <- getApproot
|
||||||
redirect ar
|
redirect RedirectTemporary ar
|
||||||
-- FIXME check the DEST information
|
-- FIXME check the DEST information
|
||||||
|
|
||||||
authIdentifier :: YesodAuth y => Handler y String
|
authIdentifier :: YesodAuth y => Handler y String
|
||||||
@ -183,5 +185,5 @@ authIdentifier = do
|
|||||||
let dest = ar ++ rp
|
let dest = ar ++ rp
|
||||||
lp <- defaultLoginPath `fmap` getYesod
|
lp <- defaultLoginPath `fmap` getYesod
|
||||||
addCookie 120 "DEST" dest
|
addCookie 120 "DEST" dest
|
||||||
redirect $ ar ++ lp
|
redirect RedirectTemporary $ ar ++ lp
|
||||||
Just x -> return x
|
Just x -> return x
|
||||||
|
|||||||
@ -19,13 +19,16 @@
|
|||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
module Yesod.Response
|
module Yesod.Response
|
||||||
( Response (..)
|
( Response (..)
|
||||||
-- * Abnormal responses
|
-- * Special responses
|
||||||
, ErrorResult (..)
|
, RedirectType (..)
|
||||||
, getHeaders
|
, getRedirectStatus
|
||||||
|
, SpecialResponse (..)
|
||||||
|
-- * Error responses
|
||||||
|
, ErrorResponse (..)
|
||||||
, getStatus
|
, getStatus
|
||||||
-- * Header
|
-- * Header
|
||||||
, Header (..)
|
, Header (..)
|
||||||
, toPair
|
, headerToPair
|
||||||
-- * Converting to Hack values
|
-- * Converting to Hack values
|
||||||
, responseToHackResponse
|
, responseToHackResponse
|
||||||
#if TEST
|
#if TEST
|
||||||
@ -49,35 +52,45 @@ import qualified Hack
|
|||||||
import Test.Framework (testGroup, Test)
|
import Test.Framework (testGroup, Test)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
import Data.Generics
|
|
||||||
import Control.Exception (Exception)
|
|
||||||
import Data.Convertible.Text (cs)
|
import Data.Convertible.Text (cs)
|
||||||
import Web.Mime
|
import Web.Mime
|
||||||
|
|
||||||
data Response = Response Int [Header] ContentType Content
|
data Response = Response Int [Header] ContentType Content
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
-- | Abnormal return codes.
|
-- | Different types of redirects.
|
||||||
data ErrorResult =
|
data RedirectType = RedirectPermanent
|
||||||
Redirect String
|
| RedirectTemporary
|
||||||
| NotFound
|
| RedirectSeeOther
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
getRedirectStatus :: RedirectType -> Int
|
||||||
|
getRedirectStatus RedirectPermanent = 301
|
||||||
|
getRedirectStatus RedirectTemporary = 302
|
||||||
|
getRedirectStatus RedirectSeeOther = 303
|
||||||
|
|
||||||
|
-- | Special types of responses which should short-circuit normal response
|
||||||
|
-- processing.
|
||||||
|
data SpecialResponse =
|
||||||
|
Redirect RedirectType String
|
||||||
|
| SendFile ContentType FilePath
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- | Responses to indicate some form of an error occurred. These are different
|
||||||
|
-- from 'SpecialResponse' in that they allow for custom error pages.
|
||||||
|
data ErrorResponse =
|
||||||
|
NotFound
|
||||||
| InternalError String
|
| InternalError String
|
||||||
| InvalidArgs [(String, String)]
|
| InvalidArgs [(String, String)]
|
||||||
| PermissionDenied
|
| PermissionDenied
|
||||||
deriving (Show, Typeable)
|
deriving (Show, Eq)
|
||||||
instance Exception ErrorResult
|
|
||||||
|
|
||||||
getStatus :: ErrorResult -> Int
|
getStatus :: ErrorResponse -> Int
|
||||||
getStatus (Redirect _) = 303
|
|
||||||
getStatus NotFound = 404
|
getStatus NotFound = 404
|
||||||
getStatus (InternalError _) = 500
|
getStatus (InternalError _) = 500
|
||||||
getStatus (InvalidArgs _) = 400
|
getStatus (InvalidArgs _) = 400
|
||||||
getStatus PermissionDenied = 403
|
getStatus PermissionDenied = 403
|
||||||
|
|
||||||
getHeaders :: ErrorResult -> [Header]
|
|
||||||
getHeaders (Redirect s) = [Header "Location" s]
|
|
||||||
getHeaders _ = []
|
|
||||||
|
|
||||||
----- header stuff
|
----- header stuff
|
||||||
-- | Headers to be added to a 'Result'.
|
-- | Headers to be added to a 'Result'.
|
||||||
data Header =
|
data Header =
|
||||||
@ -87,21 +100,21 @@ data Header =
|
|||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
-- | Convert Header to a key/value pair.
|
-- | Convert Header to a key/value pair.
|
||||||
toPair :: Header -> IO (String, String)
|
headerToPair :: Header -> IO (String, String)
|
||||||
toPair (AddCookie minutes key value) = do
|
headerToPair (AddCookie minutes key value) = do
|
||||||
now <- getCurrentTime
|
now <- getCurrentTime
|
||||||
let expires = addUTCTime (fromIntegral $ minutes * 60) now
|
let expires = addUTCTime (fromIntegral $ minutes * 60) now
|
||||||
return ("Set-Cookie", key ++ "=" ++ value ++"; path=/; expires="
|
return ("Set-Cookie", key ++ "=" ++ value ++"; path=/; expires="
|
||||||
++ formatW3 expires)
|
++ formatW3 expires)
|
||||||
toPair (DeleteCookie key) = return
|
headerToPair (DeleteCookie key) = return
|
||||||
("Set-Cookie",
|
("Set-Cookie",
|
||||||
key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
|
key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
|
||||||
toPair (Header key value) = return (key, value)
|
headerToPair (Header key value) = return (key, value)
|
||||||
|
|
||||||
responseToHackResponse :: [String] -- ^ language list
|
responseToHackResponse :: [String] -- ^ language list
|
||||||
-> Response -> IO Hack.Response
|
-> Response -> IO Hack.Response
|
||||||
responseToHackResponse _FIXMEls (Response sc hs ct c) = do
|
responseToHackResponse _FIXMEls (Response sc hs ct c) = do
|
||||||
hs' <- mapM toPair hs
|
hs' <- mapM headerToPair hs
|
||||||
let hs'' = ("Content-Type", cs ct) : hs'
|
let hs'' = ("Content-Type", cs ct) : hs'
|
||||||
let asLBS = unContent c
|
let asLBS = unContent c
|
||||||
return $ Hack.Response sc hs'' asLBS
|
return $ Hack.Response sc hs'' asLBS
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class Yesod a where
|
|||||||
clientSessionDuration = const 120
|
clientSessionDuration = const 120
|
||||||
|
|
||||||
-- | Output error response pages.
|
-- | Output error response pages.
|
||||||
errorHandler :: ErrorResult -> Handler a RepChooser
|
errorHandler :: ErrorResponse -> Handler a RepChooser
|
||||||
errorHandler = defaultErrorHandler
|
errorHandler = defaultErrorHandler
|
||||||
|
|
||||||
-- | The template directory. Blank means no templates.
|
-- | The template directory. Blank means no templates.
|
||||||
@ -58,13 +58,11 @@ getApproot :: YesodApproot y => Handler y Approot
|
|||||||
getApproot = approot `fmap` getYesod
|
getApproot = approot `fmap` getYesod
|
||||||
|
|
||||||
defaultErrorHandler :: Yesod y
|
defaultErrorHandler :: Yesod y
|
||||||
=> ErrorResult
|
=> ErrorResponse
|
||||||
-> Handler y RepChooser
|
-> Handler y RepChooser
|
||||||
defaultErrorHandler NotFound = do
|
defaultErrorHandler NotFound = do
|
||||||
rr <- getRawRequest
|
rr <- getRawRequest
|
||||||
return $ chooseRep $ toHtmlObject $ "Not found: " ++ show rr
|
return $ chooseRep $ toHtmlObject $ "Not found: " ++ show rr
|
||||||
defaultErrorHandler (Redirect url) =
|
|
||||||
return $ chooseRep $ toHtmlObject $ "Redirect to: " ++ url
|
|
||||||
defaultErrorHandler PermissionDenied =
|
defaultErrorHandler PermissionDenied =
|
||||||
return $ chooseRep $ toHtmlObject "Permission denied"
|
return $ chooseRep $ toHtmlObject "Permission denied"
|
||||||
defaultErrorHandler (InvalidArgs ia) =
|
defaultErrorHandler (InvalidArgs ia) =
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user