twain-1.0.0.0: src/Web/Twain/Internal.hs
module Web.Twain.Internal where
import Control.Exception (throwIO)
import Control.Monad (join)
import Control.Monad.IO.Class (liftIO)
import qualified Data.Aeson as JSON
import qualified Data.ByteString as B
import Data.ByteString.Builder (toLazyByteString)
import qualified Data.ByteString.Lazy as BL
import Data.Int
import Data.List as L
import Data.Text as T
import Data.Text.Encoding
import Network.HTTP.Types (Method, hCookie, status204)
import Network.Wai (Application, Middleware, Request, lazyRequestBody, queryString, requestHeaders, requestMethod, responseLBS)
import Network.Wai.Parse (File, ParseRequestBodyOptions, lbsBackEnd, parseRequestBodyEx)
import Web.Cookie (SetCookie, parseCookiesText, renderSetCookie)
import Web.Twain.Types
type MaxRequestSizeBytes = Int64
routeState :: RouteM e (RouteState e)
routeState = RouteM $ \s -> return (Right (s, s))
setRouteState :: RouteState e -> RouteM e ()
setRouteState s = RouteM $ \_ -> return (Right ((), s))
concatParams :: RouteState e -> [Param]
concatParams p =
reqBodyParams p
<> reqCookieParams p
<> reqPathParams p
<> reqQueryParams p
composeMiddleware :: [Middleware] -> Application
composeMiddleware = L.foldl' (\a m -> m a) emptyApp
routeMiddleware ::
Maybe Method ->
PathPattern ->
RouteM e a ->
e ->
Middleware
routeMiddleware method pat (RouteM route) env app req respond =
case match method pat req of
Nothing -> app req respond
Just pathParams -> do
let st =
RouteState
{ reqBodyParams = [],
reqBodyFiles = [],
reqPathParams = pathParams,
reqQueryParams = decodeQueryParam <$> queryString req,
reqCookieParams = cookieParams req,
reqBodyJson = Left "missing JSON body",
reqBodyParsed = False,
reqEnv = env,
reqWai = req
}
action <- route st
case action of
Left (Respond res) -> respond res
_ -> app req respond
match :: Maybe Method -> PathPattern -> Request -> Maybe [Param]
match method (MatchPath f) req
| maybe True (requestMethod req ==) method = f req
| otherwise = Nothing
parseBody :: ParseRequestBodyOptions -> RouteM e ([Param], [File BL.ByteString])
parseBody opts = do
s <- routeState
if reqBodyParsed s
then return (concatParams s, reqBodyFiles s)
else do
(ps, fs) <- liftIO $ parseRequestBodyEx opts lbsBackEnd (reqWai s)
let sb =
s
{ reqBodyParams = decodeBsParam <$> ps,
reqBodyFiles = fs,
reqBodyParsed = True
}
setRouteState sb
return (concatParams sb, reqBodyFiles sb)
parseBodyJson :: RouteM e (Either String JSON.Value)
parseBodyJson = do
s <- routeState
if reqBodyParsed s
then return (reqBodyJson s)
else do
jsonE <- liftIO $ JSON.eitherDecode <$> lazyRequestBody (reqWai s)
setRouteState $ s {reqBodyJson = jsonE, reqBodyParsed = True}
return jsonE
cookieParams :: Request -> [Param]
cookieParams req =
let headers = snd <$> L.filter ((==) hCookie . fst) (requestHeaders req)
in join $ parseCookiesText <$> headers
setCookieByteString :: SetCookie -> B.ByteString
setCookieByteString setCookie =
BL.toStrict (toLazyByteString (renderSetCookie setCookie))
decodeQueryParam :: (B.ByteString, Maybe B.ByteString) -> Param
decodeQueryParam (a, b) = (decodeUtf8 a, maybe "" decodeUtf8 b)
decodeBsParam :: (B.ByteString, B.ByteString) -> Param
decodeBsParam (a, b) = (decodeUtf8 a, decodeUtf8 b)
emptyApp :: Application
emptyApp req respond = respond $ responseLBS status204 [] ""