git.iko.soy/typeable/servant
on master help sign in
Split RouteApplication mega-module
48c5cc9Oleg Grenrus
servant-server/CHANGELOG.md+2
@@ -8,6 +8,8 @@
8 [#1131](https://github.com/haskell-servant/pull/1131)
8 [#1131](https://github.com/haskell-servant/pull/1131)
9- *servant-server* Reorder HTTP failure code priorities
9- *servant-server* Reorder HTTP failure code priorities
10 [#1103](https://github.com/haskell-servant/servant/pull/1103)
10 [#1103](https://github.com/haskell-servant/servant/pull/1103)
11+- *servant-server* Re-organise internal modules
12+ [#1139](https://github.com/haskell-servant/servant/pull/1139)
11- Allow `network-3.0`
13- Allow `network-3.0`
12 [#1107](https://github.com/haskell-servant/pull/1107)
14 [#1107](https://github.com/haskell-servant/pull/1107)
13
15
servant-server/servant-server.cabal+3
@@ -52,8 +52,11 @@ library
52 Servant.Server.Internal
52 Servant.Server.Internal
53 Servant.Server.Internal.BasicAuth
53 Servant.Server.Internal.BasicAuth
54 Servant.Server.Internal.Context
54 Servant.Server.Internal.Context
55+ Servant.Server.Internal.Delayed
56+ Servant.Server.Internal.DelayedIO
55 Servant.Server.Internal.Handler
57 Servant.Server.Internal.Handler
56 Servant.Server.Internal.Router
58 Servant.Server.Internal.Router
59+ Servant.Server.Internal.RouteResult
57 Servant.Server.Internal.RoutingApplication
60 Servant.Server.Internal.RoutingApplication
58 Servant.Server.Internal.ServerError
61 Servant.Server.Internal.ServerError
59 Servant.Server.StaticFiles
62 Servant.Server.StaticFiles
servant-server/src/Servant/Server/Experimental/Auth.hs+3−5
@@ -27,11 +27,9 @@ import Servant
27 ((:>))
27 ((:>))
28import Servant.API.Experimental.Auth
28import Servant.API.Experimental.Auth
29import Servant.Server.Internal
29import Servant.Server.Internal
30− (HasContextEntry, HasServer (..), getContextEntry)
30+ (DelayedIO, Handler, HasContextEntry, HasServer (..),
31−import Servant.Server.Internal.Handler
31+ addAuthCheck, delayedFailFatal, getContextEntry, runHandler,
32− (Handler, runHandler)
32+ withRequest)
33−import Servant.Server.Internal.RoutingApplication
34− (DelayedIO, addAuthCheck, delayedFailFatal, withRequest)
35
33
36-- * General Auth
34-- * General Auth
37
35
servant-server/src/Servant/Server/Internal.hs+6
@@ -22,8 +22,11 @@ module Servant.Server.Internal
22 ( module Servant.Server.Internal
22 ( module Servant.Server.Internal
23 , module Servant.Server.Internal.BasicAuth
23 , module Servant.Server.Internal.BasicAuth
24 , module Servant.Server.Internal.Context
24 , module Servant.Server.Internal.Context
25+ , module Servant.Server.Internal.Delayed
26+ , module Servant.Server.Internal.DelayedIO
25 , module Servant.Server.Internal.Handler
27 , module Servant.Server.Internal.Handler
26 , module Servant.Server.Internal.Router
28 , module Servant.Server.Internal.Router
29+ , module Servant.Server.Internal.RouteResult
27 , module Servant.Server.Internal.RoutingApplication
30 , module Servant.Server.Internal.RoutingApplication
28 , module Servant.Server.Internal.ServerError
31 , module Servant.Server.Internal.ServerError
29 ) where
32 ) where
@@ -88,8 +91,11 @@ import Web.HttpApiData
88
91
89import Servant.Server.Internal.BasicAuth
92import Servant.Server.Internal.BasicAuth
90import Servant.Server.Internal.Context
93import Servant.Server.Internal.Context
94+import Servant.Server.Internal.Delayed
95+import Servant.Server.Internal.DelayedIO
91import Servant.Server.Internal.Handler
96import Servant.Server.Internal.Handler
92import Servant.Server.Internal.Router
97import Servant.Server.Internal.Router
98+import Servant.Server.Internal.RouteResult
93import Servant.Server.Internal.RoutingApplication
99import Servant.Server.Internal.RoutingApplication
94import Servant.Server.Internal.ServerError
100import Servant.Server.Internal.ServerError
95
101
servant-server/src/Servant/Server/Internal/BasicAuth.hs+2−2
@@ -9,7 +9,7 @@ import Control.Monad
9 (guard)
9 (guard)
10import Control.Monad.Trans
10import Control.Monad.Trans
11 (liftIO)
11 (liftIO)
12−import qualified Data.ByteString as BS
12+import qualified Data.ByteString as BS
13import Data.ByteString.Base64
13import Data.ByteString.Base64
14 (decodeLenient)
14 (decodeLenient)
15import Data.Monoid
15import Data.Monoid
@@ -26,7 +26,7 @@ import Network.Wai
26
26
27import Servant.API.BasicAuth
27import Servant.API.BasicAuth
28 (BasicAuthData (BasicAuthData))
28 (BasicAuthData (BasicAuthData))
29−import Servant.Server.Internal.RoutingApplication
29+import Servant.Server.Internal.DelayedIO
30import Servant.Server.Internal.ServerError
30import Servant.Server.Internal.ServerError
31
31
32-- * Basic Auth
32-- * Basic Auth
servant-server/src/Servant/Server/Internal/Delayed.hsnew file+272
@@ -0,0 +1,272 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE MultiParamTypeClasses #-}
5+{-# LANGUAGE RecordWildCards #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeOperators #-}
8+{-# LANGUAGE UndecidableInstances #-}
9+module Servant.Server.Internal.Delayed where
10+
11+import Control.Monad.IO.Class
12+ (MonadIO (..))
13+import Control.Monad.Reader
14+ (ask)
15+import Control.Monad.Trans.Resource
16+ (ResourceT, runResourceT)
17+import Network.Wai
18+ (Request, Response)
19+
20+import Servant.Server.Internal.DelayedIO
21+import Servant.Server.Internal.Handler
22+import Servant.Server.Internal.RouteResult
23+import Servant.Server.Internal.ServerError
24+
25+-- | A 'Delayed' is a representation of a handler with scheduled
26+-- delayed checks that can trigger errors.
27+--
28+-- Why would we want to delay checks?
29+--
30+-- There are two reasons:
31+--
32+-- 1. In a straight-forward implementation, the order in which we
33+-- perform checks will determine the error we generate. This is
34+-- because once an error occurs, we would abort and not perform
35+-- any subsequent checks, but rather return the current error.
36+--
37+-- This is not a necessity: we could continue doing other checks,
38+-- and choose the preferred error. However, that would in general
39+-- mean more checking, which leads us to the other reason.
40+--
41+-- 2. We really want to avoid doing certain checks too early. For
42+-- example, captures involve parsing, and are much more costly
43+-- than static route matches. In particular, if several paths
44+-- contain the "same" capture, we'd like as much as possible to
45+-- avoid trying the same parse many times. Also tricky is the
46+-- request body. Again, this involves parsing, but also, WAI makes
47+-- obtaining the request body a side-effecting operation. We
48+-- could/can work around this by manually caching the request body,
49+-- but we'd rather keep the number of times we actually try to
50+-- decode the request body to an absolute minimum.
51+--
52+-- We prefer to have the following relative priorities of error
53+-- codes:
54+--
55+-- @
56+-- 404
57+-- 405 (bad method)
58+-- 401 (unauthorized)
59+-- 415 (unsupported media type)
60+-- 406 (not acceptable)
61+-- 400 (bad request)
62+-- @
63+--
64+-- Therefore, while routing, we delay most checks so that they
65+-- will ultimately occur in the right order.
66+--
67+-- A 'Delayed' contains many delayed blocks of tests, and
68+-- the actual handler:
69+--
70+-- 1. Delayed captures. These can actually cause 404, and
71+-- while they're costly, they should be done first among the
72+-- delayed checks (at least as long as we do not decouple the
73+-- check order from the error reporting, see above). Delayed
74+-- captures can provide inputs to the actual handler.
75+--
76+-- 2. Method check(s). This can cause a 405. On success,
77+-- it does not provide an input for the handler. Method checks
78+-- are comparatively cheap.
79+--
80+-- 3. Authentication checks. This can cause 401.
81+--
82+-- 4. Accept and content type header checks. These checks
83+-- can cause 415 and 406 errors.
84+--
85+-- 5. Query parameter checks. They require parsing and can cause 400 if the
86+-- parsing fails. Query parameter checks provide inputs to the handler
87+--
88+-- 6. Header Checks. They also require parsing and can cause 400 if parsing fails.
89+--
90+-- 7. Body check. The request body check can cause 400.
91+--
92+data Delayed env c where
93+ Delayed :: { capturesD :: env -> DelayedIO captures
94+ , methodD :: DelayedIO ()
95+ , authD :: DelayedIO auth
96+ , acceptD :: DelayedIO ()
97+ , contentD :: DelayedIO contentType
98+ , paramsD :: DelayedIO params
99+ , headersD :: DelayedIO headers
100+ , bodyD :: contentType -> DelayedIO body
101+ , serverD :: captures
102+ -> params
103+ -> headers
104+ -> auth
105+ -> body
106+ -> Request
107+ -> RouteResult c
108+ } -> Delayed env c
109+
110+instance Functor (Delayed env) where
111+ fmap f Delayed{..} =
112+ Delayed
113+ { serverD = \ c p h a b req -> f <$> serverD c p h a b req
114+ , ..
115+ } -- Note [Existential Record Update]
116+
117+-- | A 'Delayed' without any stored checks.
118+emptyDelayed :: RouteResult a -> Delayed env a
119+emptyDelayed result =
120+ Delayed (const r) r r r r r r (const r) (\ _ _ _ _ _ _ -> result)
121+ where
122+ r = return ()
123+
124+-- | Add a capture to the end of the capture block.
125+addCapture :: Delayed env (a -> b)
126+ -> (captured -> DelayedIO a)
127+ -> Delayed (captured, env) b
128+addCapture Delayed{..} new =
129+ Delayed
130+ { capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt
131+ , serverD = \ (x, v) p h a b req -> ($ v) <$> serverD x p h a b req
132+ , ..
133+ } -- Note [Existential Record Update]
134+
135+-- | Add a parameter check to the end of the params block
136+addParameterCheck :: Delayed env (a -> b)
137+ -> DelayedIO a
138+ -> Delayed env b
139+addParameterCheck Delayed {..} new =
140+ Delayed
141+ { paramsD = (,) <$> paramsD <*> new
142+ , serverD = \c (p, pNew) h a b req -> ($ pNew) <$> serverD c p h a b req
143+ , ..
144+ }
145+
146+-- | Add a parameter check to the end of the params block
147+addHeaderCheck :: Delayed env (a -> b)
148+ -> DelayedIO a
149+ -> Delayed env b
150+addHeaderCheck Delayed {..} new =
151+ Delayed
152+ { headersD = (,) <$> headersD <*> new
153+ , serverD = \c p (h, hNew) a b req -> ($ hNew) <$> serverD c p h a b req
154+ , ..
155+ }
156+
157+-- | Add a method check to the end of the method block.
158+addMethodCheck :: Delayed env a
159+ -> DelayedIO ()
160+ -> Delayed env a
161+addMethodCheck Delayed{..} new =
162+ Delayed
163+ { methodD = methodD <* new
164+ , ..
165+ } -- Note [Existential Record Update]
166+
167+-- | Add an auth check to the end of the auth block.
168+addAuthCheck :: Delayed env (a -> b)
169+ -> DelayedIO a
170+ -> Delayed env b
171+addAuthCheck Delayed{..} new =
172+ Delayed
173+ { authD = (,) <$> authD <*> new
174+ , serverD = \ c p h (y, v) b req -> ($ v) <$> serverD c p h y b req
175+ , ..
176+ } -- Note [Existential Record Update]
177+
178+-- | Add a content type and body checks around parameter checks.
179+--
180+-- We'll report failed content type check (415), before trying to parse
181+-- query parameters (400). Which, in turn, happens before request body parsing.
182+addBodyCheck :: Delayed env (a -> b)
183+ -> DelayedIO c -- ^ content type check
184+ -> (c -> DelayedIO a) -- ^ body check
185+ -> Delayed env b
186+addBodyCheck Delayed{..} newContentD newBodyD =
187+ Delayed
188+ { contentD = (,) <$> contentD <*> newContentD
189+ , bodyD = \(content, c) -> (,) <$> bodyD content <*> newBodyD c
190+ , serverD = \ c p h a (z, v) req -> ($ v) <$> serverD c p h a z req
191+ , ..
192+ } -- Note [Existential Record Update]
193+
194+
195+-- | Add an accept header check before handling parameters.
196+-- In principle, we'd like
197+-- to take a bad body (400) response take precedence over a
198+-- failed accept check (406). BUT to allow streaming the body,
199+-- we cannot run the body check and then still backtrack.
200+-- We therefore do the accept check before the body check,
201+-- when we can still backtrack. There are other solutions to
202+-- this, but they'd be more complicated (such as delaying the
203+-- body check further so that it can still be run in a situation
204+-- where we'd otherwise report 406).
205+addAcceptCheck :: Delayed env a
206+ -> DelayedIO ()
207+ -> Delayed env a
208+addAcceptCheck Delayed{..} new =
209+ Delayed
210+ { acceptD = acceptD *> new
211+ , ..
212+ } -- Note [Existential Record Update]
213+
214+-- | Many combinators extract information that is passed to
215+-- the handler without the possibility of failure. In such a
216+-- case, 'passToServer' can be used.
217+passToServer :: Delayed env (a -> b) -> (Request -> a) -> Delayed env b
218+passToServer Delayed{..} x =
219+ Delayed
220+ { serverD = \ c p h a b req -> ($ x req) <$> serverD c p h a b req
221+ , ..
222+ } -- Note [Existential Record Update]
223+
224+-- | Run a delayed server. Performs all scheduled operations
225+-- in order, and passes the results from the capture and body
226+-- blocks on to the actual handler.
227+--
228+-- This should only be called once per request; otherwise the guarantees about
229+-- effect and HTTP error ordering break down.
230+runDelayed :: Delayed env a
231+ -> env
232+ -> Request
233+ -> ResourceT IO (RouteResult a)
234+runDelayed Delayed{..} env = runDelayedIO $ do
235+ r <- ask
236+ c <- capturesD env
237+ methodD
238+ a <- authD
239+ acceptD
240+ content <- contentD
241+ p <- paramsD -- Has to be before body parsing, but after content-type checks
242+ h <- headersD
243+ b <- bodyD content
244+ liftRouteResult (serverD c p h a b r)
245+
246+-- | Runs a delayed server and the resulting action.
247+-- Takes a continuation that lets us send a response.
248+-- Also takes a continuation for how to turn the
249+-- result of the delayed server into a response.
250+runAction :: Delayed env (Handler a)
251+ -> env
252+ -> Request
253+ -> (RouteResult Response -> IO r)
254+ -> (a -> RouteResult Response)
255+ -> IO r
256+runAction action env req respond k = runResourceT $
257+ runDelayed action env req >>= go >>= liftIO . respond
258+ where
259+ go (Fail e) = return $ Fail e
260+ go (FailFatal e) = return $ FailFatal e
261+ go (Route a) = liftIO $ do
262+ e <- runHandler a
263+ case e of
264+ Left err -> return . Route $ responseServerError err
265+ Right x -> return $! k x
266+
267+{- Note [Existential Record Update]
268+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
269+
270+Due to GHC issue <https://ghc.haskell.org/trac/ghc/ticket/2595 2595>, we cannot
271+do the more succint thing - just update the records we actually change.
272+-}
servant-server/src/Servant/Server/Internal/DelayedIO.hsnew file+72
@@ -0,0 +1,72 @@
1+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE MultiParamTypeClasses #-}
3+{-# LANGUAGE TypeFamilies #-}
4+module Servant.Server.Internal.DelayedIO where
5+
6+import Control.Monad.Base
7+ (MonadBase (..))
8+import Control.Monad.Catch
9+ (MonadThrow (..))
10+import Control.Monad.Reader
11+ (MonadReader (..), ReaderT (..), runReaderT)
12+import Control.Monad.Trans
13+ (MonadIO (..), MonadTrans (..))
14+import Control.Monad.Trans.Control
15+ (ComposeSt, MonadBaseControl (..), MonadTransControl (..),
16+ defaultLiftBaseWith, defaultRestoreM)
17+import Control.Monad.Trans.Resource
18+ (MonadResource (..), ResourceT, runInternalState,
19+ runResourceT, transResourceT, withInternalState)
20+import Network.Wai
21+ (Application, Request, Response, ResponseReceived)
22+
23+import Servant.Server.Internal.RouteResult
24+import Servant.Server.Internal.ServerError
25+
26+-- | Computations used in a 'Delayed' can depend on the
27+-- incoming 'Request', may perform 'IO', and result in a
28+-- 'RouteResult', meaning they can either suceed, fail
29+-- (with the possibility to recover), or fail fatally.
30+--
31+newtype DelayedIO a = DelayedIO { runDelayedIO' :: ReaderT Request (ResourceT (RouteResultT IO)) a }
32+ deriving
33+ ( Functor, Applicative, Monad
34+ , MonadIO, MonadReader Request
35+ , MonadThrow
36+ , MonadResource
37+ )
38+
39+instance MonadBase IO DelayedIO where
40+ liftBase = liftIO
41+
42+liftRouteResult :: RouteResult a -> DelayedIO a
43+liftRouteResult x = DelayedIO $ lift . lift $ RouteResultT . return $ x
44+
45+instance MonadBaseControl IO DelayedIO where
46+ -- type StM DelayedIO a = StM (ReaderT Request (ResourceT (RouteResultT IO))) a
47+ -- liftBaseWith f = DelayedIO $ liftBaseWith $ \g -> f (g . runDelayedIO')
48+ -- restoreM = DelayedIO . restoreM
49+
50+ type StM DelayedIO a = RouteResult a
51+ liftBaseWith f = DelayedIO $ ReaderT $ \req -> withInternalState $ \s ->
52+ liftBaseWith $ \runInBase -> f $ \x ->
53+ runInBase (runInternalState (runReaderT (runDelayedIO' x) req) s)
54+ restoreM = DelayedIO . lift . withInternalState . const . restoreM
55+
56+
57+runDelayedIO :: DelayedIO a -> Request -> ResourceT IO (RouteResult a)
58+runDelayedIO m req = transResourceT runRouteResultT $ runReaderT (runDelayedIO' m) req
59+
60+-- | Fail with the option to recover.
61+delayedFail :: ServerError -> DelayedIO a
62+delayedFail err = liftRouteResult $ Fail err
63+
64+-- | Fail fatally, i.e., without any option to recover.
65+delayedFailFatal :: ServerError -> DelayedIO a
66+delayedFailFatal err = liftRouteResult $ FailFatal err
67+
68+-- | Gain access to the incoming request.
69+withRequest :: (Request -> DelayedIO a) -> DelayedIO a
70+withRequest f = do
71+ req <- ask
72+ f req
servant-server/src/Servant/Server/Internal/RouteResult.hsnew file+76
@@ -0,0 +1,76 @@
1+{-# LANGUAGE DeriveFunctor #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE MultiParamTypeClasses #-}
4+{-# LANGUAGE TypeFamilies #-}
5+{-# LANGUAGE UndecidableInstances #-}
6+module Servant.Server.Internal.RouteResult where
7+
8+import Control.Monad
9+ (ap, liftM)
10+import Control.Monad.Base
11+ (MonadBase (..))
12+import Control.Monad.Catch
13+ (MonadThrow (..))
14+import Control.Monad.Trans
15+ (MonadIO (..), MonadTrans (..))
16+import Control.Monad.Trans.Control
17+ (ComposeSt, MonadBaseControl (..), MonadTransControl (..),
18+ defaultLiftBaseWith, defaultRestoreM)
19+
20+import Servant.Server.Internal.ServerError
21+
22+-- | The result of matching against a path in the route tree.
23+data RouteResult a =
24+ Fail ServerError -- ^ Keep trying other paths.
25+ -- The 'ServantError' should only be 404, 405 or 406.
26+ | FailFatal !ServerError -- ^ Don't try other paths.
27+ | Route !a
28+ deriving (Eq, Show, Read, Functor)
29+
30+instance Applicative RouteResult where
31+ pure = return
32+ (<*>) = ap
33+
34+instance Monad RouteResult where
35+ return = Route
36+ Route a >>= f = f a
37+ Fail e >>= _ = Fail e
38+ FailFatal e >>= _ = FailFatal e
39+
40+newtype RouteResultT m a = RouteResultT { runRouteResultT :: m (RouteResult a) }
41+ deriving (Functor)
42+
43+instance MonadTrans RouteResultT where
44+ lift = RouteResultT . liftM Route
45+
46+instance (Functor m, Monad m) => Applicative (RouteResultT m) where
47+ pure = return
48+ (<*>) = ap
49+
50+instance Monad m => Monad (RouteResultT m) where
51+ return = RouteResultT . return . Route
52+ m >>= k = RouteResultT $ do
53+ a <- runRouteResultT m
54+ case a of
55+ Fail e -> return $ Fail e
56+ FailFatal e -> return $ FailFatal e
57+ Route b -> runRouteResultT (k b)
58+
59+instance MonadIO m => MonadIO (RouteResultT m) where
60+ liftIO = lift . liftIO
61+
62+instance MonadBase b m => MonadBase b (RouteResultT m) where
63+ liftBase = lift . liftBase
64+
65+instance MonadBaseControl b m => MonadBaseControl b (RouteResultT m) where
66+ type StM (RouteResultT m) a = ComposeSt RouteResultT m a
67+ liftBaseWith = defaultLiftBaseWith
68+ restoreM = defaultRestoreM
69+
70+instance MonadTransControl RouteResultT where
71+ type StT RouteResultT a = RouteResult a
72+ liftWith f = RouteResultT $ liftM return $ f runRouteResultT
73+ restoreT = RouteResultT
74+
75+instance MonadThrow m => MonadThrow (RouteResultT m) where
76+ throwM = lift . throwM
servant-server/src/Servant/Server/Internal/Router.hs+1
@@ -18,6 +18,7 @@ import qualified Data.Text as T
18import Network.Wai
18import Network.Wai
19 (Response, pathInfo)
19 (Response, pathInfo)
20import Servant.Server.Internal.RoutingApplication
20import Servant.Server.Internal.RoutingApplication
21+import Servant.Server.Internal.RouteResult
21import Servant.Server.Internal.ServerError
22import Servant.Server.Internal.ServerError
22
23
23type Router env = Router' env RoutingApplication
24type Router env = Router' env RoutingApplication
servant-server/src/Servant/Server/Internal/RoutingApplication.hs+1−380
@@ -1,98 +1,16 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveFunctor #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6−{-# LANGUAGE MultiParamTypeClasses #-}
7−{-# LANGUAGE RecordWildCards #-}
8−{-# LANGUAGE TypeFamilies #-}
9−{-# LANGUAGE TypeOperators #-}
10−{-# LANGUAGE UndecidableInstances #-}
11module Servant.Server.Internal.RoutingApplication where
1module Servant.Server.Internal.RoutingApplication where
12
2
13−import Control.Monad
14− (ap, liftM)
15−import Control.Monad.Base
16− (MonadBase (..))
17−import Control.Monad.Catch
18− (MonadThrow (..))
19−import Control.Monad.Reader
20− (MonadReader (..), ReaderT (..), runReaderT)
21−import Control.Monad.Trans
22− (MonadIO (..), MonadTrans (..))
23−import Control.Monad.Trans.Control
24− (ComposeSt, MonadBaseControl (..), MonadTransControl (..),
25− defaultLiftBaseWith, defaultRestoreM)
26−import Control.Monad.Trans.Resource
27− (MonadResource (..), ResourceT, runInternalState,
28− runResourceT, transResourceT, withInternalState)
29import Network.Wai
3import Network.Wai
30 (Application, Request, Response, ResponseReceived)
4 (Application, Request, Response, ResponseReceived)
31import Prelude ()
5import Prelude ()
32import Prelude.Compat
6import Prelude.Compat
33−import Servant.Server.Internal.Handler
7+import Servant.Server.Internal.RouteResult
34import Servant.Server.Internal.ServerError
8import Servant.Server.Internal.ServerError
35
9
36type RoutingApplication =
10type RoutingApplication =
37 Request -- ^ the request, the field 'pathInfo' may be modified by url routing
11 Request -- ^ the request, the field 'pathInfo' may be modified by url routing
38 -> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived
12 -> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived
39
13
40−-- | The result of matching against a path in the route tree.
41−data RouteResult a =
42− Fail ServerError -- ^ Keep trying other paths. The @ServantErr@
43− -- should only be 404, 405 or 406.
44− | FailFatal !ServerError -- ^ Don't try other paths.
45− | Route !a
46− deriving (Eq, Show, Read, Functor)
47−
48−instance Applicative RouteResult where
49− pure = return
50− (<*>) = ap
51−
52−instance Monad RouteResult where
53− return = Route
54− Route a >>= f = f a
55− Fail e >>= _ = Fail e
56− FailFatal e >>= _ = FailFatal e
57−
58−newtype RouteResultT m a = RouteResultT { runRouteResultT :: m (RouteResult a) }
59− deriving (Functor)
60−
61−instance MonadTrans RouteResultT where
62− lift = RouteResultT . liftM Route
63−
64−instance (Functor m, Monad m) => Applicative (RouteResultT m) where
65− pure = return
66− (<*>) = ap
67−
68−instance Monad m => Monad (RouteResultT m) where
69− return = RouteResultT . return . Route
70− m >>= k = RouteResultT $ do
71− a <- runRouteResultT m
72− case a of
73− Fail e -> return $ Fail e
74− FailFatal e -> return $ FailFatal e
75− Route b -> runRouteResultT (k b)
76−
77−instance MonadIO m => MonadIO (RouteResultT m) where
78− liftIO = lift . liftIO
79−
80−instance MonadBase b m => MonadBase b (RouteResultT m) where
81− liftBase = lift . liftBase
82−
83−instance MonadBaseControl b m => MonadBaseControl b (RouteResultT m) where
84− type StM (RouteResultT m) a = ComposeSt RouteResultT m a
85− liftBaseWith = defaultLiftBaseWith
86− restoreM = defaultRestoreM
87−
88−instance MonadTransControl RouteResultT where
89− type StT RouteResultT a = RouteResult a
90− liftWith f = RouteResultT $ liftM return $ f runRouteResultT
91− restoreT = RouteResultT
92−
93−instance MonadThrow m => MonadThrow (RouteResultT m) where
94− throwM = lift . throwM
95−
96toApplication :: RoutingApplication -> Application
14toApplication :: RoutingApplication -> Application
97toApplication ra request respond = ra request routingRespond
15toApplication ra request respond = ra request routingRespond
98 where
16 where
@@ -100,300 +18,3 @@ toApplication ra request respond = ra request routingRespond
100 routingRespond (Fail err) = respond $ responseServerError err
18 routingRespond (Fail err) = respond $ responseServerError err
101 routingRespond (FailFatal err) = respond $ responseServerError err
19 routingRespond (FailFatal err) = respond $ responseServerError err
102 routingRespond (Route v) = respond v
20 routingRespond (Route v) = respond v
103−
104−-- | A 'Delayed' is a representation of a handler with scheduled
105−-- delayed checks that can trigger errors.
106−--
107−-- Why would we want to delay checks?
108−--
109−-- There are two reasons:
110−--
111−-- 1. In a straight-forward implementation, the order in which we
112−-- perform checks will determine the error we generate. This is
113−-- because once an error occurs, we would abort and not perform
114−-- any subsequent checks, but rather return the current error.
115−--
116−-- This is not a necessity: we could continue doing other checks,
117−-- and choose the preferred error. However, that would in general
118−-- mean more checking, which leads us to the other reason.
119−--
120−-- 2. We really want to avoid doing certain checks too early. For
121−-- example, captures involve parsing, and are much more costly
122−-- than static route matches. In particular, if several paths
123−-- contain the "same" capture, we'd like as much as possible to
124−-- avoid trying the same parse many times. Also tricky is the
125−-- request body. Again, this involves parsing, but also, WAI makes
126−-- obtaining the request body a side-effecting operation. We
127−-- could/can work around this by manually caching the request body,
128−-- but we'd rather keep the number of times we actually try to
129−-- decode the request body to an absolute minimum.
130−--
131−-- We prefer to have the following relative priorities of error
132−-- codes:
133−--
134−-- @
135−-- 404
136−-- 405 (bad method)
137−-- 401 (unauthorized)
138−-- 415 (unsupported media type)
139−-- 406 (not acceptable)
140−-- 400 (bad request)
141−-- @
142−--
143−-- Therefore, while routing, we delay most checks so that they
144−-- will ultimately occur in the right order.
145−--
146−-- A 'Delayed' contains many delayed blocks of tests, and
147−-- the actual handler:
148−--
149−-- 1. Delayed captures. These can actually cause 404, and
150−-- while they're costly, they should be done first among the
151−-- delayed checks (at least as long as we do not decouple the
152−-- check order from the error reporting, see above). Delayed
153−-- captures can provide inputs to the actual handler.
154−--
155−-- 2. Method check(s). This can cause a 405. On success,
156−-- it does not provide an input for the handler. Method checks
157−-- are comparatively cheap.
158−--
159−-- 3. Authentication checks. This can cause 401.
160−--
161−-- 4. Accept and content type header checks. These checks
162−-- can cause 415 and 406 errors.
163−--
164−-- 5. Query parameter checks. They require parsing and can cause 400 if the
165−-- parsing fails. Query parameter checks provide inputs to the handler
166−--
167−-- 6. Header Checks. They also require parsing and can cause 400 if parsing fails.
168−--
169−-- 7. Body check. The request body check can cause 400.
170−--
171−data Delayed env c where
172− Delayed :: { capturesD :: env -> DelayedIO captures
173− , methodD :: DelayedIO ()
174− , authD :: DelayedIO auth
175− , acceptD :: DelayedIO ()
176− , contentD :: DelayedIO contentType
177− , paramsD :: DelayedIO params
178− , headersD :: DelayedIO headers
179− , bodyD :: contentType -> DelayedIO body
180− , serverD :: captures
181− -> params
182− -> headers
183− -> auth
184− -> body
185− -> Request
186− -> RouteResult c
187− } -> Delayed env c
188−
189−instance Functor (Delayed env) where
190− fmap f Delayed{..} =
191− Delayed
192− { serverD = \ c p h a b req -> f <$> serverD c p h a b req
193− , ..
194− } -- Note [Existential Record Update]
195−
196−-- | Computations used in a 'Delayed' can depend on the
197−-- incoming 'Request', may perform 'IO, and result in a
198−-- 'RouteResult, meaning they can either suceed, fail
199−-- (with the possibility to recover), or fail fatally.
200−--
201−newtype DelayedIO a = DelayedIO { runDelayedIO' :: ReaderT Request (ResourceT (RouteResultT IO)) a }
202− deriving
203− ( Functor, Applicative, Monad
204− , MonadIO, MonadReader Request
205− , MonadThrow
206− , MonadResource
207− )
208−
209−instance MonadBase IO DelayedIO where
210− liftBase = liftIO
211−
212−liftRouteResult :: RouteResult a -> DelayedIO a
213−liftRouteResult x = DelayedIO $ lift . lift $ RouteResultT . return $ x
214−
215−instance MonadBaseControl IO DelayedIO where
216− -- type StM DelayedIO a = StM (ReaderT Request (ResourceT (RouteResultT IO))) a
217− -- liftBaseWith f = DelayedIO $ liftBaseWith $ \g -> f (g . runDelayedIO')
218− -- restoreM = DelayedIO . restoreM
219−
220− type StM DelayedIO a = RouteResult a
221− liftBaseWith f = DelayedIO $ ReaderT $ \req -> withInternalState $ \s ->
222− liftBaseWith $ \runInBase -> f $ \x ->
223− runInBase (runInternalState (runReaderT (runDelayedIO' x) req) s)
224− restoreM = DelayedIO . lift . withInternalState . const . restoreM
225−
226−
227−runDelayedIO :: DelayedIO a -> Request -> ResourceT IO (RouteResult a)
228−runDelayedIO m req = transResourceT runRouteResultT $ runReaderT (runDelayedIO' m) req
229−
230−-- | A 'Delayed' without any stored checks.
231−emptyDelayed :: RouteResult a -> Delayed env a
232−emptyDelayed result =
233− Delayed (const r) r r r r r r (const r) (\ _ _ _ _ _ _ -> result)
234− where
235− r = return ()
236−
237−-- | Fail with the option to recover.
238−delayedFail :: ServerError -> DelayedIO a
239−delayedFail err = liftRouteResult $ Fail err
240−
241−-- | Fail fatally, i.e., without any option to recover.
242−delayedFailFatal :: ServerError -> DelayedIO a
243−delayedFailFatal err = liftRouteResult $ FailFatal err
244−
245−-- | Gain access to the incoming request.
246−withRequest :: (Request -> DelayedIO a) -> DelayedIO a
247−withRequest f = do
248− req <- ask
249− f req
250−
251−-- | Add a capture to the end of the capture block.
252−addCapture :: Delayed env (a -> b)
253− -> (captured -> DelayedIO a)
254− -> Delayed (captured, env) b
255−addCapture Delayed{..} new =
256− Delayed
257− { capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt
258− , serverD = \ (x, v) p h a b req -> ($ v) <$> serverD x p h a b req
259− , ..
260− } -- Note [Existential Record Update]
261−
262−-- | Add a parameter check to the end of the params block
263−addParameterCheck :: Delayed env (a -> b)
264− -> DelayedIO a
265− -> Delayed env b
266−addParameterCheck Delayed {..} new =
267− Delayed
268− { paramsD = (,) <$> paramsD <*> new
269− , serverD = \c (p, pNew) h a b req -> ($ pNew) <$> serverD c p h a b req
270− , ..
271− }
272−
273−-- | Add a parameter check to the end of the params block
274−addHeaderCheck :: Delayed env (a -> b)
275− -> DelayedIO a
276− -> Delayed env b
277−addHeaderCheck Delayed {..} new =
278− Delayed
279− { headersD = (,) <$> headersD <*> new
280− , serverD = \c p (h, hNew) a b req -> ($ hNew) <$> serverD c p h a b req
281− , ..
282− }
283−
284−-- | Add a method check to the end of the method block.
285−addMethodCheck :: Delayed env a
286− -> DelayedIO ()
287− -> Delayed env a
288−addMethodCheck Delayed{..} new =
289− Delayed
290− { methodD = methodD <* new
291− , ..
292− } -- Note [Existential Record Update]
293−
294−-- | Add an auth check to the end of the auth block.
295−addAuthCheck :: Delayed env (a -> b)
296− -> DelayedIO a
297− -> Delayed env b
298−addAuthCheck Delayed{..} new =
299− Delayed
300− { authD = (,) <$> authD <*> new
301− , serverD = \ c p h (y, v) b req -> ($ v) <$> serverD c p h y b req
302− , ..
303− } -- Note [Existential Record Update]
304−
305−-- | Add a content type and body checks around parameter checks.
306−--
307−-- We'll report failed content type check (415), before trying to parse
308−-- query parameters (400). Which, in turn, happens before request body parsing.
309−addBodyCheck :: Delayed env (a -> b)
310− -> DelayedIO c -- ^ content type check
311− -> (c -> DelayedIO a) -- ^ body check
312− -> Delayed env b
313−addBodyCheck Delayed{..} newContentD newBodyD =
314− Delayed
315− { contentD = (,) <$> contentD <*> newContentD
316− , bodyD = \(content, c) -> (,) <$> bodyD content <*> newBodyD c
317− , serverD = \ c p h a (z, v) req -> ($ v) <$> serverD c p h a z req
318− , ..
319− } -- Note [Existential Record Update]
320−
321−
322−-- | Add an accept header check before handling parameters.
323−-- In principle, we'd like
324−-- to take a bad body (400) response take precedence over a
325−-- failed accept check (406). BUT to allow streaming the body,
326−-- we cannot run the body check and then still backtrack.
327−-- We therefore do the accept check before the body check,
328−-- when we can still backtrack. There are other solutions to
329−-- this, but they'd be more complicated (such as delaying the
330−-- body check further so that it can still be run in a situation
331−-- where we'd otherwise report 406).
332−addAcceptCheck :: Delayed env a
333− -> DelayedIO ()
334− -> Delayed env a
335−addAcceptCheck Delayed{..} new =
336− Delayed
337− { acceptD = acceptD *> new
338− , ..
339− } -- Note [Existential Record Update]
340−
341−-- | Many combinators extract information that is passed to
342−-- the handler without the possibility of failure. In such a
343−-- case, 'passToServer' can be used.
344−passToServer :: Delayed env (a -> b) -> (Request -> a) -> Delayed env b
345−passToServer Delayed{..} x =
346− Delayed
347− { serverD = \ c p h a b req -> ($ x req) <$> serverD c p h a b req
348− , ..
349− } -- Note [Existential Record Update]
350−
351−-- | Run a delayed server. Performs all scheduled operations
352−-- in order, and passes the results from the capture and body
353−-- blocks on to the actual handler.
354−--
355−-- This should only be called once per request; otherwise the guarantees about
356−-- effect and HTTP error ordering break down.
357−runDelayed :: Delayed env a
358− -> env
359− -> Request
360− -> ResourceT IO (RouteResult a)
361−runDelayed Delayed{..} env = runDelayedIO $ do
362− r <- ask
363− c <- capturesD env
364− methodD
365− a <- authD
366− acceptD
367− content <- contentD
368− p <- paramsD -- Has to be before body parsing, but after content-type checks
369− h <- headersD
370− b <- bodyD content
371− liftRouteResult (serverD c p h a b r)
372−
373−-- | Runs a delayed server and the resulting action.
374−-- Takes a continuation that lets us send a response.
375−-- Also takes a continuation for how to turn the
376−-- result of the delayed server into a response.
377−runAction :: Delayed env (Handler a)
378− -> env
379− -> Request
380− -> (RouteResult Response -> IO r)
381− -> (a -> RouteResult Response)
382− -> IO r
383−runAction action env req respond k = runResourceT $
384− runDelayed action env req >>= go >>= liftIO . respond
385− where
386− go (Fail e) = return $ Fail e
387− go (FailFatal e) = return $ FailFatal e
388− go (Route a) = liftIO $ do
389− e <- runHandler a
390− case e of
391− Left err -> return . Route $ responseServerError err
392− Right x -> return $! k x
393−
394−{- Note [Existential Record Update]
395−~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
396−
397−Due to GHC issue <https://ghc.haskell.org/trac/ghc/ticket/2595 2595>, we cannot
398−do the more succint thing - just update the records we actually change.
399−-}
servant-server/test/Servant/Server/Internal/RoutingApplicationSpec.hs+1−1
@@ -23,7 +23,7 @@ import GHC.TypeLits
23import Network.Wai
23import Network.Wai
24 (defaultRequest)
24 (defaultRequest)
25import Servant
25import Servant
26−import Servant.Server.Internal.RoutingApplication
26+import Servant.Server.Internal
27import Test.Hspec
27import Test.Hspec
28import Test.Hspec.Wai
28import Test.Hspec.Wai
29 (request, shouldRespondWith, with)
29 (request, shouldRespondWith, with)
servant/CHANGELOG.md+2
@@ -53,6 +53,8 @@
53 [#1104](https://github.com/haskell-servant/servant/pull/1104)
53 [#1104](https://github.com/haskell-servant/servant/pull/1104)
54- *servant-server* Reorder HTTP failure code priorities
54- *servant-server* Reorder HTTP failure code priorities
55 [#1103](https://github.com/haskell-servant/servant/pull/1103)
55 [#1103](https://github.com/haskell-servant/servant/pull/1103)
56+- *servant-server* Re-organise internal modules
57+ [#1139](https://github.com/haskell-servant/servant/pull/1139)
56- Allow `network-3.0`
58- Allow `network-3.0`
57 [#1107](https://github.com/haskell-servant/servant/pull/1107)
59 [#1107](https://github.com/haskell-servant/servant/pull/1107)
58- Add `NFData NoContent` instance
60- Add `NFData NoContent` instance