@@ -46,7 +46,9 @@ import Servant.API ((:<|>) (..), (:>), Capture,
4646 Raw, RemoteHost, ReqBody, Vault)
4747import Servant.API.ContentTypes (AcceptHeader (..),
4848 AllCTRender (..),
49− AllCTUnrender (..))
49+ AllCTUnrender (..),
50+ AllMime,
51+ canHandleAcceptH)
5052import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
5153 getResponse)
5254
@@ -60,7 +62,7 @@ import Web.HttpApiData.Internal (parseUrlPieceMaybe, parseHeaderMaybe,
6062class HasServer layout where
6163 type ServerT layout (m :: * -> *) :: *
6264
63− route :: Proxy layout -> IO (RouteResult (Server layout)) -> Router
65+ route :: Proxy layout -> Delayed (Server layout) -> Router
6466
6567type Server layout = ServerT layout (ExceptT ServantErr IO)
6668
@@ -81,8 +83,8 @@ instance (HasServer a, HasServer b) => HasServer (a :<|> b) where
8183
8284 type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
8385
84− route Proxy server = choice (route pa (extractL <$> server))
85− (route pb (extractR <$> server))
86+ route Proxy server = choice (route pa ((\ (a :<|> _) -> a) <$> server))
87+ (route pb ((\ (_ :<|> b) -> b) <$> server))
8688 where pa = Proxy :: Proxy a
8789 pb = Proxy :: Proxy b
8890
@@ -112,12 +114,15 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout)
112114 type ServerT (Capture capture a :> sublayout) m =
113115 a -> ServerT sublayout m
114116
115− route Proxy subserver =
116− DynamicRouter $ \ first -> case captured captureProxy first of
117− Nothing -> LeafRouter (\_ r -> r $ Fail err404)
118− Just v -> route (Proxy :: Proxy sublayout) (feedTo subserver v)
119−
120− where captureProxy = Proxy :: Proxy (Capture capture a)
117+ route Proxy d =
118+ DynamicRouter $ \ first ->
119+ route (Proxy :: Proxy sublayout)
120+ (addCapture d $ case captured captureProxy first of
121+ Nothing -> return $ Fail err404
122+ Just v -> return $ Route v
123+ )
124+ where
125+ captureProxy = Proxy :: Proxy (Capture capture a)
121126
122127allowedMethodHead :: Method -> Request -> Bool
123128allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -130,56 +135,64 @@ processMethodRouter :: forall a. ConvertibleStrings a B.ByteString
130135 -> Maybe [(HeaderName, B.ByteString)]
131136 -> Request -> RouteResult Response
132137processMethodRouter handleA status method headers request = case handleA of
133− Nothing -> FailFatal err406
134− Just (contentT, body) -> Route $! responseLBS status hdrs bdy
138+ Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
139+ Just (contentT, body) -> Route $ responseLBS status hdrs bdy
135140 where
136141 bdy = if allowedMethodHead method request then "" else body
137142 hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
138143
144+methodCheck :: Method -> Request -> IO (RouteResult ())
145+methodCheck method request
146+ | allowedMethod method request = return $ Route ()
147+ | otherwise = return $ Fail err405
148+
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
153+
139154methodRouter :: (AllCTRender ctypes a)
140155 => Method -> Proxy ctypes -> Status
141− -> IO (RouteResult (ExceptT ServantErr IO a))
156+ -> Delayed (ExceptT ServantErr IO a)
142157 -> Router
143158methodRouter method proxy status action = LeafRouter route'
144159 where
145160 route' request respond
146− | pathIsEmpty request && allowedMethod method request = do
147− runAction action respond $ \ output -> do
148− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
149− handleA = handleAcceptH proxy (AcceptHeader accH) output
150− processMethodRouter handleA status method Nothing request
151− | pathIsEmpty request && requestMethod request /= method =
152− respond $ Fail err405
161+ | pathIsEmpty 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
166+ let handleA = handleAcceptH proxy (AcceptHeader accH) output
167+ processMethodRouter handleA status method Nothing request
153168 | otherwise = respond $ Fail err404
154169
155170methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
156171 => Method -> Proxy ctypes -> Status
157− -> IO (RouteResult (ExceptT ServantErr IO (Headers h v)))
172+ -> Delayed (ExceptT ServantErr IO (Headers h v))
158173 -> Router
159174methodRouterHeaders method proxy status action = LeafRouter route'
160175 where
161176 route' request respond
162− | pathIsEmpty request && allowedMethod method request = do
163− runAction action respond $ \ output -> do
164− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
165− headers = getHeaders output
166− handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
167− processMethodRouter handleA status method (Just headers) request
168− | pathIsEmpty request && requestMethod request /= method =
169− respond $ Fail err405
177+ | pathIsEmpty 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
182+ let headers = getHeaders output
183+ handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
184+ processMethodRouter handleA status method (Just headers) request
170185 | otherwise = respond $ Fail err404
171186
172187methodRouterEmpty :: Method
173− -> IO (RouteResult (ExceptT ServantErr IO ()))
188+ -> Delayed (ExceptT ServantErr IO ())
174189 -> Router
175190methodRouterEmpty method action = LeafRouter route'
176191 where
177192 route' request respond
178− | pathIsEmpty request && allowedMethod method request = do
179− runAction action respond $ \ () ->
193+ | pathIsEmpty request = do
194+ runAction (addMethodCheck action (methodCheck method request)) respond $ \ () ->
180195 Route $! responseLBS noContent204 [] ""
181− | pathIsEmpty request && requestMethod request /= method =
182− respond $ Fail err405
183196 | otherwise = respond $ Fail err404
184197
185198-- | If you have a 'Delete' endpoint in your API,
@@ -300,7 +313,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
300313
301314 route Proxy subserver = WithRequest $ \ request ->
302315 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)
304317 where str = fromString $ symbolVal (Proxy :: Proxy sym)
305318
306319-- | When implementing the handler for a 'Post' endpoint,
@@ -472,7 +485,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
472485 Just Nothing -> Nothing -- param present with no value -> Nothing
473486 Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
474487 -- the right type
475− in route (Proxy :: Proxy sublayout) (feedTo subserver param)
488+ in route (Proxy :: Proxy sublayout) (passToServer subserver param)
476489 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
477490
478491-- | 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)
507520 -- corresponding values
508521 parameters = filter looksLikeParam querytext
509522 values = mapMaybe (convert . snd) parameters
510− in route (Proxy :: Proxy sublayout) (feedTo subserver values)
523+ in route (Proxy :: Proxy sublayout) (passToServer subserver values)
511524 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
512525 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
513526 convert Nothing = Nothing
@@ -537,7 +550,7 @@ instance (KnownSymbol sym, HasServer sublayout)
537550 Just Nothing -> True -- param is there, with no value
538551 Just (Just v) -> examine v -- param with a value
539552 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)
541554 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
542555 examine v | v == "true" || v == "1" || v == "" = True
543556 | otherwise = False
@@ -555,7 +568,7 @@ instance HasServer Raw where
555568 type ServerT Raw m = Application
556569
557570 route Proxy rawApplication = LeafRouter $ \ request respond -> do
558− r <- rawApplication
571+ r <- runDelayed rawApplication
559572 case r of
560573 Route app -> app request (respond . Route)
561574 Fail a -> respond $ Fail a
@@ -589,19 +602,21 @@ instance ( AllCTUnrender list a, HasServer sublayout
589602 a -> ServerT sublayout m
590603
591604 route Proxy subserver = WithRequest $ \ request ->
592− route (Proxy :: Proxy sublayout) $ do
593− -- See HTTP RFC 2616, section 7.2.1
594− -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
595− -- See also "W3C Internet Media Type registration, consistency of use"
596− -- http://www.w3.org/2001/tag/2002/0129-mime
597− let contentTypeH = fromMaybe "application/octet-stream"
598− $ lookup hContentType $ requestHeaders request
599− mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
600− <$> lazyRequestBody request
601− case mrqbody of
602− Nothing -> return $ FailFatal err415
603− Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
604− Just (Right v) -> feedTo subserver v
605+ route (Proxy :: Proxy sublayout) (addBodyCheck subserver (bodyCheck request))
606+ where
607+ bodyCheck request = do
608+ -- See HTTP RFC 2616, section 7.2.1
609+ -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
610+ -- See also "W3C Internet Media Type registration, consistency of use"
611+ -- http://www.w3.org/2001/tag/2002/0129-mime
612+ let contentTypeH = fromMaybe "application/octet-stream"
613+ $ lookup hContentType $ requestHeaders request
614+ mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
615+ <$> lazyRequestBody request
616+ case mrqbody of
617+ Nothing -> return $ FailFatal err415
618+ Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
619+ Just (Right v) -> return $ Route v
605620
606621-- | Make sure the incoming request starts with @"/path"@, strip it and
607622-- pass the rest of the request path to @sublayout@.
@@ -618,13 +633,13 @@ instance HasServer api => HasServer (RemoteHost :> api) where
618633 type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
619634
620635 route Proxy subserver = WithRequest $ \req ->
621− route (Proxy :: Proxy api) (feedTo subserver $ remoteHost req)
636+ route (Proxy :: Proxy api) (passToServer subserver $ remoteHost req)
622637
623638instance HasServer api => HasServer (IsSecure :> api) where
624639 type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
625640
626641 route Proxy subserver = WithRequest $ \req ->
627− route (Proxy :: Proxy api) (feedTo subserver $ secure req)
642+ route (Proxy :: Proxy api) (passToServer subserver $ secure req)
628643
629644 where secure req = if isSecure req then Secure else NotSecure
630645
@@ -632,13 +647,13 @@ instance HasServer api => HasServer (Vault :> api) where
632647 type ServerT (Vault :> api) m = Vault -> ServerT api m
633648
634649 route Proxy subserver = WithRequest $ \req ->
635− route (Proxy :: Proxy api) (feedTo subserver $ vault req)
650+ route (Proxy :: Proxy api) (passToServer subserver $ vault req)
636651
637652instance HasServer api => HasServer (HttpVersion :> api) where
638653 type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
639654
640655 route Proxy subserver = WithRequest $ \req ->
641− route (Proxy :: Proxy api) (feedTo subserver $ httpVersion req)
656+ route (Proxy :: Proxy api) (passToServer subserver $ httpVersion req)
642657
643658pathIsEmpty :: Request -> Bool
644659pathIsEmpty = go . pathInfo