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
|
||||
|
||||
import Web.Encodings
|
||||
import Data.ByteString.Class
|
||||
import qualified Data.ByteString.Lazy as B
|
||||
import Data.Object
|
||||
|
||||
import qualified Hack
|
||||
import Hack.Middleware.CleanPath
|
||||
@ -59,15 +59,19 @@ class ResourceName a b => RestfulApp a b | a -> b where
|
||||
, 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
|
||||
-- beautifying static HTML. FIXME more user friendly.
|
||||
responseWrapper :: a -> String -> B.ByteString -> IO B.ByteString
|
||||
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
|
||||
-- a Hack application.
|
||||
toHackApp :: RestfulApp resourceName modelType
|
||||
@ -107,19 +111,20 @@ toHackApplication :: RestfulApp resourceName model
|
||||
-> Hack.Application
|
||||
toHackApplication sampleRN hm env = do
|
||||
let (Right resource) = splitPath $ Hack.pathInfo env
|
||||
case findResourceNames resource of
|
||||
[] -> response404 sampleRN $ env
|
||||
[(rn, urlParams')] -> do
|
||||
let verb :: Verb
|
||||
verb = toVerb $ Hack.requestMethod env
|
||||
rr :: RawRequest
|
||||
rr = envToRawRequest urlParams' env
|
||||
handler :: Handler
|
||||
handler = hm rn verb
|
||||
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
|
||||
ctypes' = parseHttpAccept rawHttpAccept
|
||||
runResponse (handler rr) ctypes'
|
||||
x -> error $ "Invalid matches: " ++ show x
|
||||
let (handler, urlParams') =
|
||||
case findResourceNames resource of
|
||||
[] -> (noHandler, [])
|
||||
[(rn, urlParams'')] ->
|
||||
let verb = toVerb $ Hack.requestMethod env
|
||||
in (hm rn verb, urlParams'')
|
||||
x -> error $ "Invalid findResourceNames: " ++ show x
|
||||
let rr = envToRawRequest urlParams' env
|
||||
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
|
||||
ctypes' = parseHttpAccept rawHttpAccept
|
||||
runResponse (errorHandler sampleRN rr)
|
||||
(responseWrapper sampleRN)
|
||||
ctypes'
|
||||
(handler rr)
|
||||
|
||||
envToRawRequest :: [(ParamName, ParamValue)] -> Hack.Env -> RawRequest
|
||||
envToRawRequest urlParams' env =
|
||||
@ -132,10 +137,3 @@ envToRawRequest urlParams' env =
|
||||
rawCookie = tryLookup "" "Cookie" $ Hack.http env
|
||||
cookies' = decodeCookies rawCookie :: [(String, String)]
|
||||
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 TypeSynonymInstances #-}
|
||||
{-# LANGUAGE OverlappingInstances #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
---------------------------------------------------------
|
||||
--
|
||||
-- Module : Web.Restful.Request
|
||||
@ -210,6 +211,9 @@ data RawRequest = RawRequest
|
||||
, rawFiles :: [(ParamName, FileInfo)]
|
||||
, rawEnv :: Hack.Env
|
||||
}
|
||||
deriving Show
|
||||
|
||||
deriving instance Show FileInfo
|
||||
|
||||
-- | All GET paramater values with the given name.
|
||||
getParams :: RawRequest -> ParamName -> [ParamValue]
|
||||
|
||||
@ -29,6 +29,8 @@ module Web.Restful.Response
|
||||
, header
|
||||
, GenResponse (..)
|
||||
, liftIO
|
||||
, ErrorResult (..)
|
||||
, HasRepsW (..)
|
||||
) where
|
||||
|
||||
import Data.ByteString.Class
|
||||
@ -61,47 +63,43 @@ data HasRepsW = forall a. HasReps a => HasRepsW a
|
||||
instance HasReps HasRepsW where
|
||||
reps (HasRepsW r) = reps r
|
||||
|
||||
-- | The result of a request. This does not include possible headers.
|
||||
data Result =
|
||||
data ErrorResult =
|
||||
Redirect String
|
||||
| NotFound
|
||||
| InternalError String
|
||||
| Content HasRepsW
|
||||
|
||||
instance HasReps Result where
|
||||
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 :: ErrorResult -> Int
|
||||
getStatus (Redirect _) = 303
|
||||
getStatus NotFound = 404
|
||||
getStatus (InternalError _) = 500
|
||||
getStatus (Content _) = 200
|
||||
|
||||
getHeaders :: Result -> [Header]
|
||||
getHeaders :: ErrorResult -> [Header]
|
||||
getHeaders (Redirect s) = [Header "Location" s]
|
||||
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 Response = ResponseIO HasRepsW
|
||||
|
||||
runResponse :: Response -> [ContentType] -> IO Hack.Response
|
||||
runResponse (ResponseT inside) ctypesAll = do
|
||||
runResponse :: (ErrorResult -> HasRepsW)
|
||||
-> (ContentType -> B.ByteString -> IO B.ByteString)
|
||||
-> [ContentType]
|
||||
-> Response
|
||||
-> IO Hack.Response
|
||||
runResponse eh wrapper ctypesAll (ResponseT inside) = do
|
||||
(x, headers') <- inside
|
||||
let extraHeaders =
|
||||
case x of
|
||||
Left r -> getHeaders r
|
||||
Right _ -> []
|
||||
headers <- mapM toPair (headers' ++ extraHeaders)
|
||||
let outReps = either reps reps x
|
||||
let outReps = either (reps . eh) reps x
|
||||
let statusCode =
|
||||
case x of
|
||||
Left r -> getStatus r
|
||||
Right _ -> 200
|
||||
(ctype, finalRep) <- chooseRep outReps ctypesAll
|
||||
(ctype, selectedRep) <- chooseRep outReps ctypesAll
|
||||
finalRep <- wrapper ctype selectedRep
|
||||
let headers'' = ("Content-Type", ctype) : headers
|
||||
return $! Hack.Response statusCode headers'' finalRep
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user