Skip to content

Commit 15143cc

Browse files
committed
Merge pull request #457 from kosmikus/fix-router-sharing
Fix router sharing
2 parents d4c6f67 + b1a6d88 commit 15143cc

File tree

10 files changed

+293
-270
lines changed

10 files changed

+293
-270
lines changed

servant-server/CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
0.7
22
---
33

4-
* The `Router` type has been changed. There are now more situations where
5-
servers will make use of static lookups to efficiently route the request
6-
to the correct endpoint. Functions `layout` and `layoutWithContext` have
7-
been added to visualize the router layout for debugging purposes. Test
4+
* The `Router` type has been changed. Static router tables should now
5+
be properly shared between requests, drastically increasing the
6+
number of situations where servers will be able to route requests
7+
efficiently. Functions `layout` and `layoutWithContext` have been
8+
added to visualize the router layout for debugging purposes. Test
89
cases for expected router layouts have been added.
910
* Export `throwError` from module `Servant`
1011
* Add `Handler` type synonym

servant-server/src/Servant/Server.hs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -132,21 +132,13 @@ serve p = serveWithContext p EmptyContext
132132

133133
serveWithContext :: (HasServer layout context)
134134
=> Proxy layout -> Context context -> Server layout -> Application
135-
serveWithContext p context server = toApplication (runRouter (route p context d))
136-
where
137-
d = Delayed r r r r (\ _ _ _ -> Route server)
138-
r = return (Route ())
135+
serveWithContext p context server =
136+
toApplication (runRouter (route p context (emptyDelayed (Route server))))
139137

140138
-- | The function 'layout' produces a textual description of the internal
141139
-- router layout for debugging purposes. Note that the router layout is
142140
-- determined just by the API, not by the handlers.
143141
--
144-
-- This function makes certain assumptions about the well-behavedness of
145-
-- the 'HasServer' instances of the combinators which should be ok for the
146-
-- core servant constructions, but might not be satisfied for some other
147-
-- combinators provided elsewhere. It is possible that the function may
148-
-- crash for these.
149-
--
150142
-- Example:
151143
--
152144
-- For the following API
@@ -168,7 +160,7 @@ serveWithContext p context server = toApplication (runRouter (route p context d)
168160
-- > │ └─ e/
169161
-- > │ └─•
170162
-- > ├─ b/
171-
-- > │ └─ <dyn>/
163+
-- > │ └─ <capture>/
172164
-- > │ ├─•
173165
-- > │ ┆
174166
-- > │ └─•
@@ -185,7 +177,7 @@ serveWithContext p context server = toApplication (runRouter (route p context d)
185177
--
186178
-- [@─•@] Leaves reflect endpoints.
187179
--
188-
-- [@\<dyn\>/@] This is a delayed capture of a path component.
180+
-- [@\<capture\>/@] This is a delayed capture of a path component.
189181
--
190182
-- [@\<raw\>@] This is a part of the API we do not know anything about.
191183
--
@@ -200,10 +192,8 @@ layout p = layoutWithContext p EmptyContext
200192
-- | Variant of 'layout' that takes an additional 'Context'.
201193
layoutWithContext :: (HasServer layout context)
202194
=> Proxy layout -> Context context -> Text
203-
layoutWithContext p context = routerLayout (route p context d)
204-
where
205-
d = Delayed r r r r (\ _ _ _ -> FailFatal err501)
206-
r = return (Route ())
195+
layoutWithContext p context =
196+
routerLayout (route p context (emptyDelayed (FailFatal err501)))
207197

208198
-- Documentation
209199

servant-server/src/Servant/Server/Experimental/Auth.hs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
module Servant.Server.Experimental.Auth where
1414

15+
import Control.Monad.Trans (liftIO)
1516
import Control.Monad.Trans.Except (runExceptT)
1617
import Data.Proxy (Proxy (Proxy))
1718
import Data.Typeable (Typeable)
@@ -24,10 +25,11 @@ import Servant.Server.Internal (HasContextEntry,
2425
HasServer, ServerT,
2526
getContextEntry,
2627
route)
27-
import Servant.Server.Internal.Router (Router' (WithRequest))
28-
import Servant.Server.Internal.RoutingApplication (RouteResult (FailFatal, Route),
29-
addAuthCheck)
30-
import Servant.Server.Internal.ServantErr (ServantErr, Handler)
28+
import Servant.Server.Internal.RoutingApplication (addAuthCheck,
29+
delayedFailFatal,
30+
DelayedIO,
31+
withRequest)
32+
import Servant.Server.Internal.ServantErr (Handler)
3133

3234
-- * General Auth
3335

@@ -57,8 +59,10 @@ instance ( HasServer api context
5759
type ServerT (AuthProtect tag :> api) m =
5860
AuthServerData (AuthProtect tag) -> ServerT api m
5961

60-
route Proxy context subserver = WithRequest $ \ request ->
61-
route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck request)
62+
route Proxy context subserver =
63+
route (Proxy :: Proxy api) context (subserver `addAuthCheck` withRequest authCheck)
6264
where
65+
authHandler :: Request -> Handler (AuthServerData (AuthProtect tag))
6366
authHandler = unAuthHandler (getContextEntry context)
64-
authCheck = fmap (either FailFatal Route) . runExceptT . authHandler
67+
authCheck :: Request -> DelayedIO (AuthServerData (AuthProtect tag))
68+
authCheck = (>>= either delayedFailFatal return) . liftIO . runExceptT . authHandler

servant-server/src/Servant/Server/Internal.hs

Lines changed: 58 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module Servant.Server.Internal
2222
, module Servant.Server.Internal.ServantErr
2323
) where
2424

25+
import Control.Monad.Trans (liftIO)
2526
import qualified Data.ByteString as B
2627
import qualified Data.ByteString.Char8 as BC8
2728
import qualified Data.ByteString.Lazy as BL
@@ -70,7 +71,11 @@ import Servant.Server.Internal.ServantErr
7071
class HasServer layout context where
7172
type ServerT layout (m :: * -> *) :: *
7273

73-
route :: Proxy layout -> Context context -> Delayed (Server layout) -> Router
74+
route ::
75+
Proxy layout
76+
-> Context context
77+
-> Delayed env (Server layout)
78+
-> Router env
7479

7580
type Server layout = ServerT layout Handler
7681

@@ -92,7 +97,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
9297
type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
9398

9499
route Proxy context server = choice (route pa context ((\ (a :<|> _) -> a) <$> server))
95-
(route pb context ((\ (_ :<|> b) -> b) <$> server))
100+
(route pb context ((\ (_ :<|> b) -> b) <$> server))
96101
where pa = Proxy :: Proxy a
97102
pb = Proxy :: Proxy b
98103

@@ -120,12 +125,12 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout context)
120125
a -> ServerT sublayout m
121126

122127
route Proxy context d =
123-
DynamicRouter $ \ first ->
128+
CaptureRouter $
124129
route (Proxy :: Proxy sublayout)
125130
context
126-
(addCapture d $ case parseUrlPieceMaybe first :: Maybe a of
127-
Nothing -> return $ Fail err400
128-
Just v -> return $ Route v
131+
(addCapture d $ \ txt -> case parseUrlPieceMaybe txt :: Maybe a of
132+
Nothing -> delayedFail err400
133+
Just v -> return v
129134
)
130135

131136
allowedMethodHead :: Method -> Request -> Bool
@@ -144,41 +149,41 @@ processMethodRouter handleA status method headers request = case handleA of
144149
bdy = if allowedMethodHead method request then "" else body
145150
hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
146151

147-
methodCheck :: Method -> Request -> IO (RouteResult ())
152+
methodCheck :: Method -> Request -> DelayedIO ()
148153
methodCheck method request
149-
| allowedMethod method request = return $ Route ()
150-
| otherwise = return $ Fail err405
154+
| allowedMethod method request = return ()
155+
| otherwise = delayedFail err405
151156

152-
acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> IO (RouteResult ())
157+
acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
153158
acceptCheck proxy accH
154-
| canHandleAcceptH proxy (AcceptHeader accH) = return $ Route ()
155-
| otherwise = return $ FailFatal err406
159+
| canHandleAcceptH proxy (AcceptHeader accH) = return ()
160+
| otherwise = delayedFailFatal err406
156161

157162
methodRouter :: (AllCTRender ctypes a)
158163
=> Method -> Proxy ctypes -> Status
159-
-> Delayed (Handler a)
160-
-> Router
164+
-> Delayed env (Handler a)
165+
-> Router env
161166
methodRouter method proxy status action = leafRouter route'
162167
where
163-
route' request respond =
168+
route' env request respond =
164169
let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
165170
in runAction (action `addMethodCheck` methodCheck method request
166171
`addAcceptCheck` acceptCheck proxy accH
167-
) respond $ \ output -> do
172+
) env request respond $ \ output -> do
168173
let handleA = handleAcceptH proxy (AcceptHeader accH) output
169174
processMethodRouter handleA status method Nothing request
170175

171176
methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
172177
=> Method -> Proxy ctypes -> Status
173-
-> Delayed (Handler (Headers h v))
174-
-> Router
178+
-> Delayed env (Handler (Headers h v))
179+
-> Router env
175180
methodRouterHeaders method proxy status action = leafRouter route'
176181
where
177-
route' request respond =
182+
route' env request respond =
178183
let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
179184
in runAction (action `addMethodCheck` methodCheck method request
180185
`addAcceptCheck` acceptCheck proxy accH
181-
) respond $ \ output -> do
186+
) env request respond $ \ output -> do
182187
let headers = getHeaders output
183188
handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
184189
processMethodRouter handleA status method (Just headers) request
@@ -230,8 +235,8 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
230235
type ServerT (Header sym a :> sublayout) m =
231236
Maybe a -> ServerT sublayout m
232237

233-
route Proxy context subserver = WithRequest $ \ request ->
234-
let mheader = parseHeaderMaybe =<< lookup str (requestHeaders request)
238+
route Proxy context subserver =
239+
let mheader req = parseHeaderMaybe =<< lookup str (requestHeaders req)
235240
in route (Proxy :: Proxy sublayout) context (passToServer subserver mheader)
236241
where str = fromString $ symbolVal (Proxy :: Proxy sym)
237242

@@ -262,10 +267,10 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
262267
type ServerT (QueryParam sym a :> sublayout) m =
263268
Maybe a -> ServerT sublayout m
264269

265-
route Proxy context subserver = WithRequest $ \ request ->
266-
let querytext = parseQueryText $ rawQueryString request
267-
param =
268-
case lookup paramname querytext of
270+
route Proxy context subserver =
271+
let querytext r = parseQueryText $ rawQueryString r
272+
param r =
273+
case lookup paramname (querytext r) of
269274
Nothing -> Nothing -- param absent from the query string
270275
Just Nothing -> Nothing -- param present with no value -> Nothing
271276
Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
@@ -298,13 +303,13 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
298303
type ServerT (QueryParams sym a :> sublayout) m =
299304
[a] -> ServerT sublayout m
300305

301-
route Proxy context subserver = WithRequest $ \ request ->
302-
let querytext = parseQueryText $ rawQueryString request
306+
route Proxy context subserver =
307+
let querytext r = parseQueryText $ rawQueryString r
303308
-- if sym is "foo", we look for query string parameters
304309
-- named "foo" or "foo[]" and call parseQueryParam on the
305310
-- corresponding values
306-
parameters = filter looksLikeParam querytext
307-
values = mapMaybe (convert . snd) parameters
311+
parameters r = filter looksLikeParam (querytext r)
312+
values r = mapMaybe (convert . snd) (parameters r)
308313
in route (Proxy :: Proxy sublayout) context (passToServer subserver values)
309314
where paramname = cs $ symbolVal (Proxy :: Proxy sym)
310315
looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
@@ -329,9 +334,9 @@ instance (KnownSymbol sym, HasServer sublayout context)
329334
type ServerT (QueryFlag sym :> sublayout) m =
330335
Bool -> ServerT sublayout m
331336

332-
route Proxy context subserver = WithRequest $ \ request ->
333-
let querytext = parseQueryText $ rawQueryString request
334-
param = case lookup paramname querytext of
337+
route Proxy context subserver =
338+
let querytext r = parseQueryText $ rawQueryString r
339+
param r = case lookup paramname (querytext r) of
335340
Just Nothing -> True -- param is there, with no value
336341
Just (Just v) -> examine v -- param with a value
337342
Nothing -> False -- param not in the query string
@@ -352,8 +357,8 @@ instance HasServer Raw context where
352357

353358
type ServerT Raw m = Application
354359

355-
route Proxy _ rawApplication = RawRouter $ \ request respond -> do
356-
r <- runDelayed rawApplication
360+
route Proxy _ rawApplication = RawRouter $ \ env request respond -> do
361+
r <- runDelayed rawApplication env request
357362
case r of
358363
Route app -> app request (respond . Route)
359364
Fail a -> respond $ Fail a
@@ -386,22 +391,22 @@ instance ( AllCTUnrender list a, HasServer sublayout context
386391
type ServerT (ReqBody list a :> sublayout) m =
387392
a -> ServerT sublayout m
388393

389-
route Proxy context subserver = WithRequest $ \ request ->
390-
route (Proxy :: Proxy sublayout) context (addBodyCheck subserver (bodyCheck request))
394+
route Proxy context subserver =
395+
route (Proxy :: Proxy sublayout) context (addBodyCheck subserver bodyCheck)
391396
where
392-
bodyCheck request = do
397+
bodyCheck = withRequest $ \ request -> do
393398
-- See HTTP RFC 2616, section 7.2.1
394399
-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
395400
-- See also "W3C Internet Media Type registration, consistency of use"
396401
-- http://www.w3.org/2001/tag/2002/0129-mime
397402
let contentTypeH = fromMaybe "application/octet-stream"
398403
$ lookup hContentType $ requestHeaders request
399404
mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
400-
<$> lazyRequestBody request
405+
<$> liftIO (lazyRequestBody request)
401406
case mrqbody of
402-
Nothing -> return $ FailFatal err415
403-
Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
404-
Just (Right v) -> return $ Route v
407+
Nothing -> delayedFailFatal err415
408+
Just (Left e) -> delayedFailFatal err400 { errBody = cs e }
409+
Just (Right v) -> return v
405410

406411
-- | Make sure the incoming request starts with @"/path"@, strip it and
407412
-- pass the rest of the request path to @sublayout@.
@@ -418,28 +423,28 @@ instance (KnownSymbol path, HasServer sublayout context) => HasServer (path :> s
418423
instance HasServer api context => HasServer (RemoteHost :> api) context where
419424
type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
420425

421-
route Proxy context subserver = WithRequest $ \req ->
422-
route (Proxy :: Proxy api) context (passToServer subserver $ remoteHost req)
426+
route Proxy context subserver =
427+
route (Proxy :: Proxy api) context (passToServer subserver remoteHost)
423428

424429
instance HasServer api context => HasServer (IsSecure :> api) context where
425430
type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
426431

427-
route Proxy context subserver = WithRequest $ \req ->
428-
route (Proxy :: Proxy api) context (passToServer subserver $ secure req)
432+
route Proxy context subserver =
433+
route (Proxy :: Proxy api) context (passToServer subserver secure)
429434

430435
where secure req = if isSecure req then Secure else NotSecure
431436

432437
instance HasServer api context => HasServer (Vault :> api) context where
433438
type ServerT (Vault :> api) m = Vault -> ServerT api m
434439

435-
route Proxy context subserver = WithRequest $ \req ->
436-
route (Proxy :: Proxy api) context (passToServer subserver $ vault req)
440+
route Proxy context subserver =
441+
route (Proxy :: Proxy api) context (passToServer subserver vault)
437442

438443
instance HasServer api context => HasServer (HttpVersion :> api) context where
439444
type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
440445

441-
route Proxy context subserver = WithRequest $ \req ->
442-
route (Proxy :: Proxy api) context (passToServer subserver $ httpVersion req)
446+
route Proxy context subserver =
447+
route (Proxy :: Proxy api) context (passToServer subserver httpVersion)
443448

444449
-- | Basic Authentication
445450
instance ( KnownSymbol realm
@@ -450,12 +455,12 @@ instance ( KnownSymbol realm
450455

451456
type ServerT (BasicAuth realm usr :> api) m = usr -> ServerT api m
452457

453-
route Proxy context subserver = WithRequest $ \ request ->
454-
route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck request)
458+
route Proxy context subserver =
459+
route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck)
455460
where
456461
realm = BC8.pack $ symbolVal (Proxy :: Proxy realm)
457462
basicAuthContext = getContextEntry context
458-
authCheck req = runBasicAuth req realm basicAuthContext
463+
authCheck = withRequest $ \ req -> runBasicAuth req realm basicAuthContext
459464

460465
-- * helpers
461466

servant-server/src/Servant/Server/Internal/BasicAuth.hs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
module Servant.Server.Internal.BasicAuth where
77

88
import Control.Monad (guard)
9+
import Control.Monad.Trans (liftIO)
910
import qualified Data.ByteString as BS
1011
import Data.ByteString.Base64 (decodeLenient)
1112
import Data.Monoid ((<>))
@@ -57,13 +58,13 @@ decodeBAHdr req = do
5758

5859
-- | Run and check basic authentication, returning the appropriate http error per
5960
-- the spec.
60-
runBasicAuth :: Request -> BS.ByteString -> BasicAuthCheck usr -> IO (RouteResult usr)
61+
runBasicAuth :: Request -> BS.ByteString -> BasicAuthCheck usr -> DelayedIO usr
6162
runBasicAuth req realm (BasicAuthCheck ba) =
6263
case decodeBAHdr req of
6364
Nothing -> plzAuthenticate
64-
Just e -> ba e >>= \res -> case res of
65+
Just e -> liftIO (ba e) >>= \res -> case res of
6566
BadPassword -> plzAuthenticate
6667
NoSuchUser -> plzAuthenticate
67-
Unauthorized -> return $ FailFatal err403
68-
Authorized usr -> return $ Route usr
69-
where plzAuthenticate = return $ FailFatal err401 { errHeaders = [mkBAChallengerHdr realm] }
68+
Unauthorized -> delayedFailFatal err403
69+
Authorized usr -> return usr
70+
where plzAuthenticate = delayedFailFatal err401 { errHeaders = [mkBAChallengerHdr realm] }

0 commit comments

Comments
 (0)