@@ -1,15 +1,15 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE FlexibleInstances #-}
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
55{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE PolyKinds #-}
8−{-# LANGUAGE ScopedTypeVariables #-}
9−{-# LANGUAGE TypeFamilies #-}
10−{-# LANGUAGE TypeOperators #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE PolyKinds #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TypeFamilies #-}
10+{-# LANGUAGE TypeOperators #-}
1111#if !MIN_VERSION_base(4,8,0)
12−{-# LANGUAGE OverlappingInstances #-}
12+{-# LANGUAGE OverlappingInstances #-}
1313#endif
1414
1515module Servant.Server.Internal
@@ -26,9 +26,9 @@ import Control.Monad.Trans.Except (ExceptT)
2626import qualified Data.ByteString as B
2727import qualified Data.ByteString.Lazy as BL
2828import qualified Data.Map as M
29−import Data.Maybe (mapMaybe, fromMaybe)
29+import Data.Maybe (fromMaybe, mapMaybe)
3030import Data.String (fromString)
31−import Data.String.Conversions (cs, (<>), ConvertibleStrings)
31+import Data.String.Conversions (ConvertibleStrings, cs, (<>))
3232import Data.Text (Text)
3333import Data.Typeable
3434import GHC.TypeLits (KnownSymbol, symbolVal)
@@ -46,9 +46,11 @@ import Servant.API ((:<|>) (..), (:>), Capture,
4646 Raw, RemoteHost, ReqBody, Vault)
4747import Servant.API.ContentTypes (AcceptHeader (..),
4848 AllCTRender (..),
49− AllCTUnrender (..))
50−import Servant.API.ResponseHeaders (Headers, getResponse, GetHeaders,
51− getHeaders)
49+ AllCTUnrender (..),
50+ AllMime,
51+ canHandleAcceptH)
52+import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
53+ getResponse)
5254
5355import Servant.Server.Internal.Router
5456import Servant.Server.Internal.RoutingApplication
@@ -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,13 +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 =
117+ route Proxy d =
116118 DynamicRouter $ \ first ->
117− route (Proxy :: Proxy sublayout)
118− (case captured captureProxy first of
119− Nothing -> return $ failWith NotFound
120− Just v -> feedTo subserver v)
121− where captureProxy = Proxy :: Proxy (Capture capture a)
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)
122126
123127allowedMethodHead :: Method -> Request -> Bool
124128allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -131,57 +135,65 @@ processMethodRouter :: forall a. ConvertibleStrings a B.ByteString
131135 -> Maybe [(HeaderName, B.ByteString)]
132136 -> Request -> RouteResult Response
133137processMethodRouter handleA status method headers request = case handleA of
134− Nothing -> failWith UnsupportedMediaType
135− Just (contentT, body) -> succeedWith $ 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
136140 where
137141 bdy = if allowedMethodHead method request then "" else body
138142 hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
139143
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+
140154methodRouter :: (AllCTRender ctypes a)
141155 => Method -> Proxy ctypes -> Status
142− -> IO (RouteResult (ExceptT ServantErr IO a))
156+ -> Delayed (ExceptT ServantErr IO a)
143157 -> Router
144158methodRouter method proxy status action = LeafRouter route'
145159 where
146160 route' request respond
147− | pathIsEmpty request && allowedMethod method request = do
148− runAction action respond $ \ output -> do
149− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
150− handleA = handleAcceptH proxy (AcceptHeader accH) output
151− processMethodRouter handleA status method Nothing request
152− | pathIsEmpty request && requestMethod request /= method =
153− respond $ failWith WrongMethod
154− | otherwise = respond $ failWith NotFound
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
168+ | otherwise = respond $ Fail err404
155169
156170methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
157171 => Method -> Proxy ctypes -> Status
158− -> IO (RouteResult (ExceptT ServantErr IO (Headers h v)))
172+ -> Delayed (ExceptT ServantErr IO (Headers h v))
159173 -> Router
160174methodRouterHeaders method proxy status action = LeafRouter route'
161175 where
162176 route' request respond
163− | pathIsEmpty request && allowedMethod method request = do
164− runAction action respond $ \ output -> do
165− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
166− headers = getHeaders output
167− handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
168− processMethodRouter handleA status method (Just headers) request
169− | pathIsEmpty request && requestMethod request /= method =
170− respond $ failWith WrongMethod
171− | otherwise = respond $ failWith NotFound
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
185+ | otherwise = respond $ Fail err404
172186
173187methodRouterEmpty :: Method
174− -> IO (RouteResult (ExceptT ServantErr IO ()))
188+ -> Delayed (ExceptT ServantErr IO ())
175189 -> Router
176190methodRouterEmpty method action = LeafRouter route'
177191 where
178192 route' request respond
179− | pathIsEmpty request && allowedMethod method request = do
180− runAction action respond $ \ () ->
181− succeedWith $ responseLBS noContent204 [] ""
182− | pathIsEmpty request && requestMethod request /= method =
183− respond $ failWith WrongMethod
184− | otherwise = respond $ failWith NotFound
193+ | pathIsEmpty request = do
194+ runAction (addMethodCheck action (methodCheck method request)) respond $ \ () ->
195+ Route $! responseLBS noContent204 [] ""
196+ | otherwise = respond $ Fail err404
185197
186198-- | If you have a 'Delete' endpoint in your API,
187199-- the handler for this endpoint is meant to delete
@@ -301,7 +313,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
301313
302314 route Proxy subserver = WithRequest $ \ request ->
303315 let mheader = parseHeaderMaybe =<< lookup str (requestHeaders request)
304− in route (Proxy :: Proxy sublayout) (feedTo subserver mheader)
316+ in route (Proxy :: Proxy sublayout) (passToServer subserver mheader)
305317 where str = fromString $ symbolVal (Proxy :: Proxy sym)
306318
307319-- | When implementing the handler for a 'Post' endpoint,
@@ -473,7 +485,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
473485 Just Nothing -> Nothing -- param present with no value -> Nothing
474486 Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
475487 -- the right type
476− in route (Proxy :: Proxy sublayout) (feedTo subserver param)
488+ in route (Proxy :: Proxy sublayout) (passToServer subserver param)
477489 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
478490
479491-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
@@ -508,7 +520,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
508520 -- corresponding values
509521 parameters = filter looksLikeParam querytext
510522 values = mapMaybe (convert . snd) parameters
511− in route (Proxy :: Proxy sublayout) (feedTo subserver values)
523+ in route (Proxy :: Proxy sublayout) (passToServer subserver values)
512524 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
513525 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
514526 convert Nothing = Nothing
@@ -538,7 +550,7 @@ instance (KnownSymbol sym, HasServer sublayout)
538550 Just Nothing -> True -- param is there, with no value
539551 Just (Just v) -> examine v -- param with a value
540552 Nothing -> False -- param not in the query string
541− in route (Proxy :: Proxy sublayout) (feedTo subserver param)
553+ in route (Proxy :: Proxy sublayout) (passToServer subserver param)
542554 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
543555 examine v | v == "true" || v == "1" || v == "" = True
544556 | otherwise = False
@@ -556,10 +568,11 @@ instance HasServer Raw where
556568 type ServerT Raw m = Application
557569
558570 route Proxy rawApplication = LeafRouter $ \ request respond -> do
559− r <- rawApplication
571+ r <- runDelayed rawApplication
560572 case r of
561− RR (Left err) -> respond $ failWith err
562− RR (Right app) -> app request (respond . succeedWith)
573+ Route app -> app request (respond . Route)
574+ Fail a -> respond $ Fail a
575+ FailFatal e -> respond $ FailFatal e
563576
564577-- | If you use 'ReqBody' in one of the endpoints for your API,
565578-- this automatically requires your server-side handler to be a function
@@ -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 $ failWith $ UnsupportedMediaType
603− Just (Left e) -> return $ failWith $ InvalidBody 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