@@ -2,6 +2,7 @@
2{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE PolyKinds #-}
7{-# LANGUAGE PolyKinds #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
@@ -11,17 +12,20 @@
11{-# LANGUAGE OverlappingInstances #-}
12{-# LANGUAGE OverlappingInstances #-}
14−module Servant.Server.Internal where
15+module Servant.Server.Internal
16+ ( module Servant.Server.Internal
17+ , module Servant.Server.Internal.PathInfo
18+ , module Servant.Server.Internal.Router
19+ , module Servant.Server.Internal.RoutingApplication
20+ , module Servant.Server.Internal.ServantErr
16#if !MIN_VERSION_base(4,8,0)
23#if !MIN_VERSION_base(4,8,0)
17import Control.Applicative ((<$>))
24import Control.Applicative ((<$>))
18−import Data.Monoid (Monoid, mappend, mempty)
20−import Control.Monad.Trans.Either (EitherT, runEitherT)
26+import Control.Monad.Trans.Either (EitherT)
21import qualified Data.ByteString as B
27import qualified Data.ByteString as B
22−import qualified Data.ByteString.Lazy as BL
28+import qualified Data.Map as M
23−import Data.IORef (newIORef, readIORef, writeIORef)
24−import Data.List (unfoldr)
25import Data.Maybe (catMaybes, fromMaybe)
29import Data.Maybe (catMaybes, fromMaybe)
26import Data.String (fromString)
30import Data.String (fromString)
27import Data.String.Conversions (cs, (<>))
31import Data.String.Conversions (cs, (<>))
@@ -31,12 +35,9 @@ import Data.Text.Encoding (decodeUtf8, encodeUtf8)
31import Data.Typeable
35import Data.Typeable
32import GHC.TypeLits (KnownSymbol, symbolVal)
36import GHC.TypeLits (KnownSymbol, symbolVal)
33import Network.HTTP.Types hiding (Header, ResponseHeaders)
37import Network.HTTP.Types hiding (Header, ResponseHeaders)
34−import Network.Wai (Application, Request, Response,
38+import Network.Wai (Application, lazyRequestBody,
35− ResponseReceived, lazyRequestBody,
36− pathInfo, rawQueryString,
39+ rawQueryString, requestHeaders,
37− requestBody, requestHeaders,
38− requestMethod, responseLBS,
40+ requestMethod, responseLBS)
40import Servant.API ((:<|>) (..), (:>), Capture,
41import Servant.API ((:<|>) (..), (:>), Capture,
41 Delete, Get, Header,
42 Delete, Get, Header,
42 MatrixFlag, MatrixParam, MatrixParams,
43 MatrixFlag, MatrixParam, MatrixParams,
@@ -50,136 +51,15 @@ import Servant.API.ResponseHeaders (Headers, getResponse, GetHeaders,
50 getHeaders)
51 getHeaders)
51import Servant.Common.Text (FromText, fromText)
52import Servant.Common.Text (FromText, fromText)
54+import Servant.Server.Internal.PathInfo
55+import Servant.Server.Internal.Router
56+import Servant.Server.Internal.RoutingApplication
53import Servant.Server.Internal.ServantErr
57import Servant.Server.Internal.ServantErr
55−data ReqBodyState = Uncalled
56− | Called !B.ByteString
60−toApplication :: RoutingApplication -> Application
61−toApplication ra request respond = do
62− reqBodyRef <- newIORef Uncalled
63− -- We may need to consume the requestBody more than once. In order to
64− -- maintain the illusion that 'requestBody' works as expected,
65− -- 'ReqBodyState' is introduced, and the complete body is memoized and
66− -- returned as many times as requested with empty "Done" marker chunks in
68− -- See https://github.com/haskell-servant/servant/issues/3
70− ior <- readIORef reqBodyRef
73− r <- BL.toStrict <$> strictRequestBody request
74− writeIORef reqBodyRef $ Done r
77− writeIORef reqBodyRef $ Done bs
80− writeIORef reqBodyRef $ Called bs
83− ra request{ requestBody = memoReqBody } (routingRespond . routeResult)
85− routingRespond :: Either RouteMismatch Response -> IO ResponseReceived
86− routingRespond (Left NotFound) =
87− respond $ responseLBS notFound404 [] "not found"
88− routingRespond (Left WrongMethod) =
89− respond $ responseLBS methodNotAllowed405 [] "method not allowed"
90− routingRespond (Left (InvalidBody err)) =
91− respond $ responseLBS badRequest400 [] $ fromString $ "invalid request body: " ++ err
92− routingRespond (Left UnsupportedMediaType) =
93− respond $ responseLBS unsupportedMediaType415 [] "unsupported media type"
94− routingRespond (Left (HttpError status body)) =
95− respond $ responseLBS status [] $ fromMaybe (BL.fromStrict $ statusMessage status) body
96− routingRespond (Right response) =
99−-- Note that the ordering of the constructors has great significance! It
100−-- determines the Ord instance and, consequently, the monoid instance.
103− NotFound -- ^ the usual "not found" error
104− | WrongMethod -- ^ a more informative "you just got the HTTP method wrong" error
105− | UnsupportedMediaType -- ^ request body has unsupported media type
106− | InvalidBody String -- ^ an even more informative "your json request body wasn't valid" error
107− | HttpError Status (Maybe BL.ByteString) -- ^ an even even more informative arbitrary HTTP response code error.
108− deriving (Eq, Ord, Show)
110−instance Monoid RouteMismatch where
112− -- The following isn't great, since it picks @InvalidBody@ based on
113− -- alphabetical ordering, but any choice would be arbitrary.
115− -- "As one judge said to the other, 'Be just and if you can't be just, be
116− -- arbitrary'" -- William Burroughs
120−-- | A wrapper around @'Either' 'RouteMismatch' a@.
121−newtype RouteResult a =
122− RR { routeResult :: Either RouteMismatch a }
125−failWith :: RouteMismatch -> RouteResult a
128−succeedWith :: a -> RouteResult a
129−succeedWith = RR . Right
131−isMismatch :: RouteResult a -> Bool
132−isMismatch (RR (Left _)) = True
135−-- | Like `null . pathInfo`, but works with redundant trailing slashes.
136−pathIsEmpty :: Request -> Bool
137−pathIsEmpty = f . processedPathInfo
143−-- | If we get a `Right`, it has precedence over everything else.
145−-- This in particular means that if we could get several 'Right's,
146−-- only the first we encounter would be taken into account.
147−instance Monoid (RouteResult a) where
148− mempty = RR $ Left mempty
150− RR (Left x) `mappend` RR (Left y) = RR $ Left (x <> y)
151− RR (Left _) `mappend` RR (Right y) = RR $ Right y
154−type RoutingApplication =
155− Request -- ^ the request, the field 'pathInfo' may be modified by url routing
156− -> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived
158−splitMatrixParameters :: Text -> (Text, Text)
159−splitMatrixParameters = T.break (== ';')
161−parsePathInfo :: Request -> [Text]
162−parsePathInfo = filter (/= "") . mergePairs . map splitMatrixParameters . pathInfo
163− where mergePairs = concat . unfoldr pairToList
164− pairToList [] = Nothing
165− pairToList ((a, b):xs) = Just ([a, b], xs)
167−-- | Returns a processed pathInfo from the request.
169−-- In order to handle matrix parameters in the request correctly, the raw pathInfo needs to be
170−-- processed, so routing works as intended. Therefor this function should be used to access
171−-- the pathInfo for routing purposes.
172−processedPathInfo :: Request -> [Text]
173−processedPathInfo r =
175− (x:xs) | T.head x == ';' -> xs
177− where pinfo = parsePathInfo r
179class HasServer layout where
59class HasServer layout where
180 type ServerT layout (m :: * -> *) :: *
60 type ServerT layout (m :: * -> *) :: *
182− route :: Proxy layout -> Server layout -> RoutingApplication
62+ route :: Proxy layout -> IO (RouteResult (Server layout)) -> Router
184type Server layout = ServerT layout (EitherT ServantErr IO)
64type Server layout = ServerT layout (EitherT ServantErr IO)
@@ -200,12 +80,8 @@ instance (HasServer a, HasServer b) => HasServer (a :<|> b) where
201 type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
81 type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
203− route Proxy (a :<|> b) request respond =
83+ route Proxy server = choice (route pa (extractL <$> server))
204− route pa a request $ \mResponse ->
84+ (route pb (extractR <$> server))
205− if isMismatch mResponse
206− then route pb b request $ \mResponse' -> respond (mResponse <> mResponse')
207− else respond mResponse
209 where pa = Proxy :: Proxy a
85 where pa = Proxy :: Proxy a
210 pb = Proxy :: Proxy b
86 pb = Proxy :: Proxy b
@@ -235,18 +111,65 @@ instance (KnownSymbol capture, FromText a, HasServer sublayout)
235 type ServerT (Capture capture a :> sublayout) m =
111 type ServerT (Capture capture a :> sublayout) m =
236 a -> ServerT sublayout m
112 a -> ServerT sublayout m
238− route Proxy subserver request respond = case processedPathInfo request of
114+ route Proxy subserver =
239− (first : rest)
115+ DynamicRouter $ \ first ->
116+ route (Proxy :: Proxy sublayout)
240− -> case captured captureProxy first of
117+ (case captured captureProxy first of
241− Nothing -> respond $ failWith NotFound
118+ Nothing -> return $ failWith NotFound
242− Just v -> route (Proxy :: Proxy sublayout) (subserver v) request{
119+ Just v -> feedTo subserver v)
245− _ -> respond $ failWith NotFound
247 where captureProxy = Proxy :: Proxy (Capture capture a)
120 where captureProxy = Proxy :: Proxy (Capture capture a)
123+methodRouter :: (AllCTRender ctypes a)
124+ => Method -> Proxy ctypes -> Status
125+ -> IO (RouteResult (EitherT ServantErr IO a))
127+methodRouter method proxy status action = LeafRouter route'
129+ route' request respond
130+ | pathIsEmpty request && requestMethod request == method = do
131+ runAction action respond $ \ output -> do
132+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
133+ case handleAcceptH proxy (AcceptHeader accH) output of
134+ Nothing -> failWith UnsupportedMediaType
135+ Just (contentT, body) -> succeedWith $
136+ responseLBS status [ ("Content-Type" , cs contentT)] body
137+ | pathIsEmpty request && requestMethod request /= method =
138+ respond $ failWith WrongMethod
139+ | otherwise = respond $ failWith NotFound
141+methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
142+ => Method -> Proxy ctypes -> Status
143+ -> IO (RouteResult (EitherT ServantErr IO (Headers h v)))
145+methodRouterHeaders method proxy status action = LeafRouter route'
147+ route' request respond
148+ | pathIsEmpty request && requestMethod request == method = do
149+ runAction action respond $ \ output -> do
150+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
151+ headers = getHeaders output
152+ case handleAcceptH proxy (AcceptHeader accH) (getResponse output) of
153+ Nothing -> failWith UnsupportedMediaType
154+ Just (contentT, body) -> succeedWith $
155+ responseLBS status ( ("Content-Type" , cs contentT) : headers) body
156+ | pathIsEmpty request && requestMethod request /= method =
157+ respond $ failWith WrongMethod
158+ | otherwise = respond $ failWith NotFound
160+methodRouterEmpty :: Method
161+ -> IO (RouteResult (EitherT ServantErr IO ()))
163+methodRouterEmpty method action = LeafRouter route'
165+ route' request respond
166+ | pathIsEmpty request && requestMethod request == method = do
167+ runAction action respond $ \ () ->
168+ succeedWith $ responseLBS noContent204 [] ""
169+ | pathIsEmpty request && requestMethod request /= method =
170+ respond $ failWith WrongMethod
171+ | otherwise = respond $ failWith NotFound
250-- | If you have a 'Delete' endpoint in your API,
173-- | If you have a 'Delete' endpoint in your API,
251-- the handler for this endpoint is meant to delete
174-- the handler for this endpoint is meant to delete
252-- a resource.
175-- a resource.
@@ -267,20 +190,7 @@ instance
268 type ServerT (Delete ctypes a) m = m a
191 type ServerT (Delete ctypes a) m = m a
270− route Proxy action request respond
193+ route Proxy = methodRouter methodDelete (Proxy :: Proxy ctypes) ok200
271− | pathIsEmpty request && requestMethod request == methodDelete = do
272− e <- runEitherT action
275− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
276− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
277− Nothing -> failWith UnsupportedMediaType
278− Just (contentT, body) -> succeedWith $
279− responseLBS status200 [ ("Content-Type" , cs contentT)] body
280− Left err -> succeedWith $ responseServantErr err
281− | pathIsEmpty request && requestMethod request /= methodDelete =
282− respond $ failWith WrongMethod
283− | otherwise = respond $ failWith NotFound
286#if MIN_VERSION_base(4,8,0)
196#if MIN_VERSION_base(4,8,0)
@@ -290,15 +200,7 @@ instance
291 type ServerT (Delete ctypes ()) m = m ()
201 type ServerT (Delete ctypes ()) m = m ()
293− route Proxy action request respond
203+ route Proxy = methodRouterEmpty methodDelete
294− | pathIsEmpty request && requestMethod request == methodDelete = do
295− e <- runEitherT action
296− respond . succeedWith $ case e of
297− Right () -> responseLBS noContent204 [] ""
298− Left err -> responseServantErr err
299− | pathIsEmpty request && requestMethod request /= methodDelete =
300− respond $ failWith WrongMethod
301− | otherwise = respond $ failWith NotFound
303-- Add response headers
205-- Add response headers
@@ -310,21 +212,7 @@ instance
311 type ServerT (Delete ctypes (Headers h v)) m = m (Headers h v)
213 type ServerT (Delete ctypes (Headers h v)) m = m (Headers h v)
313− route Proxy action request respond
215+ route Proxy = methodRouterHeaders methodDelete (Proxy :: Proxy ctypes) ok200
314− | pathIsEmpty request && requestMethod request == methodDelete = do
315− e <- runEitherT action
318− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
319− headers = getHeaders output
320− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
321− Nothing -> failWith UnsupportedMediaType
322− Just (contentT, body) -> succeedWith $
323− responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
324− Left err -> succeedWith $ responseServantErr err
325− | pathIsEmpty request && requestMethod request /= methodDelete =
326− respond $ failWith WrongMethod
327− | otherwise = respond $ failWith NotFound
329-- | When implementing the handler for a 'Get' endpoint,
217-- | When implementing the handler for a 'Get' endpoint,
330-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Post.Post'
218-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Post.Post'
@@ -347,20 +235,7 @@ instance
348 type ServerT (Get ctypes a) m = m a
236 type ServerT (Get ctypes a) m = m a
350− route Proxy action request respond
238+ route Proxy = methodRouter methodGet (Proxy :: Proxy ctypes) ok200
351− | pathIsEmpty request && requestMethod request == methodGet = do
352− e <- runEitherT action
355− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
356− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
357− Nothing -> failWith UnsupportedMediaType
358− Just (contentT, body) -> succeedWith $
359− responseLBS ok200 [ ("Content-Type" , cs contentT)] body
360− Left err -> succeedWith $ responseServantErr err
361− | pathIsEmpty request && requestMethod request /= methodGet =
362− respond $ failWith WrongMethod
363− | otherwise = respond $ failWith NotFound
365-- '()' ==> 204 No Content
240-- '()' ==> 204 No Content
@@ -371,15 +246,7 @@ instance
372 type ServerT (Get ctypes ()) m = m ()
247 type ServerT (Get ctypes ()) m = m ()
374− route Proxy action request respond
249+ route Proxy = methodRouterEmpty methodGet
375− | pathIsEmpty request && requestMethod request == methodGet = do
376− e <- runEitherT action
377− respond . succeedWith $ case e of
378− Right () -> responseLBS noContent204 [] ""
379− Left err -> responseServantErr err
380− | pathIsEmpty request && requestMethod request /= methodGet =
381− respond $ failWith WrongMethod
382− | otherwise = respond $ failWith NotFound
384-- Add response headers
251-- Add response headers
@@ -391,21 +258,7 @@ instance
392 type ServerT (Get ctypes (Headers h v)) m = m (Headers h v)
259 type ServerT (Get ctypes (Headers h v)) m = m (Headers h v)
394− route Proxy action request respond
261+ route Proxy = methodRouterHeaders methodGet (Proxy :: Proxy ctypes) ok200
395− | pathIsEmpty request && requestMethod request == methodGet = do
396− e <- runEitherT action
399− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
400− headers = getHeaders output
401− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
402− Nothing -> failWith UnsupportedMediaType
403− Just (contentT, body) -> succeedWith $
404− responseLBS ok200 ( ("Content-Type" , cs contentT) : headers) body
405− Left err -> succeedWith $ responseServantErr err
406− | pathIsEmpty request && requestMethod request /= methodGet =
407− respond $ failWith WrongMethod
408− | otherwise = respond $ failWith NotFound
410-- | If you use 'Header' in one of the endpoints for your API,
263-- | If you use 'Header' in one of the endpoints for your API,
411-- this automatically requires your server-side handler to be a function
264-- this automatically requires your server-side handler to be a function
@@ -433,11 +286,10 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
433 type ServerT (Header sym a :> sublayout) m =
286 type ServerT (Header sym a :> sublayout) m =
434 Maybe a -> ServerT sublayout m
287 Maybe a -> ServerT sublayout m
436− route Proxy subserver request respond = do
289+ route Proxy subserver = WithRequest $ \ request ->
437 let mheader = fromText . decodeUtf8 =<< lookup str (requestHeaders request)
290 let mheader = fromText . decodeUtf8 =<< lookup str (requestHeaders request)
438− route (Proxy :: Proxy sublayout) (subserver mheader) request respond
291+ in route (Proxy :: Proxy sublayout) (feedTo subserver mheader)
440− where str = fromString $ symbolVal (Proxy :: Proxy sym)
292+ where str = fromString $ symbolVal (Proxy :: Proxy sym)
442-- | When implementing the handler for a 'Post' endpoint,
294-- | When implementing the handler for a 'Post' endpoint,
443-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
295-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
@@ -461,20 +313,7 @@ instance
462 type ServerT (Post ctypes a) m = m a
314 type ServerT (Post ctypes a) m = m a
464− route Proxy action request respond
316+ route Proxy = methodRouter methodPost (Proxy :: Proxy ctypes) created201
465− | pathIsEmpty request && requestMethod request == methodPost = do
466− e <- runEitherT action
469− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
470− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
471− Nothing -> failWith UnsupportedMediaType
472− Just (contentT, body) -> succeedWith $
473− responseLBS status201 [ ("Content-Type" , cs contentT)] body
474− Left err -> succeedWith $ responseServantErr err
475− | pathIsEmpty request && requestMethod request /= methodPost =
476− respond $ failWith WrongMethod
477− | otherwise = respond $ failWith NotFound
480#if MIN_VERSION_base(4,8,0)
319#if MIN_VERSION_base(4,8,0)
@@ -484,15 +323,7 @@ instance
485 type ServerT (Post ctypes ()) m = m ()
324 type ServerT (Post ctypes ()) m = m ()
487− route Proxy action request respond
326+ route Proxy = methodRouterEmpty methodPost
488− | pathIsEmpty request && requestMethod request == methodPost = do
489− e <- runEitherT action
490− respond . succeedWith $ case e of
491− Right () -> responseLBS noContent204 [] ""
492− Left err -> responseServantErr err
493− | pathIsEmpty request && requestMethod request /= methodPost =
494− respond $ failWith WrongMethod
495− | otherwise = respond $ failWith NotFound
497-- Add response headers
328-- Add response headers
@@ -504,21 +335,7 @@ instance
505 type ServerT (Post ctypes (Headers h v)) m = m (Headers h v)
336 type ServerT (Post ctypes (Headers h v)) m = m (Headers h v)
507− route Proxy action request respond
338+ route Proxy = methodRouterHeaders methodPost (Proxy :: Proxy ctypes) created201
508− | pathIsEmpty request && requestMethod request == methodPost = do
509− e <- runEitherT action
512− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
513− headers = getHeaders output
514− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
515− Nothing -> failWith UnsupportedMediaType
516− Just (contentT, body) -> succeedWith $
517− responseLBS status201 ( ("Content-Type" , cs contentT) : headers) body
518− Left err -> succeedWith $ responseServantErr err
519− | pathIsEmpty request && requestMethod request /= methodPost =
520− respond $ failWith WrongMethod
521− | otherwise = respond $ failWith NotFound
523-- | When implementing the handler for a 'Put' endpoint,
340-- | When implementing the handler for a 'Put' endpoint,
524-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
341-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
@@ -541,20 +358,7 @@ instance
542 type ServerT (Put ctypes a) m = m a
359 type ServerT (Put ctypes a) m = m a
544− route Proxy action request respond
361+ route Proxy = methodRouter methodPut (Proxy :: Proxy ctypes) ok200
545− | pathIsEmpty request && requestMethod request == methodPut = do
546− e <- runEitherT action
549− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
550− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
551− Nothing -> failWith UnsupportedMediaType
552− Just (contentT, body) -> succeedWith $
553− responseLBS status200 [ ("Content-Type" , cs contentT)] body
554− Left err -> succeedWith $ responseServantErr err
555− | pathIsEmpty request && requestMethod request /= methodPut =
556− respond $ failWith WrongMethod
557− | otherwise = respond $ failWith NotFound
560#if MIN_VERSION_base(4,8,0)
364#if MIN_VERSION_base(4,8,0)
@@ -564,15 +368,7 @@ instance
565 type ServerT (Put ctypes ()) m = m ()
369 type ServerT (Put ctypes ()) m = m ()
567− route Proxy action request respond
371+ route Proxy = methodRouterEmpty methodPut
568− | pathIsEmpty request && requestMethod request == methodPut = do
569− e <- runEitherT action
570− respond . succeedWith $ case e of
571− Right () -> responseLBS noContent204 [] ""
572− Left err -> responseServantErr err
573− | pathIsEmpty request && requestMethod request /= methodPut =
574− respond $ failWith WrongMethod
575− | otherwise = respond $ failWith NotFound
577-- Add response headers
373-- Add response headers
@@ -584,21 +380,7 @@ instance
585 type ServerT (Put ctypes (Headers h v)) m = m (Headers h v)
381 type ServerT (Put ctypes (Headers h v)) m = m (Headers h v)
587− route Proxy action request respond
383+ route Proxy = methodRouterHeaders methodPut (Proxy :: Proxy ctypes) ok200
588− | pathIsEmpty request && requestMethod request == methodPut = do
589− e <- runEitherT action
592− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
593− headers = getHeaders output
594− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
595− Nothing -> failWith UnsupportedMediaType
596− Just (contentT, body) -> succeedWith $
597− responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
598− Left err -> succeedWith $ responseServantErr err
599− | pathIsEmpty request && requestMethod request /= methodPut =
600− respond $ failWith WrongMethod
601− | otherwise = respond $ failWith NotFound
603-- | When implementing the handler for a 'Patch' endpoint,
385-- | When implementing the handler for a 'Patch' endpoint,
604-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
386-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
@@ -619,20 +401,7 @@ instance
620 type ServerT (Patch ctypes a) m = m a
402 type ServerT (Patch ctypes a) m = m a
622− route Proxy action request respond
404+ route Proxy = methodRouter methodPatch (Proxy :: Proxy ctypes) ok200
623− | pathIsEmpty request && requestMethod request == methodPatch = do
624− e <- runEitherT action
627− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
628− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
629− Nothing -> failWith UnsupportedMediaType
630− Just (contentT, body) -> succeedWith $
631− responseLBS status200 [ ("Content-Type" , cs contentT)] body
632− Left err -> succeedWith $ responseServantErr err
633− | pathIsEmpty request && requestMethod request /= methodPatch =
634− respond $ failWith WrongMethod
635− | otherwise = respond $ failWith NotFound
638#if MIN_VERSION_base(4,8,0)
407#if MIN_VERSION_base(4,8,0)
@@ -642,15 +411,7 @@ instance
643 type ServerT (Patch ctypes ()) m = m ()
412 type ServerT (Patch ctypes ()) m = m ()
645− route Proxy action request respond
414+ route Proxy = methodRouterEmpty methodPatch
646− | pathIsEmpty request && requestMethod request == methodPatch = do
647− e <- runEitherT action
648− respond . succeedWith $ case e of
649− Right () -> responseLBS noContent204 [] ""
650− Left err -> responseServantErr err
651− | pathIsEmpty request && requestMethod request /= methodPatch =
652− respond $ failWith WrongMethod
653− | otherwise = respond $ failWith NotFound
655-- Add response headers
416-- Add response headers
@@ -662,21 +423,7 @@ instance
663 type ServerT (Patch ctypes (Headers h v)) m = m (Headers h v)
424 type ServerT (Patch ctypes (Headers h v)) m = m (Headers h v)
665− route Proxy action request respond
426+ route Proxy = methodRouterHeaders methodPatch (Proxy :: Proxy ctypes) ok200
666− | pathIsEmpty request && requestMethod request == methodPatch = do
667− e <- runEitherT action
669− Right outpatch -> do
670− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
671− headers = getHeaders outpatch
672− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse outpatch) of
673− Nothing -> failWith UnsupportedMediaType
674− Just (contentT, body) -> succeedWith $
675− responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
676− Left err -> succeedWith $ responseServantErr err
677− | pathIsEmpty request && requestMethod request /= methodPatch =
678− respond $ failWith WrongMethod
679− | otherwise = respond $ failWith NotFound
681-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
428-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
682-- this automatically requires your server-side handler to be a function
429-- this automatically requires your server-side handler to be a function
@@ -705,7 +452,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
705 type ServerT (QueryParam sym a :> sublayout) m =
452 type ServerT (QueryParam sym a :> sublayout) m =
706 Maybe a -> ServerT sublayout m
453 Maybe a -> ServerT sublayout m
708− route Proxy subserver request respond = do
455+ route Proxy subserver = WithRequest $ \ request ->
709 let querytext = parseQueryText $ rawQueryString request
456 let querytext = parseQueryText $ rawQueryString request
711 case lookup paramname querytext of
458 case lookup paramname querytext of
@@ -713,9 +460,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
713 Just Nothing -> Nothing -- param present with no value -> Nothing
460 Just Nothing -> Nothing -- param present with no value -> Nothing
714 Just (Just v) -> fromText v -- if present, we try to convert to
461 Just (Just v) -> fromText v -- if present, we try to convert to
715 -- the right type
462 -- the right type
717− route (Proxy :: Proxy sublayout) (subserver param) request respond
463+ in route (Proxy :: Proxy sublayout) (feedTo subserver param)
719 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
464 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
721-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
466-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
@@ -743,16 +488,14 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
743 type ServerT (QueryParams sym a :> sublayout) m =
488 type ServerT (QueryParams sym a :> sublayout) m =
744 [a] -> ServerT sublayout m
489 [a] -> ServerT sublayout m
746− route Proxy subserver request respond = do
491+ route Proxy subserver = WithRequest $ \ request ->
747 let querytext = parseQueryText $ rawQueryString request
492 let querytext = parseQueryText $ rawQueryString request
748 -- if sym is "foo", we look for query string parameters
493 -- if sym is "foo", we look for query string parameters
749 -- named "foo" or "foo[]" and call fromText on the
494 -- named "foo" or "foo[]" and call fromText on the
750 -- corresponding values
495 -- corresponding values
751 parameters = filter looksLikeParam querytext
496 parameters = filter looksLikeParam querytext
752 values = catMaybes $ map (convert . snd) parameters
497 values = catMaybes $ map (convert . snd) parameters
754− route (Proxy :: Proxy sublayout) (subserver values) request respond
498+ in route (Proxy :: Proxy sublayout) (feedTo subserver values)
756 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
499 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
757 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
500 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
758 convert Nothing = Nothing
501 convert Nothing = Nothing
@@ -776,15 +519,13 @@ instance (KnownSymbol sym, HasServer sublayout)
776 type ServerT (QueryFlag sym :> sublayout) m =
519 type ServerT (QueryFlag sym :> sublayout) m =
777 Bool -> ServerT sublayout m
520 Bool -> ServerT sublayout m
779− route Proxy subserver request respond = do
522+ route Proxy subserver = WithRequest $ \ request ->
780 let querytext = parseQueryText $ rawQueryString request
523 let querytext = parseQueryText $ rawQueryString request
781 param = case lookup paramname querytext of
524 param = case lookup paramname querytext of
782 Just Nothing -> True -- param is there, with no value
525 Just Nothing -> True -- param is there, with no value
783 Just (Just v) -> examine v -- param with a value
526 Just (Just v) -> examine v -- param with a value
784 Nothing -> False -- param not in the query string
527 Nothing -> False -- param not in the query string
786− route (Proxy :: Proxy sublayout) (subserver param) request respond
528+ in route (Proxy :: Proxy sublayout) (feedTo subserver param)
788 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
529 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
789 examine v | v == "true" || v == "1" || v == "" = True
530 examine v | v == "true" || v == "1" || v == "" = True
790 | otherwise = False
531 | otherwise = False
@@ -819,16 +560,17 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
819 type ServerT (MatrixParam sym a :> sublayout) m =
560 type ServerT (MatrixParam sym a :> sublayout) m =
820 Maybe a -> ServerT sublayout m
561 Maybe a -> ServerT sublayout m
563+ route Proxy subserver = WithRequest $ \ request ->
822− route Proxy subserver request respond = case parsePathInfo request of
564+ case parsePathInfo request of
823− (first : _)
565+ (first : _)
824− -> do let querytext = parseMatrixText . encodeUtf8 $ T.tail first
566+ -> do let querytext = parseMatrixText . encodeUtf8 $ T.tail first
825− param = case lookup paramname querytext of
567+ param = case lookup paramname querytext of
826− Nothing -> Nothing -- param absent from the query string
568+ Nothing -> Nothing -- param absent from the query string
827− Just Nothing -> Nothing -- param present with no value -> Nothing
569+ Just Nothing -> Nothing -- param present with no value -> Nothing
828− Just (Just v) -> fromText v -- if present, we try to convert to
570+ Just (Just v) -> fromText v -- if present, we try to convert to
829− -- the right type
571+ -- the right type
830− route (Proxy :: Proxy sublayout) (subserver param) request respond
572+ route (Proxy :: Proxy sublayout) (feedTo subserver param)
831− _ -> route (Proxy :: Proxy sublayout) (subserver Nothing) request respond
573+ _ -> route (Proxy :: Proxy sublayout) (feedTo subserver Nothing)
833 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
575 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
@@ -857,16 +599,17 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
857 type ServerT (MatrixParams sym a :> sublayout) m =
599 type ServerT (MatrixParams sym a :> sublayout) m =
858 [a] -> ServerT sublayout m
600 [a] -> ServerT sublayout m
602+ route Proxy subserver = WithRequest $ \ request ->
860− route Proxy subserver request respond = case parsePathInfo request of
603+ case parsePathInfo request of
861− (first : _)
604+ (first : _)
862− -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
605+ -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
863− -- if sym is "foo", we look for matrix parameters
606+ -- if sym is "foo", we look for matrix parameters
864− -- named "foo" or "foo[]" and call fromText on the
607+ -- named "foo" or "foo[]" and call fromText on the
865− -- corresponding values
608+ -- corresponding values
866− parameters = filter looksLikeParam matrixtext
609+ parameters = filter looksLikeParam matrixtext
867− values = catMaybes $ map (convert . snd) parameters
610+ values = catMaybes $ map (convert . snd) parameters
868− route (Proxy :: Proxy sublayout) (subserver values) request respond
611+ route (Proxy :: Proxy sublayout) (feedTo subserver values)
869− _ -> route (Proxy :: Proxy sublayout) (subserver []) request respond
612+ _ -> route (Proxy :: Proxy sublayout) (feedTo subserver [])
871 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
614 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
872 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
615 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
@@ -891,17 +634,18 @@ instance (KnownSymbol sym, HasServer sublayout)
891 type ServerT (MatrixFlag sym :> sublayout) m =
634 type ServerT (MatrixFlag sym :> sublayout) m =
892 Bool -> ServerT sublayout m
635 Bool -> ServerT sublayout m
637+ route Proxy subserver = WithRequest $ \ request ->
894− route Proxy subserver request respond = case parsePathInfo request of
638+ case parsePathInfo request of
895− (first : _)
639+ (first : _)
896− -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
640+ -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
897− param = case lookup paramname matrixtext of
641+ param = case lookup paramname matrixtext of
898− Just Nothing -> True -- param is there, with no value
642+ Just Nothing -> True -- param is there, with no value
899− Just (Just v) -> examine v -- param with a value
643+ Just (Just v) -> examine v -- param with a value
900− Nothing -> False -- param not in the query string
644+ Nothing -> False -- param not in the query string
902− route (Proxy :: Proxy sublayout) (subserver param) request respond
646+ route (Proxy :: Proxy sublayout) (feedTo subserver param)
904− _ -> route (Proxy :: Proxy sublayout) (subserver False) request respond
648+ _ -> route (Proxy :: Proxy sublayout) (feedTo subserver False)
906 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
650 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
907 examine v | v == "true" || v == "1" || v == "" = True
651 examine v | v == "true" || v == "1" || v == "" = True
@@ -919,8 +663,11 @@ instance HasServer Raw where
920 type ServerT Raw m = Application
664 type ServerT Raw m = Application
922− route Proxy rawApplication request respond =
666+ route Proxy rawApplication = LeafRouter $ \ request respond -> do
669+ RR (Left err) -> respond $ failWith err
923− rawApplication request (respond . succeedWith)
670+ RR (Right app) -> app request (respond . succeedWith)
925-- | If you use 'ReqBody' in one of the endpoints for your API,
672-- | If you use 'ReqBody' in one of the endpoints for your API,
926-- this automatically requires your server-side handler to be a function
673-- this automatically requires your server-side handler to be a function
@@ -948,19 +695,20 @@ instance ( AllCTUnrender list a, HasServer sublayout
948 type ServerT (ReqBody list a :> sublayout) m =
695 type ServerT (ReqBody list a :> sublayout) m =
949 a -> ServerT sublayout m
696 a -> ServerT sublayout m
951− route Proxy subserver request respond = do
698+ route Proxy subserver = WithRequest $ \ request ->
699+ route (Proxy :: Proxy sublayout) $ do
952− -- See HTTP RFC 2616, section 7.2.1
700+ -- See HTTP RFC 2616, section 7.2.1
953− -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
701+ -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
954− -- See also "W3C Internet Media Type registration, consistency of use"
702+ -- See also "W3C Internet Media Type registration, consistency of use"
955− -- http://www.w3.org/2001/tag/2002/0129-mime
703+ -- http://www.w3.org/2001/tag/2002/0129-mime
956− let contentTypeH = fromMaybe "application/octet-stream"
704+ let contentTypeH = fromMaybe "application/octet-stream"
957− $ lookup hContentType $ requestHeaders request
705+ $ lookup hContentType $ requestHeaders request
958− mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
706+ mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
959− <$> lazyRequestBody request
707+ <$> lazyRequestBody request
960− case mrqbody of
708+ case mrqbody of
961− Nothing -> respond . failWith $ UnsupportedMediaType
709+ Nothing -> return $ failWith $ UnsupportedMediaType
962− Just (Left e) -> respond . failWith $ InvalidBody e
710+ Just (Left e) -> return $ failWith $ InvalidBody e
963− Just (Right v) -> route (Proxy :: Proxy sublayout) (subserver v) request respond
711+ Just (Right v) -> feedTo subserver v
965-- | Make sure the incoming request starts with @"/path"@, strip it and
713-- | Make sure the incoming request starts with @"/path"@, strip it and
966-- pass the rest of the request path to @sublayout@.
714-- pass the rest of the request path to @sublayout@.
@@ -968,14 +716,9 @@ instance (KnownSymbol path, HasServer sublayout) => HasServer (path :> sublayout
969 type ServerT (path :> sublayout) m = ServerT sublayout m
717 type ServerT (path :> sublayout) m = ServerT sublayout m
971− route Proxy subserver request respond = case processedPathInfo request of
719+ route Proxy subserver = StaticRouter $
973− | first == cs (symbolVal proxyPath)
720+ M.singleton (cs (symbolVal proxyPath))
974− -> route (Proxy :: Proxy sublayout) subserver request{
721+ (route (Proxy :: Proxy sublayout) subserver)
977− _ -> respond $ failWith NotFound
979 where proxyPath = Proxy :: Proxy path
722 where proxyPath = Proxy :: Proxy path
981ct_wildcard :: B.ByteString
724ct_wildcard :: B.ByteString