2015-06-01 19:38:51 +02:00
|
|
|
{-# LANGUAGE CPP #-}
|
2015-09-10 08:49:19 +02:00
|
|
|
{-# LANGUAGE DeriveFunctor #-}
|
2015-06-01 19:38:51 +02:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
|
|
|
module Servant.Server.Internal.RoutingApplication where
|
|
|
|
|
|
|
|
#if !MIN_VERSION_base(4,8,0)
|
2015-08-17 23:56:29 +02:00
|
|
|
import Control.Applicative (Applicative, (<$>))
|
2015-09-10 08:49:19 +02:00
|
|
|
import Data.Monoid (Monoid, mappend, mempty,
|
|
|
|
(<>))
|
2015-06-01 19:38:51 +02:00
|
|
|
#endif
|
2015-09-12 14:11:24 +02:00
|
|
|
import Control.Monad.Trans.Except (ExceptT, runExceptT)
|
2015-08-17 23:56:29 +02:00
|
|
|
import qualified Data.ByteString as B
|
|
|
|
import qualified Data.ByteString.Lazy as BL
|
|
|
|
import Data.IORef (newIORef, readIORef,
|
|
|
|
writeIORef)
|
|
|
|
import Network.Wai (Application, Request,
|
|
|
|
Response, ResponseReceived,
|
2015-09-10 08:49:19 +02:00
|
|
|
requestBody,
|
2015-08-17 23:56:29 +02:00
|
|
|
strictRequestBody)
|
|
|
|
import Servant.API ((:<|>) (..))
|
2015-06-01 19:38:51 +02:00
|
|
|
import Servant.Server.Internal.ServantErr
|
|
|
|
|
|
|
|
type RoutingApplication =
|
|
|
|
Request -- ^ the request, the field 'pathInfo' may be modified by url routing
|
|
|
|
-> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived
|
|
|
|
|
|
|
|
-- | A wrapper around @'Either' 'RouteMismatch' a@.
|
2015-09-10 08:49:19 +02:00
|
|
|
data RouteResult a =
|
2015-09-15 11:37:17 +02:00
|
|
|
Fail ServantErr -- ^ Keep trying other paths. The @ServantErr@
|
2015-09-10 08:49:19 +02:00
|
|
|
-- should only be 404 or 405.
|
2015-09-15 11:37:17 +02:00
|
|
|
| FailFatal ServantErr -- ^ Don't other paths.
|
|
|
|
| Route a
|
2015-09-10 08:49:19 +02:00
|
|
|
deriving (Eq, Show, Read, Functor)
|
2015-06-01 19:38:51 +02:00
|
|
|
|
|
|
|
data ReqBodyState = Uncalled
|
|
|
|
| Called !B.ByteString
|
|
|
|
| Done !B.ByteString
|
|
|
|
|
|
|
|
toApplication :: RoutingApplication -> Application
|
|
|
|
toApplication ra request respond = do
|
|
|
|
reqBodyRef <- newIORef Uncalled
|
|
|
|
-- We may need to consume the requestBody more than once. In order to
|
|
|
|
-- maintain the illusion that 'requestBody' works as expected,
|
|
|
|
-- 'ReqBodyState' is introduced, and the complete body is memoized and
|
|
|
|
-- returned as many times as requested with empty "Done" marker chunks in
|
|
|
|
-- between.
|
|
|
|
-- See https://github.com/haskell-servant/servant/issues/3
|
|
|
|
let memoReqBody = do
|
|
|
|
ior <- readIORef reqBodyRef
|
|
|
|
case ior of
|
|
|
|
Uncalled -> do
|
|
|
|
r <- BL.toStrict <$> strictRequestBody request
|
|
|
|
writeIORef reqBodyRef $ Done r
|
|
|
|
return r
|
|
|
|
Called bs -> do
|
|
|
|
writeIORef reqBodyRef $ Done bs
|
|
|
|
return bs
|
|
|
|
Done bs -> do
|
|
|
|
writeIORef reqBodyRef $ Called bs
|
|
|
|
return B.empty
|
|
|
|
|
2015-09-10 08:49:19 +02:00
|
|
|
ra request{ requestBody = memoReqBody } routingRespond
|
2015-06-01 19:38:51 +02:00
|
|
|
where
|
2015-09-10 08:49:19 +02:00
|
|
|
routingRespond :: RouteResult Response -> IO ResponseReceived
|
2015-09-15 11:37:17 +02:00
|
|
|
routingRespond (Fail err) = respond $! responseServantErr err
|
|
|
|
routingRespond (FailFatal err) = respond $! responseServantErr err
|
|
|
|
routingRespond (Route v) = respond v
|
2015-06-01 19:38:51 +02:00
|
|
|
|
2015-09-12 14:11:24 +02:00
|
|
|
runAction :: IO (RouteResult (ExceptT ServantErr IO a))
|
2015-06-01 19:38:51 +02:00
|
|
|
-> (RouteResult Response -> IO r)
|
|
|
|
-> (a -> RouteResult Response)
|
|
|
|
-> IO r
|
2015-09-10 08:49:19 +02:00
|
|
|
runAction action respond k = action >>= go >>= respond
|
2015-06-01 19:38:51 +02:00
|
|
|
where
|
2015-09-15 11:37:17 +02:00
|
|
|
go (Fail e) = return $ Fail e
|
|
|
|
go (FailFatal e) = return $ FailFatal e
|
|
|
|
go (Route a) = do
|
2015-09-12 14:11:24 +02:00
|
|
|
e <- runExceptT a
|
2015-09-10 08:49:19 +02:00
|
|
|
case e of
|
2015-09-15 11:37:17 +02:00
|
|
|
Left err -> return . Route $ responseServantErr err
|
2015-09-10 08:49:19 +02:00
|
|
|
Right x -> return $! k x
|
2015-06-01 19:38:51 +02:00
|
|
|
|
|
|
|
feedTo :: IO (RouteResult (a -> b)) -> a -> IO (RouteResult b)
|
|
|
|
feedTo f x = (($ x) <$>) <$> f
|
|
|
|
|
|
|
|
extractL :: RouteResult (a :<|> b) -> RouteResult a
|
2015-09-15 11:37:17 +02:00
|
|
|
extractL (Route (a :<|> _)) = Route a
|
|
|
|
extractL (Fail x) = Fail x
|
|
|
|
extractL (FailFatal x) = FailFatal x
|
2015-06-01 19:38:51 +02:00
|
|
|
|
|
|
|
extractR :: RouteResult (a :<|> b) -> RouteResult b
|
2015-09-15 11:37:17 +02:00
|
|
|
extractR (Route (_ :<|> b)) = Route b
|
|
|
|
extractR (Fail x) = Fail x
|
|
|
|
extractR (FailFatal x) = FailFatal x
|