Response wrapping and error handling done properly
This commit is contained in:
parent
86ca811ac5
commit
c75c72d9cb
@ -23,8 +23,8 @@ module Web.Restful.Application
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Web.Encodings
|
import Web.Encodings
|
||||||
import Data.ByteString.Class
|
|
||||||
import qualified Data.ByteString.Lazy as B
|
import qualified Data.ByteString.Lazy as B
|
||||||
|
import Data.Object
|
||||||
|
|
||||||
import qualified Hack
|
import qualified Hack
|
||||||
import Hack.Middleware.CleanPath
|
import Hack.Middleware.CleanPath
|
||||||
@ -59,15 +59,19 @@ class ResourceName a b => RestfulApp a b | a -> b where
|
|||||||
, methodOverride
|
, methodOverride
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | How to generate 404 pages. FIXME make more user-friendly.
|
|
||||||
response404 :: a -> Hack.Env -> IO Hack.Response
|
|
||||||
response404 _ = default404
|
|
||||||
|
|
||||||
-- | Wrappers for cleaning up responses. Especially intended for
|
-- | Wrappers for cleaning up responses. Especially intended for
|
||||||
-- beautifying static HTML. FIXME more user friendly.
|
-- beautifying static HTML. FIXME more user friendly.
|
||||||
responseWrapper :: a -> String -> B.ByteString -> IO B.ByteString
|
responseWrapper :: a -> String -> B.ByteString -> IO B.ByteString
|
||||||
responseWrapper _ _ = return
|
responseWrapper _ _ = return
|
||||||
|
|
||||||
|
-- | Output error response pages.
|
||||||
|
errorHandler :: a -> RawRequest -> ErrorResult -> HasRepsW
|
||||||
|
errorHandler _ rr NotFound = HasRepsW $ toObject $ "Not found: " ++ show rr
|
||||||
|
errorHandler _ _ (Redirect url) =
|
||||||
|
HasRepsW $ toObject $ "Redirect to: " ++ url
|
||||||
|
errorHandler _ _ (InternalError e) =
|
||||||
|
HasRepsW $ toObject $ "Internal server error: " ++ e
|
||||||
|
|
||||||
-- | Given a sample resource name (purely for typing reasons), generating
|
-- | Given a sample resource name (purely for typing reasons), generating
|
||||||
-- a Hack application.
|
-- a Hack application.
|
||||||
toHackApp :: RestfulApp resourceName modelType
|
toHackApp :: RestfulApp resourceName modelType
|
||||||
@ -107,19 +111,20 @@ toHackApplication :: RestfulApp resourceName model
|
|||||||
-> Hack.Application
|
-> Hack.Application
|
||||||
toHackApplication sampleRN hm env = do
|
toHackApplication sampleRN hm env = do
|
||||||
let (Right resource) = splitPath $ Hack.pathInfo env
|
let (Right resource) = splitPath $ Hack.pathInfo env
|
||||||
case findResourceNames resource of
|
let (handler, urlParams') =
|
||||||
[] -> response404 sampleRN $ env
|
case findResourceNames resource of
|
||||||
[(rn, urlParams')] -> do
|
[] -> (noHandler, [])
|
||||||
let verb :: Verb
|
[(rn, urlParams'')] ->
|
||||||
verb = toVerb $ Hack.requestMethod env
|
let verb = toVerb $ Hack.requestMethod env
|
||||||
rr :: RawRequest
|
in (hm rn verb, urlParams'')
|
||||||
rr = envToRawRequest urlParams' env
|
x -> error $ "Invalid findResourceNames: " ++ show x
|
||||||
handler :: Handler
|
let rr = envToRawRequest urlParams' env
|
||||||
handler = hm rn verb
|
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
|
||||||
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
|
ctypes' = parseHttpAccept rawHttpAccept
|
||||||
ctypes' = parseHttpAccept rawHttpAccept
|
runResponse (errorHandler sampleRN rr)
|
||||||
runResponse (handler rr) ctypes'
|
(responseWrapper sampleRN)
|
||||||
x -> error $ "Invalid matches: " ++ show x
|
ctypes'
|
||||||
|
(handler rr)
|
||||||
|
|
||||||
envToRawRequest :: [(ParamName, ParamValue)] -> Hack.Env -> RawRequest
|
envToRawRequest :: [(ParamName, ParamValue)] -> Hack.Env -> RawRequest
|
||||||
envToRawRequest urlParams' env =
|
envToRawRequest urlParams' env =
|
||||||
@ -132,10 +137,3 @@ envToRawRequest urlParams' env =
|
|||||||
rawCookie = tryLookup "" "Cookie" $ Hack.http env
|
rawCookie = tryLookup "" "Cookie" $ Hack.http env
|
||||||
cookies' = decodeCookies rawCookie :: [(String, String)]
|
cookies' = decodeCookies rawCookie :: [(String, String)]
|
||||||
in RawRequest rawPieces urlParams' gets' posts cookies' files env
|
in RawRequest rawPieces urlParams' gets' posts cookies' files env
|
||||||
|
|
||||||
default404 :: Hack.Env -> IO Hack.Response
|
|
||||||
default404 env = return $
|
|
||||||
Hack.Response
|
|
||||||
404
|
|
||||||
[("Content-Type", "text/plain")]
|
|
||||||
$ toLazyByteString $ "Not found: " ++ Hack.pathInfo env
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE TypeSynonymInstances #-}
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
{-# LANGUAGE OverlappingInstances #-}
|
{-# LANGUAGE OverlappingInstances #-}
|
||||||
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
--
|
--
|
||||||
-- Module : Web.Restful.Request
|
-- Module : Web.Restful.Request
|
||||||
@ -210,6 +211,9 @@ data RawRequest = RawRequest
|
|||||||
, rawFiles :: [(ParamName, FileInfo)]
|
, rawFiles :: [(ParamName, FileInfo)]
|
||||||
, rawEnv :: Hack.Env
|
, rawEnv :: Hack.Env
|
||||||
}
|
}
|
||||||
|
deriving Show
|
||||||
|
|
||||||
|
deriving instance Show FileInfo
|
||||||
|
|
||||||
-- | All GET paramater values with the given name.
|
-- | All GET paramater values with the given name.
|
||||||
getParams :: RawRequest -> ParamName -> [ParamValue]
|
getParams :: RawRequest -> ParamName -> [ParamValue]
|
||||||
|
|||||||
@ -29,6 +29,8 @@ module Web.Restful.Response
|
|||||||
, header
|
, header
|
||||||
, GenResponse (..)
|
, GenResponse (..)
|
||||||
, liftIO
|
, liftIO
|
||||||
|
, ErrorResult (..)
|
||||||
|
, HasRepsW (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.ByteString.Class
|
import Data.ByteString.Class
|
||||||
@ -61,47 +63,43 @@ data HasRepsW = forall a. HasReps a => HasRepsW a
|
|||||||
instance HasReps HasRepsW where
|
instance HasReps HasRepsW where
|
||||||
reps (HasRepsW r) = reps r
|
reps (HasRepsW r) = reps r
|
||||||
|
|
||||||
-- | The result of a request. This does not include possible headers.
|
data ErrorResult =
|
||||||
data Result =
|
|
||||||
Redirect String
|
Redirect String
|
||||||
| NotFound
|
| NotFound
|
||||||
| InternalError String
|
| InternalError String
|
||||||
| Content HasRepsW
|
|
||||||
|
|
||||||
instance HasReps Result where
|
getStatus :: ErrorResult -> Int
|
||||||
reps (Redirect s) = [("text/plain", toLazyByteString s)]
|
|
||||||
reps NotFound = [("text/plain", toLazyByteString "not found")] -- FIXME use the real 404 page
|
|
||||||
reps (InternalError s) = [("text/plain", toLazyByteString s)]
|
|
||||||
reps (Content r) = reps r
|
|
||||||
|
|
||||||
getStatus :: Result -> Int
|
|
||||||
getStatus (Redirect _) = 303
|
getStatus (Redirect _) = 303
|
||||||
getStatus NotFound = 404
|
getStatus NotFound = 404
|
||||||
getStatus (InternalError _) = 500
|
getStatus (InternalError _) = 500
|
||||||
getStatus (Content _) = 200
|
|
||||||
|
|
||||||
getHeaders :: Result -> [Header]
|
getHeaders :: ErrorResult -> [Header]
|
||||||
getHeaders (Redirect s) = [Header "Location" s]
|
getHeaders (Redirect s) = [Header "Location" s]
|
||||||
getHeaders _ = []
|
getHeaders _ = []
|
||||||
|
|
||||||
newtype ResponseT m a = ResponseT (m (Either Result a, [Header]))
|
newtype ResponseT m a = ResponseT (m (Either ErrorResult a, [Header]))
|
||||||
type ResponseIO = ResponseT IO
|
type ResponseIO = ResponseT IO
|
||||||
type Response = ResponseIO HasRepsW
|
type Response = ResponseIO HasRepsW
|
||||||
|
|
||||||
runResponse :: Response -> [ContentType] -> IO Hack.Response
|
runResponse :: (ErrorResult -> HasRepsW)
|
||||||
runResponse (ResponseT inside) ctypesAll = do
|
-> (ContentType -> B.ByteString -> IO B.ByteString)
|
||||||
|
-> [ContentType]
|
||||||
|
-> Response
|
||||||
|
-> IO Hack.Response
|
||||||
|
runResponse eh wrapper ctypesAll (ResponseT inside) = do
|
||||||
(x, headers') <- inside
|
(x, headers') <- inside
|
||||||
let extraHeaders =
|
let extraHeaders =
|
||||||
case x of
|
case x of
|
||||||
Left r -> getHeaders r
|
Left r -> getHeaders r
|
||||||
Right _ -> []
|
Right _ -> []
|
||||||
headers <- mapM toPair (headers' ++ extraHeaders)
|
headers <- mapM toPair (headers' ++ extraHeaders)
|
||||||
let outReps = either reps reps x
|
let outReps = either (reps . eh) reps x
|
||||||
let statusCode =
|
let statusCode =
|
||||||
case x of
|
case x of
|
||||||
Left r -> getStatus r
|
Left r -> getStatus r
|
||||||
Right _ -> 200
|
Right _ -> 200
|
||||||
(ctype, finalRep) <- chooseRep outReps ctypesAll
|
(ctype, selectedRep) <- chooseRep outReps ctypesAll
|
||||||
|
finalRep <- wrapper ctype selectedRep
|
||||||
let headers'' = ("Content-Type", ctype) : headers
|
let headers'' = ("Content-Type", ctype) : headers
|
||||||
return $! Hack.Response statusCode headers'' finalRep
|
return $! Hack.Response statusCode headers'' finalRep
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user