@@ -22,15 +22,13 @@ module Servant.Server.Internal
2222 , module Servant.Server.Internal.ServantErr
2323 ) where
2424
25−import Control.Monad.Trans.Except (ExceptT)
25+import Control.Monad.Trans (liftIO)
2626import qualified Data.ByteString as B
2727import qualified Data.ByteString.Char8 as BC8
2828import qualified Data.ByteString.Lazy as BL
29−import qualified Data.Map as M
3029import Data.Maybe (fromMaybe, mapMaybe)
3130import Data.String (fromString)
3231import Data.String.Conversions (cs, (<>))
33−import Data.Text (Text)
3432import Data.Typeable
3533import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
3634 symbolVal)
@@ -38,7 +36,7 @@ import Network.HTTP.Types hiding (Header, ResponseHeaders)
3836import Network.Socket (SockAddr)
3937import Network.Wai (Application, Request, Response,
4038 httpVersion, isSecure,
41− lazyRequestBody, pathInfo,
39+ lazyRequestBody,
4240 rawQueryString, remoteHost,
4341 requestHeaders, requestMethod,
4442 responseLBS, vault)
@@ -70,12 +68,16 @@ import Servant.Server.Internal.RoutingApplication
7068import Servant.Server.Internal.ServantErr
7169
7270
73−class HasServer layout context where
74− type ServerT layout (m :: * -> *) :: *
71+class HasServer api context where
72+ type ServerT api (m :: * -> *) :: *
7573
76− route :: Proxy layout -> Context context -> Delayed (Server layout) -> Router
74+ route ::
75+ Proxy api
76+ -> Context context
77+ -> Delayed env (Server api)
78+ -> Router env
7779
78−type Server layout = ServerT layout (ExceptT ServantErr IO)
80+type Server api = ServerT api Handler
7981
8082-- * Instances
8183
@@ -95,7 +97,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
9597 type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
9698
9799 route Proxy context server = choice (route pa context ((\ (a :<|> _) -> a) <$> server))
98− (route pb context ((\ (_ :<|> b) -> b) <$> server))
100+ (route pb context ((\ (_ :<|> b) -> b) <$> server))
99101 where pa = Proxy :: Proxy a
100102 pb = Proxy :: Proxy b
101103
@@ -114,21 +116,21 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
114116-- >
115117-- > server :: Server MyApi
116118-- > server = getBook
117−-- > where getBook :: Text -> ExceptT ServantErr IO Book
119+-- > where getBook :: Text -> Handler Book
118120-- > getBook isbn = ...
119−instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout context)
120− => HasServer (Capture capture a :> sublayout) context where
121+instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
122+ => HasServer (Capture capture a :> api) context where
121123
122− type ServerT (Capture capture a :> sublayout) m =
123− a -> ServerT sublayout m
124+ type ServerT (Capture capture a :> api) m =
125+ a -> ServerT api m
124126
125127 route Proxy context d =
126− DynamicRouter $ \ first ->
127− route (Proxy :: Proxy sublayout)
128+ CaptureRouter $
129+ route (Proxy :: Proxy api)
128130 context
129− (addCapture d $ case parseUrlPieceMaybe first :: Maybe a of
130− Nothing -> return $ Fail err400
131− Just v -> return $ Route v
131+ (addCapture d $ \ txt -> case parseUrlPieceMaybe txt :: Maybe a of
132+ Nothing -> delayedFail err400
133+ Just v -> return v
132134 )
133135
134136allowedMethodHead :: Method -> Request -> Bool
@@ -147,48 +149,51 @@ processMethodRouter handleA status method headers request = case handleA of
147149 bdy = if allowedMethodHead method request then "" else body
148150 hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
149151
150−methodCheck :: Method -> Request -> IO (RouteResult ())
152+methodCheck :: Method -> Request -> DelayedIO ()
151153methodCheck method request
152− | allowedMethod method request = return $ Route ()
153− | otherwise = return $ Fail err405
154−
155−acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> IO (RouteResult ())
154+ | allowedMethod method request = return ()
155+ | otherwise = delayedFail err405
156+
157+-- This has switched between using 'Fail' and 'FailFatal' a number of
158+-- times. If the 'acceptCheck' is run after the body check (which would
159+-- be morally right), then we have to set this to 'FailFatal', because
160+-- the body check is not reversible, and therefore backtracking after the
161+-- body check is no longer an option. However, we now run the accept
162+-- check before the body check and can therefore afford to make it
163+-- recoverable.
164+acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
156165acceptCheck proxy accH
157− | canHandleAcceptH proxy (AcceptHeader accH) = return $ Route ()
158− | otherwise = return $ FailFatal err406
166+ | canHandleAcceptH proxy (AcceptHeader accH) = return ()
167+ | otherwise = delayedFail err406
159168
160169methodRouter :: (AllCTRender ctypes a)
161170 => Method -> Proxy ctypes -> Status
162− -> Delayed (ExceptT ServantErr IO a)
163− -> Router
164−methodRouter method proxy status action = LeafRouter route'
171+ -> Delayed env (Handler a)
172+ -> Router env
173+methodRouter method proxy status action = leafRouter route'
165174 where
166− route' request respond
167− | pathIsEmpty request =
175+ route' env request respond =
168176 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
169177 in runAction (action `addMethodCheck` methodCheck method request
170178 `addAcceptCheck` acceptCheck proxy accH
171− ) respond $ \ output -> do
179+ ) env request respond $ \ output -> do
172180 let handleA = handleAcceptH proxy (AcceptHeader accH) output
173181 processMethodRouter handleA status method Nothing request
174− | otherwise = respond $ Fail err404
175182
176183methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
177184 => Method -> Proxy ctypes -> Status
178− -> Delayed (ExceptT ServantErr IO (Headers h v))
179− -> Router
180−methodRouterHeaders method proxy status action = LeafRouter route'
185+ -> Delayed env (Handler (Headers h v))
186+ -> Router env
187+methodRouterHeaders method proxy status action = leafRouter route'
181188 where
182− route' request respond
183− | pathIsEmpty request =
189+ route' env request respond =
184190 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
185191 in runAction (action `addMethodCheck` methodCheck method request
186192 `addAcceptCheck` acceptCheck proxy accH
187− ) respond $ \ output -> do
193+ ) env request respond $ \ output -> do
188194 let headers = getHeaders output
189195 handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
190196 processMethodRouter handleA status method (Just headers) request
191− | otherwise = respond $ Fail err404
192197
193198instance OVERLAPPABLE_
194199 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
@@ -229,17 +234,17 @@ instance OVERLAPPING_
229234-- >
230235-- > server :: Server MyApi
231236-- > server = viewReferer
232−-- > where viewReferer :: Referer -> ExceptT ServantErr IO referer
237+-- > where viewReferer :: Referer -> Handler referer
233238-- > viewReferer referer = return referer
234−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
235− => HasServer (Header sym a :> sublayout) context where
239+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
240+ => HasServer (Header sym a :> api) context where
236241
237− type ServerT (Header sym a :> sublayout) m =
238− Maybe a -> ServerT sublayout m
242+ type ServerT (Header sym a :> api) m =
243+ Maybe a -> ServerT api m
239244
240− route Proxy context subserver = WithRequest $ \ request ->
241− let mheader = parseHeaderMaybe =<< lookup str (requestHeaders request)
242− in route (Proxy :: Proxy sublayout) context (passToServer subserver mheader)
245+ route Proxy context subserver =
246+ let mheader req = parseHeaderMaybe =<< lookup str (requestHeaders req)
247+ in route (Proxy :: Proxy api) context (passToServer subserver mheader)
243248 where str = fromString $ symbolVal (Proxy :: Proxy sym)
244249
245250-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
@@ -260,24 +265,24 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
260265-- >
261266-- > server :: Server MyApi
262267-- > server = getBooksBy
263−-- > where getBooksBy :: Maybe Text -> ExceptT ServantErr IO [Book]
268+-- > where getBooksBy :: Maybe Text -> Handler [Book]
264269-- > getBooksBy Nothing = ...return all books...
265270-- > getBooksBy (Just author) = ...return books by the given author...
266−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
267− => HasServer (QueryParam sym a :> sublayout) context where
271+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
272+ => HasServer (QueryParam sym a :> api) context where
268273
269− type ServerT (QueryParam sym a :> sublayout) m =
270− Maybe a -> ServerT sublayout m
274+ type ServerT (QueryParam sym a :> api) m =
275+ Maybe a -> ServerT api m
271276
272− route Proxy context subserver = WithRequest $ \ request ->
273− let querytext = parseQueryText $ rawQueryString request
274− param =
275− case lookup paramname querytext of
277+ route Proxy context subserver =
278+ let querytext r = parseQueryText $ rawQueryString r
279+ param r =
280+ case lookup paramname (querytext r) of
276281 Nothing -> Nothing -- param absent from the query string
277282 Just Nothing -> Nothing -- param present with no value -> Nothing
278283 Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
279284 -- the right type
280− in route (Proxy :: Proxy sublayout) context (passToServer subserver param)
285+ in route (Proxy :: Proxy api) context (passToServer subserver param)
281286 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
282287
283288-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
@@ -297,22 +302,22 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
297302-- >
298303-- > server :: Server MyApi
299304-- > server = getBooksBy
300−-- > where getBooksBy :: [Text] -> ExceptT ServantErr IO [Book]
305+-- > where getBooksBy :: [Text] -> Handler [Book]
301306-- > getBooksBy authors = ...return all books by these authors...
302−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
303− => HasServer (QueryParams sym a :> sublayout) context where
307+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
308+ => HasServer (QueryParams sym a :> api) context where
304309
305− type ServerT (QueryParams sym a :> sublayout) m =
306− [a] -> ServerT sublayout m
310+ type ServerT (QueryParams sym a :> api) m =
311+ [a] -> ServerT api m
307312
308− route Proxy context subserver = WithRequest $ \ request ->
309− let querytext = parseQueryText $ rawQueryString request
313+ route Proxy context subserver =
314+ let querytext r = parseQueryText $ rawQueryString r
310315 -- if sym is "foo", we look for query string parameters
311316 -- named "foo" or "foo[]" and call parseQueryParam on the
312317 -- corresponding values
313− parameters = filter looksLikeParam querytext
314− values = mapMaybe (convert . snd) parameters
315− in route (Proxy :: Proxy sublayout) context (passToServer subserver values)
318+ parameters r = filter looksLikeParam (querytext r)
319+ values r = mapMaybe (convert . snd) (parameters r)
320+ in route (Proxy :: Proxy api) context (passToServer subserver values)
316321 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
317322 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
318323 convert Nothing = Nothing
@@ -328,21 +333,21 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
328333-- >
329334-- > server :: Server MyApi
330335-- > server = getBooks
331−-- > where getBooks :: Bool -> ExceptT ServantErr IO [Book]
336+-- > where getBooks :: Bool -> Handler [Book]
332337-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
333−instance (KnownSymbol sym, HasServer sublayout context)
334− => HasServer (QueryFlag sym :> sublayout) context where
338+instance (KnownSymbol sym, HasServer api context)
339+ => HasServer (QueryFlag sym :> api) context where
335340
336− type ServerT (QueryFlag sym :> sublayout) m =
337− Bool -> ServerT sublayout m
341+ type ServerT (QueryFlag sym :> api) m =
342+ Bool -> ServerT api m
338343
339− route Proxy context subserver = WithRequest $ \ request ->
340− let querytext = parseQueryText $ rawQueryString request
341− param = case lookup paramname querytext of
344+ route Proxy context subserver =
345+ let querytext r = parseQueryText $ rawQueryString r
346+ param r = case lookup paramname (querytext r) of
342347 Just Nothing -> True -- param is there, with no value
343348 Just (Just v) -> examine v -- param with a value
344349 Nothing -> False -- param not in the query string
345− in route (Proxy :: Proxy sublayout) context (passToServer subserver param)
350+ in route (Proxy :: Proxy api) context (passToServer subserver param)
346351 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
347352 examine v | v == "true" || v == "1" || v == "" = True
348353 | otherwise = False
@@ -359,8 +364,8 @@ instance HasServer Raw context where
359364
360365 type ServerT Raw m = Application
361366
362− route Proxy _ rawApplication = LeafRouter $ \ request respond -> do
363− r <- runDelayed rawApplication
367+ route Proxy _ rawApplication = RawRouter $ \ env request respond -> do
368+ r <- runDelayed rawApplication env request
364369 case r of
365370 Route app -> app request (respond . Route)
366371 Fail a -> respond $ Fail a
@@ -385,18 +390,18 @@ instance HasServer Raw context where
385390-- >
386391-- > server :: Server MyApi
387392-- > server = postBook
388−-- > where postBook :: Book -> ExceptT ServantErr IO Book
393+-- > where postBook :: Book -> Handler Book
389394-- > postBook book = ...insert into your db...
390−instance ( AllCTUnrender list a, HasServer sublayout context
391− ) => HasServer (ReqBody list a :> sublayout) context where
395+instance ( AllCTUnrender list a, HasServer api context
396+ ) => HasServer (ReqBody list a :> api) context where
392397
393− type ServerT (ReqBody list a :> sublayout) m =
394− a -> ServerT sublayout m
398+ type ServerT (ReqBody list a :> api) m =
399+ a -> ServerT api m
395400
396− route Proxy context subserver = WithRequest $ \ request ->
397− route (Proxy :: Proxy sublayout) context (addBodyCheck subserver (bodyCheck request))
401+ route Proxy context subserver =
402+ route (Proxy :: Proxy api) context (addBodyCheck subserver bodyCheck)
398403 where
399− bodyCheck request = do
404+ bodyCheck = withRequest $ \ request -> do
400405 -- See HTTP RFC 2616, section 7.2.1
401406 -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
402407 -- See also "W3C Internet Media Type registration, consistency of use"
@@ -404,48 +409,49 @@ instance ( AllCTUnrender list a, HasServer sublayout context
404409 let contentTypeH = fromMaybe "application/octet-stream"
405410 $ lookup hContentType $ requestHeaders request
406411 mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
407− <$> lazyRequestBody request
412+ <$> liftIO (lazyRequestBody request)
408413 case mrqbody of
409− Nothing -> return $ FailFatal err415
410− Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
411− Just (Right v) -> return $ Route v
414+ Nothing -> delayedFailFatal err415
415+ Just (Left e) -> delayedFailFatal err400 { errBody = cs e }
416+ Just (Right v) -> return v
412417
413418-- | Make sure the incoming request starts with @"/path"@, strip it and
414−-- pass the rest of the request path to @sublayout@.
415−instance (KnownSymbol path, HasServer sublayout context) => HasServer (path :> sublayout) context where
419+-- pass the rest of the request path to @api@.
420+instance (KnownSymbol path, HasServer api context) => HasServer (path :> api) context where
416421
417− type ServerT (path :> sublayout) m = ServerT sublayout m
422+ type ServerT (path :> api) m = ServerT api m
418423
419− route Proxy context subserver = StaticRouter $
420− M.singleton (cs (symbolVal proxyPath))
421− (route (Proxy :: Proxy sublayout) context subserver)
424+ route Proxy context subserver =
425+ pathRouter
426+ (cs (symbolVal proxyPath))
427+ (route (Proxy :: Proxy api) context subserver)
422428 where proxyPath = Proxy :: Proxy path
423429
424430instance HasServer api context => HasServer (RemoteHost :> api) context where
425431 type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
426432
427− route Proxy context subserver = WithRequest $ \req ->
428− route (Proxy :: Proxy api) context (passToServer subserver $ remoteHost req)
433+ route Proxy context subserver =
434+ route (Proxy :: Proxy api) context (passToServer subserver remoteHost)
429435
430436instance HasServer api context => HasServer (IsSecure :> api) context where
431437 type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
432438
433− route Proxy context subserver = WithRequest $ \req ->
434− route (Proxy :: Proxy api) context (passToServer subserver $ secure req)
439+ route Proxy context subserver =
440+ route (Proxy :: Proxy api) context (passToServer subserver secure)
435441
436442 where secure req = if isSecure req then Secure else NotSecure
437443
438444instance HasServer api context => HasServer (Vault :> api) context where
439445 type ServerT (Vault :> api) m = Vault -> ServerT api m
440446
441− route Proxy context subserver = WithRequest $ \req ->
442− route (Proxy :: Proxy api) context (passToServer subserver $ vault req)
447+ route Proxy context subserver =
448+ route (Proxy :: Proxy api) context (passToServer subserver vault)
443449
444450instance HasServer api context => HasServer (HttpVersion :> api) context where
445451 type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
446452
447− route Proxy context subserver = WithRequest $ \req ->
448− route (Proxy :: Proxy api) context (passToServer subserver $ httpVersion req)
453+ route Proxy context subserver =
454+ route (Proxy :: Proxy api) context (passToServer subserver httpVersion)
449455
450456-- | Basic Authentication
451457instance ( KnownSymbol realm
@@ -456,21 +462,15 @@ instance ( KnownSymbol realm
456462
457463 type ServerT (BasicAuth realm usr :> api) m = usr -> ServerT api m
458464
459− route Proxy context subserver = WithRequest $ \ request ->
460− route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck request)
465+ route Proxy context subserver =
466+ route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck)
461467 where
462468 realm = BC8.pack $ symbolVal (Proxy :: Proxy realm)
463469 basicAuthContext = getContextEntry context
464− authCheck req = runBasicAuth req realm basicAuthContext
470+ authCheck = withRequest $ \ req -> runBasicAuth req realm basicAuthContext
465471
466472-- * helpers
467473
468−pathIsEmpty :: Request -> Bool
469−pathIsEmpty = go . pathInfo
470− where go [] = True
471− go [""] = True
472− go _ = False
473−
474474ct_wildcard :: B.ByteString
475475ct_wildcard = "*" <> "/" <> "*" -- Because CPP
476476