@@ -46,7 +46,9 @@ import Servant.API ((:<|>) (..), (:>), Capture,
46 Raw, RemoteHost, ReqBody, Vault)
46 Raw, RemoteHost, ReqBody, Vault)
47import Servant.API.ContentTypes (AcceptHeader (..),
47import Servant.API.ContentTypes (AcceptHeader (..),
48 AllCTRender (..),
48 AllCTRender (..),
49− AllCTUnrender (..))
49+ AllCTUnrender (..),
50import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
52import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
51 getResponse)
53 getResponse)
@@ -60,7 +62,7 @@ import Web.HttpApiData.Internal (parseUrlPieceMaybe, parseHeaderMaybe,
60class HasServer layout where
62class HasServer layout where
61 type ServerT layout (m :: * -> *) :: *
63 type ServerT layout (m :: * -> *) :: *
63− route :: Proxy layout -> IO (RouteResult (Server layout)) -> Router
65+ route :: Proxy layout -> Delayed (Server layout) -> Router
65type Server layout = ServerT layout (ExceptT ServantErr IO)
67type Server layout = ServerT layout (ExceptT ServantErr IO)
@@ -81,8 +83,8 @@ instance (HasServer a, HasServer b) => HasServer (a :<|> b) where
82 type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
84 type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
84− route Proxy server = choice (route pa (extractL <$> server))
86+ route Proxy server = choice (route pa ((\ (a :<|> _) -> a) <$> server))
85− (route pb (extractR <$> server))
87+ (route pb ((\ (_ :<|> b) -> b) <$> server))
86 where pa = Proxy :: Proxy a
88 where pa = Proxy :: Proxy a
87 pb = Proxy :: Proxy b
89 pb = Proxy :: Proxy b
@@ -112,12 +114,15 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout)
112 type ServerT (Capture capture a :> sublayout) m =
114 type ServerT (Capture capture a :> sublayout) m =
113 a -> ServerT sublayout m
115 a -> ServerT sublayout m
115− route Proxy subserver =
117+ route Proxy d =
118+ DynamicRouter $ \ first ->
119+ route (Proxy :: Proxy sublayout)
116− DynamicRouter $ \ first -> case captured captureProxy first of
120+ (addCapture d $ case captured captureProxy first of
117− Nothing -> LeafRouter (\_ r -> r $ Fail err404)
121+ Nothing -> return $ Fail err404
118− Just v -> route (Proxy :: Proxy sublayout) (feedTo subserver v)
122+ Just v -> return $ Route v
120− where captureProxy = Proxy :: Proxy (Capture capture a)
125+ captureProxy = Proxy :: Proxy (Capture capture a)
122allowedMethodHead :: Method -> Request -> Bool
127allowedMethodHead :: Method -> Request -> Bool
123allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
128allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -130,56 +135,64 @@ processMethodRouter :: forall a. ConvertibleStrings a B.ByteString
130 -> Maybe [(HeaderName, B.ByteString)]
135 -> Maybe [(HeaderName, B.ByteString)]
131 -> Request -> RouteResult Response
136 -> Request -> RouteResult Response
132processMethodRouter handleA status method headers request = case handleA of
137processMethodRouter handleA status method headers request = case handleA of
133− Nothing -> FailFatal err406
138+ Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
134− Just (contentT, body) -> Route $! responseLBS status hdrs bdy
139+ Just (contentT, body) -> Route $ responseLBS status hdrs bdy
136 bdy = if allowedMethodHead method request then "" else body
141 bdy = if allowedMethodHead method request then "" else body
137 hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
142 hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
144+methodCheck :: Method -> Request -> IO (RouteResult ())
145+methodCheck method request
146+ | allowedMethod method request = return $ Route ()
147+ | otherwise = return $ Fail err405
149+acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> IO (RouteResult ())
150+acceptCheck proxy accH
151+ | canHandleAcceptH proxy (AcceptHeader accH) = return $ Route ()
152+ | otherwise = return $ Fail err406
139methodRouter :: (AllCTRender ctypes a)
154methodRouter :: (AllCTRender ctypes a)
140 => Method -> Proxy ctypes -> Status
155 => Method -> Proxy ctypes -> Status
141− -> IO (RouteResult (ExceptT ServantErr IO a))
156+ -> Delayed (ExceptT ServantErr IO a)
142 -> Router
157 -> Router
143methodRouter method proxy status action = LeafRouter route'
158methodRouter method proxy status action = LeafRouter route'
145 route' request respond
160 route' request respond
146− | pathIsEmpty request && allowedMethod method request = do
161+ | pathIsEmpty request =
147− runAction action respond $ \ output -> do
148− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
162+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
163+ in runAction (action `addMethodCheck` methodCheck method request
164+ `addAcceptCheck` acceptCheck proxy accH
165+ ) respond $ \ output -> do
149− handleA = handleAcceptH proxy (AcceptHeader accH) output
166+ let handleA = handleAcceptH proxy (AcceptHeader accH) output
150− processMethodRouter handleA status method Nothing request
167+ processMethodRouter handleA status method Nothing request
151− | pathIsEmpty request && requestMethod request /= method =
152− respond $ Fail err405
153 | otherwise = respond $ Fail err404
168 | otherwise = respond $ Fail err404
155methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
170methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
156 => Method -> Proxy ctypes -> Status
171 => Method -> Proxy ctypes -> Status
157− -> IO (RouteResult (ExceptT ServantErr IO (Headers h v)))
172+ -> Delayed (ExceptT ServantErr IO (Headers h v))
158 -> Router
173 -> Router
159methodRouterHeaders method proxy status action = LeafRouter route'
174methodRouterHeaders method proxy status action = LeafRouter route'
161 route' request respond
176 route' request respond
162− | pathIsEmpty request && allowedMethod method request = do
177+ | pathIsEmpty request =
163− runAction action respond $ \ output -> do
164− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
178+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
179+ in runAction (action `addMethodCheck` methodCheck method request
180+ `addAcceptCheck` acceptCheck proxy accH
181+ ) respond $ \ output -> do
165− headers = getHeaders output
182+ let headers = getHeaders output
166− handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
183+ handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
167− processMethodRouter handleA status method (Just headers) request
184+ processMethodRouter handleA status method (Just headers) request
168− | pathIsEmpty request && requestMethod request /= method =
169− respond $ Fail err405
170 | otherwise = respond $ Fail err404
185 | otherwise = respond $ Fail err404
172methodRouterEmpty :: Method
187methodRouterEmpty :: Method
173− -> IO (RouteResult (ExceptT ServantErr IO ()))
188+ -> Delayed (ExceptT ServantErr IO ())
174 -> Router
189 -> Router
175methodRouterEmpty method action = LeafRouter route'
190methodRouterEmpty method action = LeafRouter route'
177 route' request respond
192 route' request respond
178− | pathIsEmpty request && allowedMethod method request = do
193+ | pathIsEmpty request = do
179− runAction action respond $ \ () ->
194+ runAction (addMethodCheck action (methodCheck method request)) respond $ \ () ->
180 Route $! responseLBS noContent204 [] ""
195 Route $! responseLBS noContent204 [] ""
181− | pathIsEmpty request && requestMethod request /= method =
182− respond $ Fail err405
183 | otherwise = respond $ Fail err404
196 | otherwise = respond $ Fail err404
185-- | If you have a 'Delete' endpoint in your API,
198-- | If you have a 'Delete' endpoint in your API,
@@ -300,7 +313,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
301 route Proxy subserver = WithRequest $ \ request ->
314 route Proxy subserver = WithRequest $ \ request ->
302 let mheader = parseHeaderMaybe =<< lookup str (requestHeaders request)
315 let mheader = parseHeaderMaybe =<< lookup str (requestHeaders request)
303− in route (Proxy :: Proxy sublayout) (feedTo subserver mheader)
316+ in route (Proxy :: Proxy sublayout) (passToServer subserver mheader)
304 where str = fromString $ symbolVal (Proxy :: Proxy sym)
317 where str = fromString $ symbolVal (Proxy :: Proxy sym)
306-- | When implementing the handler for a 'Post' endpoint,
319-- | When implementing the handler for a 'Post' endpoint,
@@ -472,7 +485,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
472 Just Nothing -> Nothing -- param present with no value -> Nothing
485 Just Nothing -> Nothing -- param present with no value -> Nothing
473 Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
486 Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
474 -- the right type
487 -- the right type
475− in route (Proxy :: Proxy sublayout) (feedTo subserver param)
488+ in route (Proxy :: Proxy sublayout) (passToServer subserver param)
476 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
489 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
478-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
491-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
@@ -507,7 +520,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
507 -- corresponding values
520 -- corresponding values
508 parameters = filter looksLikeParam querytext
521 parameters = filter looksLikeParam querytext
509 values = mapMaybe (convert . snd) parameters
522 values = mapMaybe (convert . snd) parameters
510− in route (Proxy :: Proxy sublayout) (feedTo subserver values)
523+ in route (Proxy :: Proxy sublayout) (passToServer subserver values)
511 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
524 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
512 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
525 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
513 convert Nothing = Nothing
526 convert Nothing = Nothing
@@ -537,7 +550,7 @@ instance (KnownSymbol sym, HasServer sublayout)
537 Just Nothing -> True -- param is there, with no value
550 Just Nothing -> True -- param is there, with no value
538 Just (Just v) -> examine v -- param with a value
551 Just (Just v) -> examine v -- param with a value
539 Nothing -> False -- param not in the query string
552 Nothing -> False -- param not in the query string
540− in route (Proxy :: Proxy sublayout) (feedTo subserver param)
553+ in route (Proxy :: Proxy sublayout) (passToServer subserver param)
541 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
554 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
542 examine v | v == "true" || v == "1" || v == "" = True
555 examine v | v == "true" || v == "1" || v == "" = True
543 | otherwise = False
556 | otherwise = False
@@ -555,7 +568,7 @@ instance HasServer Raw where
555 type ServerT Raw m = Application
568 type ServerT Raw m = Application
557 route Proxy rawApplication = LeafRouter $ \ request respond -> do
570 route Proxy rawApplication = LeafRouter $ \ request respond -> do
558− r <- rawApplication
571+ r <- runDelayed rawApplication
559 case r of
572 case r of
560 Route app -> app request (respond . Route)
573 Route app -> app request (respond . Route)
561 Fail a -> respond $ Fail a
574 Fail a -> respond $ Fail a
@@ -589,19 +602,21 @@ instance ( AllCTUnrender list a, HasServer sublayout
589 a -> ServerT sublayout m
602 a -> ServerT sublayout m
591 route Proxy subserver = WithRequest $ \ request ->
604 route Proxy subserver = WithRequest $ \ request ->
592− route (Proxy :: Proxy sublayout) $ do
605+ route (Proxy :: Proxy sublayout) (addBodyCheck subserver (bodyCheck request))
607+ bodyCheck request = do
593− -- See HTTP RFC 2616, section 7.2.1
608+ -- See HTTP RFC 2616, section 7.2.1
594− -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
609+ -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
595− -- See also "W3C Internet Media Type registration, consistency of use"
610+ -- See also "W3C Internet Media Type registration, consistency of use"
596− -- http://www.w3.org/2001/tag/2002/0129-mime
611+ -- http://www.w3.org/2001/tag/2002/0129-mime
597− let contentTypeH = fromMaybe "application/octet-stream"
612+ let contentTypeH = fromMaybe "application/octet-stream"
598− $ lookup hContentType $ requestHeaders request
613+ $ lookup hContentType $ requestHeaders request
599− mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
614+ mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
600− <$> lazyRequestBody request
615+ <$> lazyRequestBody request
601− case mrqbody of
616+ case mrqbody of
602− Nothing -> return $ FailFatal err415
617+ Nothing -> return $ FailFatal err415
603− Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
618+ Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
604− Just (Right v) -> feedTo subserver v
619+ Just (Right v) -> return $ Route v
606-- | Make sure the incoming request starts with @"/path"@, strip it and
621-- | Make sure the incoming request starts with @"/path"@, strip it and
607-- pass the rest of the request path to @sublayout@.
622-- pass the rest of the request path to @sublayout@.
@@ -618,13 +633,13 @@ instance HasServer api => HasServer (RemoteHost :> api) where
618 type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
633 type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
620 route Proxy subserver = WithRequest $ \req ->
635 route Proxy subserver = WithRequest $ \req ->
621− route (Proxy :: Proxy api) (feedTo subserver $ remoteHost req)
636+ route (Proxy :: Proxy api) (passToServer subserver $ remoteHost req)
623instance HasServer api => HasServer (IsSecure :> api) where
638instance HasServer api => HasServer (IsSecure :> api) where
624 type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
639 type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
626 route Proxy subserver = WithRequest $ \req ->
641 route Proxy subserver = WithRequest $ \req ->
627− route (Proxy :: Proxy api) (feedTo subserver $ secure req)
642+ route (Proxy :: Proxy api) (passToServer subserver $ secure req)
629 where secure req = if isSecure req then Secure else NotSecure
644 where secure req = if isSecure req then Secure else NotSecure
@@ -632,13 +647,13 @@ instance HasServer api => HasServer (Vault :> api) where
632 type ServerT (Vault :> api) m = Vault -> ServerT api m
647 type ServerT (Vault :> api) m = Vault -> ServerT api m
634 route Proxy subserver = WithRequest $ \req ->
649 route Proxy subserver = WithRequest $ \req ->
635− route (Proxy :: Proxy api) (feedTo subserver $ vault req)
650+ route (Proxy :: Proxy api) (passToServer subserver $ vault req)
637instance HasServer api => HasServer (HttpVersion :> api) where
652instance HasServer api => HasServer (HttpVersion :> api) where
638 type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
653 type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
640 route Proxy subserver = WithRequest $ \req ->
655 route Proxy subserver = WithRequest $ \req ->
641− route (Proxy :: Proxy api) (feedTo subserver $ httpVersion req)
656+ route (Proxy :: Proxy api) (passToServer subserver $ httpVersion req)
643pathIsEmpty :: Request -> Bool
658pathIsEmpty :: Request -> Bool
644pathIsEmpty = go . pathInfo
659pathIsEmpty = go . pathInfo