Response wrapping and error handling done properly

This commit is contained in:
Michael Snoyman 2009-09-18 09:36:47 +03:00
parent 86ca811ac5
commit c75c72d9cb
3 changed files with 42 additions and 42 deletions

View File

@ -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

View File

@ -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]

View File

@ -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