@@ -21,26 +21,33 @@ module Servant.Server.Internal
21#if !MIN_VERSION_base(4,8,0)
21#if !MIN_VERSION_base(4,8,0)
22import Control.Applicative ((<$>))
22import Control.Applicative ((<$>))
24−import Control.Monad.Trans.Except (ExceptT)
24+import Control.Monad.Trans.Except (ExceptT)
25−import qualified Data.ByteString as B
25+import qualified Data.ByteString as B
26−import qualified Data.ByteString.Lazy as BL
26+import qualified Data.ByteString.Lazy as BL
27−import qualified Data.Map as M
27+import qualified Data.Map as M
28−import Data.Maybe (fromMaybe, mapMaybe)
28+import Data.Maybe (fromMaybe, mapMaybe)
29−import Data.String (fromString)
29+import Data.String (fromString)
30−import Data.String.Conversions (ConvertibleStrings, cs, (<>))
30+import Data.String.Conversions (cs, (<>))
31−import Data.Text (Text)
31+import Data.Text (Text)
32import Data.Typeable
32import Data.Typeable
33−import GHC.TypeLits (KnownSymbol, symbolVal)
33+import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
34−import Network.HTTP.Types hiding (Header, ResponseHeaders)
35+import Network.HTTP.Types hiding (Header, ResponseHeaders)
35−import Network.Socket (SockAddr)
36+import Network.Socket (SockAddr)
36−import Network.Wai (Application, lazyRequestBody,
37+import Network.Wai (Application, Request, Response,
38+ httpVersion, isSecure,
39+ lazyRequestBody, pathInfo,
37− rawQueryString, requestHeaders,
40+ rawQueryString, remoteHost,
41+ requestHeaders, requestMethod,
38− requestMethod, responseLBS, remoteHost,
42+ responseLBS, vault)
39− isSecure, vault, httpVersion, Response,
43+import Web.HttpApiData (FromHttpApiData)
40− Request, pathInfo)
44+import Web.HttpApiData.Internal (parseHeaderMaybe,
45+ parseQueryParamMaybe,
41import Servant.API ((:<|>) (..), (:>), Capture,
48import Servant.API ((:<|>) (..), (:>), Capture,
42− Delete, Get, Header,
49+ Verb, ReflectMethod(reflectMethod),
43− IsSecure(..), Patch, Post, Put,
50+ IsSecure(..), Header,
44 QueryFlag, QueryParam, QueryParams,
51 QueryFlag, QueryParam, QueryParams,
45 Raw, RemoteHost, ReqBody, Vault)
52 Raw, RemoteHost, ReqBody, Vault)
46import Servant.API.ContentTypes (AcceptHeader (..),
53import Servant.API.ContentTypes (AcceptHeader (..),
@@ -55,8 +62,6 @@ import Servant.Server.Internal.Router
55import Servant.Server.Internal.RoutingApplication
62import Servant.Server.Internal.RoutingApplication
56import Servant.Server.Internal.ServantErr
63import Servant.Server.Internal.ServantErr
58−import Web.HttpApiData (FromHttpApiData)
59−import Web.HttpApiData.Internal (parseUrlPieceMaybe, parseHeaderMaybe, parseQueryParamMaybe)
61class HasServer layout where
66class HasServer layout where
62 type ServerT layout (m :: * -> *) :: *
67 type ServerT layout (m :: * -> *) :: *
@@ -129,12 +134,12 @@ allowedMethodHead method request = method == methodGet && requestMethod request
129allowedMethod :: Method -> Request -> Bool
134allowedMethod :: Method -> Request -> Bool
130allowedMethod method request = allowedMethodHead method request || requestMethod request == method
135allowedMethod method request = allowedMethodHead method request || requestMethod request == method
132−processMethodRouter :: forall a. ConvertibleStrings a B.ByteString
133− => Maybe (a, BL.ByteString) -> Status -> Method
137+processMethodRouter :: Maybe (BL.ByteString, BL.ByteString) -> Status -> Method
134 -> Maybe [(HeaderName, B.ByteString)]
138 -> Maybe [(HeaderName, B.ByteString)]
135 -> Request -> RouteResult Response
139 -> Request -> RouteResult Response
136processMethodRouter handleA status method headers request = case handleA of
140processMethodRouter handleA status method headers request = case handleA of
137 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
141 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
142+ Just (_, "") -> Route $ responseLBS status204 (fromMaybe [] headers) ""
138 Just (contentT, body) -> Route $ responseLBS status hdrs bdy
143 Just (contentT, body) -> Route $ responseLBS status hdrs bdy
140 bdy = if allowedMethodHead method request then "" else body
145 bdy = if allowedMethodHead method request then "" else body
@@ -160,7 +165,7 @@ methodRouter method proxy status action = LeafRouter route'
160 | pathIsEmpty request =
165 | pathIsEmpty request =
161 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
166 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
162 in runAction (action `addMethodCheck` methodCheck method request
167 in runAction (action `addMethodCheck` methodCheck method request
163− `addAcceptCheck` acceptCheck proxy accH
168+ `addAcceptCheck` acceptCheck proxy accH
164 ) respond $ \ output -> do
169 ) respond $ \ output -> do
165 let handleA = handleAcceptH proxy (AcceptHeader accH) output
170 let handleA = handleAcceptH proxy (AcceptHeader accH) output
166 processMethodRouter handleA status method Nothing request
171 processMethodRouter handleA status method Nothing request
@@ -176,95 +181,34 @@ methodRouterHeaders method proxy status action = LeafRouter route'
176 | pathIsEmpty request =
181 | pathIsEmpty request =
177 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
182 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
178 in runAction (action `addMethodCheck` methodCheck method request
183 in runAction (action `addMethodCheck` methodCheck method request
179− `addAcceptCheck` acceptCheck proxy accH
184+ `addAcceptCheck` acceptCheck proxy accH
180 ) respond $ \ output -> do
185 ) respond $ \ output -> do
181 let headers = getHeaders output
186 let headers = getHeaders output
182 handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
187 handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
183 processMethodRouter handleA status method (Just headers) request
188 processMethodRouter handleA status method (Just headers) request
184 | otherwise = respond $ Fail err404
189 | otherwise = respond $ Fail err404
186−methodRouterEmpty :: Method
187− -> Delayed (ExceptT ServantErr IO ())
189−methodRouterEmpty method action = LeafRouter route'
191− route' request respond
192− | pathIsEmpty request = do
193− runAction (addMethodCheck action (methodCheck method request)) respond $ \ () ->
194− Route $! responseLBS noContent204 [] ""
195− | otherwise = respond $ Fail err404
197−-- | If you have a 'Delete' endpoint in your API,
198−-- the handler for this endpoint is meant to delete
201−-- The code of the handler will, just like
202−-- for 'Servant.API.Get.Get', 'Servant.API.Post.Post' and
203−-- 'Servant.API.Put.Put', run in @ExceptT ServantErr IO ()@.
204−-- The 'Int' represents the status code and the 'String' a message
205−-- to be returned. You can use 'Control.Monad.Trans.Except.throwE' to
206−-- painlessly error out if the conditions for a successful deletion
208instance OVERLAPPABLE_
191instance OVERLAPPABLE_
209− ( AllCTRender ctypes a
192+ ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
210− ) => HasServer (Delete ctypes a) where
193+ ) => HasServer (Verb method status ctypes a) where
212− type ServerT (Delete ctypes a) m = m a
214− route Proxy = methodRouter methodDelete (Proxy :: Proxy ctypes) ok200
216−instance OVERLAPPING_
217− HasServer (Delete ctypes ()) where
219− type ServerT (Delete ctypes ()) m = m ()
195+ type ServerT (Verb method status ctypes a) m = m a
221− route Proxy = methodRouterEmpty methodDelete
197+ route Proxy = methodRouter method (Proxy :: Proxy ctypes) status
198+ where method = reflectMethod (Proxy :: Proxy method)
199+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
223−-- Add response headers
224instance OVERLAPPING_
201instance OVERLAPPING_
225− ( GetHeaders (Headers h v), AllCTRender ctypes v
202+instance
203+ ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
204+ , GetHeaders (Headers h a)
226− ) => HasServer (Delete ctypes (Headers h v)) where
205+ ) => HasServer (Verb method status ctypes (Headers h a)) where
228− type ServerT (Delete ctypes (Headers h v)) m = m (Headers h v)
230− route Proxy = methodRouterHeaders methodDelete (Proxy :: Proxy ctypes) ok200
232−-- | When implementing the handler for a 'Get' endpoint,
207+ type ServerT (Verb method status ctypes (Headers h a)) m = m (Headers h a)
233−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Post.Post'
234−-- and 'Servant.API.Put.Put', the handler code runs in the
235−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
236−-- the status code and the 'String' a message, returned in case of
237−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
238−-- to quickly fail if some conditions are not met.
240−-- If successfully returning a value, we use the type-level list, combined
241−-- with the request's @Accept@ header, to encode the value for you
242−-- (returning a status code of 200). If there was no @Accept@ header or it
243−-- was @*\/\*@, we return encode using the first @Content-Type@ type on the
245−instance OVERLAPPABLE_
246− ( AllCTRender ctypes a ) => HasServer (Get ctypes a) where
248− type ServerT (Get ctypes a) m = m a
250− route Proxy = methodRouter methodGet (Proxy :: Proxy ctypes) ok200
252−-- '()' ==> 204 No Content
253−instance OVERLAPPING_
254− HasServer (Get ctypes ()) where
256− type ServerT (Get ctypes ()) m = m ()
258− route Proxy = methodRouterEmpty methodGet
260−-- Add response headers
261−instance OVERLAPPING_
262− ( GetHeaders (Headers h v), AllCTRender ctypes v
263− ) => HasServer (Get ctypes (Headers h v)) where
265− type ServerT (Get ctypes (Headers h v)) m = m (Headers h v)
267− route Proxy = methodRouterHeaders methodGet (Proxy :: Proxy ctypes) ok200
209+ route Proxy = methodRouterHeaders method (Proxy :: Proxy ctypes) status
210+ where method = reflectMethod (Proxy :: Proxy method)
211+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
269-- | If you use 'Header' in one of the endpoints for your API,
213-- | If you use 'Header' in one of the endpoints for your API,
270-- this automatically requires your server-side handler to be a function
214-- this automatically requires your server-side handler to be a function
@@ -297,113 +241,6 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
297 in route (Proxy :: Proxy sublayout) (passToServer subserver mheader)
241 in route (Proxy :: Proxy sublayout) (passToServer subserver mheader)
298 where str = fromString $ symbolVal (Proxy :: Proxy sym)
242 where str = fromString $ symbolVal (Proxy :: Proxy sym)
300−-- | When implementing the handler for a 'Post' endpoint,
301−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
302−-- and 'Servant.API.Put.Put', the handler code runs in the
303−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
304−-- the status code and the 'String' a message, returned in case of
305−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
306−-- to quickly fail if some conditions are not met.
308−-- If successfully returning a value, we use the type-level list, combined
309−-- with the request's @Accept@ header, to encode the value for you
310−-- (returning a status code of 201). If there was no @Accept@ header or it
311−-- was @*\/\*@, we return encode using the first @Content-Type@ type on the
313−instance OVERLAPPABLE_
314− ( AllCTRender ctypes a
315− ) => HasServer (Post ctypes a) where
317− type ServerT (Post ctypes a) m = m a
319− route Proxy = methodRouter methodPost (Proxy :: Proxy ctypes) created201
321−instance OVERLAPPING_
322− HasServer (Post ctypes ()) where
324− type ServerT (Post ctypes ()) m = m ()
326− route Proxy = methodRouterEmpty methodPost
328−-- Add response headers
329−instance OVERLAPPING_
330− ( GetHeaders (Headers h v), AllCTRender ctypes v
331− ) => HasServer (Post ctypes (Headers h v)) where
333− type ServerT (Post ctypes (Headers h v)) m = m (Headers h v)
335− route Proxy = methodRouterHeaders methodPost (Proxy :: Proxy ctypes) created201
337−-- | When implementing the handler for a 'Put' endpoint,
338−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
339−-- and 'Servant.API.Post.Post', the handler code runs in the
340−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
341−-- the status code and the 'String' a message, returned in case of
342−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
343−-- to quickly fail if some conditions are not met.
345−-- If successfully returning a value, we use the type-level list, combined
346−-- with the request's @Accept@ header, to encode the value for you
347−-- (returning a status code of 200). If there was no @Accept@ header or it
348−-- was @*\/\*@, we return encode using the first @Content-Type@ type on the
350−instance OVERLAPPABLE_
351− ( AllCTRender ctypes a) => HasServer (Put ctypes a) where
353− type ServerT (Put ctypes a) m = m a
355− route Proxy = methodRouter methodPut (Proxy :: Proxy ctypes) ok200
357−instance OVERLAPPING_
358− HasServer (Put ctypes ()) where
360− type ServerT (Put ctypes ()) m = m ()
362− route Proxy = methodRouterEmpty methodPut
364−-- Add response headers
365−instance OVERLAPPING_
366− ( GetHeaders (Headers h v), AllCTRender ctypes v
367− ) => HasServer (Put ctypes (Headers h v)) where
369− type ServerT (Put ctypes (Headers h v)) m = m (Headers h v)
371− route Proxy = methodRouterHeaders methodPut (Proxy :: Proxy ctypes) ok200
373−-- | When implementing the handler for a 'Patch' endpoint,
374−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
375−-- and 'Servant.API.Put.Put', the handler code runs in the
376−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
377−-- the status code and the 'String' a message, returned in case of
378−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
379−-- to quickly fail if some conditions are not met.
381−-- If successfully returning a value, we just require that its type has
382−-- a 'ToJSON' instance and servant takes care of encoding it for you,
383−-- yielding status code 200 along the way.
384−instance OVERLAPPABLE_
385− ( AllCTRender ctypes a) => HasServer (Patch ctypes a) where
387− type ServerT (Patch ctypes a) m = m a
389− route Proxy = methodRouter methodPatch (Proxy :: Proxy ctypes) ok200
391−instance OVERLAPPING_
392− HasServer (Patch ctypes ()) where
394− type ServerT (Patch ctypes ()) m = m ()
396− route Proxy = methodRouterEmpty methodPatch
398−-- Add response headers
399−instance OVERLAPPING_
400− ( GetHeaders (Headers h v), AllCTRender ctypes v
401− ) => HasServer (Patch ctypes (Headers h v)) where
403− type ServerT (Patch ctypes (Headers h v)) m = m (Headers h v)
405− route Proxy = methodRouterHeaders methodPatch (Proxy :: Proxy ctypes) ok200
407-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
244-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
408-- this automatically requires your server-side handler to be a function
245-- this automatically requires your server-side handler to be a function
409-- that takes an argument of type @'Maybe' 'Text'@.
246-- that takes an argument of type @'Maybe' 'Text'@.