@@ -6,6 +6,7 @@
6{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleContexts #-}
7{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE FlexibleInstances #-}
8{-# LANGUAGE InstanceSigs #-}
8{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE LambdaCase #-}
9{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE PolyKinds #-}
12{-# LANGUAGE PolyKinds #-}
@@ -35,9 +36,10 @@ module Servant.Server.Internal
35import Control.Monad
36import Control.Monad
36 (join, when)
37 (join, when)
37import Control.Monad.Trans
38import Control.Monad.Trans
38− (liftIO)
39+ (liftIO, lift)
39import Control.Monad.Trans.Resource
40import Control.Monad.Trans.Resource
40− (runResourceT)
41+ (runResourceT, ReleaseKey)
41import qualified Data.ByteString as B
43import qualified Data.ByteString as B
42import qualified Data.ByteString.Builder as BB
44import qualified Data.ByteString.Builder as BB
43import qualified Data.ByteString.Char8 as BC8
45import qualified Data.ByteString.Char8 as BC8
@@ -64,7 +66,7 @@ import Network.HTTP.Types hiding
64import Network.Socket
66import Network.Socket
65 (SockAddr)
67 (SockAddr)
66import Network.Wai
68import Network.Wai
67− (Application, Request, httpVersion, isSecure, lazyRequestBody,
69+ (Application, Request, Response, ResponseReceived, httpVersion, isSecure, lazyRequestBody,
68 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
70 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
69 requestMethod, responseLBS, responseStream, vault)
71 requestMethod, responseLBS, responseStream, vault)
70import Prelude ()
72import Prelude ()
@@ -74,10 +76,10 @@ import Servant.API
74 CaptureAll, Description, EmptyAPI, Fragment,
76 CaptureAll, Description, EmptyAPI, Fragment,
75 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
77 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
76 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
78 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
77− QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
79+ QueryParam', QueryParams, Raw, RawM, ReflectMethod (reflectMethod),
78 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
80 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
79 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
81 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
80− WithNamedContext, NamedRoutes)
82+ WithNamedContext, WithResource, NamedRoutes)
81import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
83import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
82import Servant.API.ContentTypes
84import Servant.API.ContentTypes
83 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
85 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
@@ -115,6 +117,10 @@ import Servant.API.TypeLevel
115 (AtLeastOneFragment, FragmentUnique)
117 (AtLeastOneFragment, FragmentUnique)
117class HasServer api context where
119class HasServer api context where
120+ -- | The type of a server for this API, given a monad to run effects in.
122+ -- Note that the result kind is @*@, so it is /not/ a monad transformer, unlike
123+ -- what the @T@ in the name might suggest.
118 type ServerT api (m :: * -> *) :: *
124 type ServerT api (m :: * -> *) :: *
@@ -244,6 +250,42 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
244 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
250 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
245 hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
251 hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
253+-- | If you use 'WithResource' in one of the endpoints for your API Servant
254+-- will provide the handler for this endpoint an argument of the specified type.
255+-- The lifespan of this resource will be automatically managed by Servant. This
256+-- resource will be created before the handler starts and it will be destoyed
257+-- after it ends. A new resource is created for each request to the endpoint.
259+-- The creation and destruction are done using a 'Data.Acquire.Acquire'
260+-- provided via server 'Context'.
264+-- > type MyApi = WithResource Handle :> "writeToFile" :> Post '[JSON] NoContent
266+-- > server :: Server MyApi
267+-- > server = writeToFile
268+-- > where writeToFile :: (ReleaseKey, Handle) -> Handler NoContent
269+-- > writeToFile (_, h) = hPutStrLn h "message"
271+-- In addition to the resource, the handler will also receive a 'ReleaseKey'
272+-- which can be used to deallocate the resource before the end of the request
275+instance (HasServer api ctx, HasContextEntry ctx (Acquire a))
276+ => HasServer (WithResource a :> api) ctx where
278+ type ServerT (WithResource a :> api) m = (ReleaseKey, a) -> ServerT api m
280+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy @api) pc nt . s
282+ route Proxy context d = route (Proxy @api) context (d `addParameterCheck` allocateResource)
284+ allocateResource :: DelayedIO (ReleaseKey, a)
285+ allocateResource = DelayedIO $ lift $ allocateAcquire (getContextEntry context)
247allowedMethodHead :: Method -> Request -> Bool
289allowedMethodHead :: Method -> Request -> Bool
248allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
290allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -611,6 +653,35 @@ instance HasServer Raw context where
611 Fail a -> respond $ Fail a
653 Fail a -> respond $ Fail a
612 FailFatal e -> respond $ FailFatal e
654 FailFatal e -> respond $ FailFatal e
656+-- | Just pass the request to the underlying application and serve its response.
660+-- > type MyApi = "images" :> Raw
662+-- > server :: Server MyApi
663+-- > server = serveDirectory "/var/www/images"
664+instance HasServer RawM context where
665+ type ServerT RawM m = Request -> (Response -> IO ResponseReceived) -> m ResponseReceived
670+ -> Delayed env (Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived) -> Router env
671+ route _ _ handleDelayed = RawRouter $ \env request respond -> runResourceT $ do
672+ routeResult <- runDelayed handleDelayed env request
673+ let respond' = liftIO . respond
674+ liftIO $ case routeResult of
675+ Route handler -> runHandler (handler request (respond . Route)) >>=
677+ Left e -> respond' $ FailFatal e
679+ Fail e -> respond' $ Fail e
680+ FailFatal e -> respond' $ FailFatal e
682+ hoistServerWithContext _ _ f srvM = \req respond -> f (srvM req respond)
614-- | If you use 'ReqBody' in one of the endpoints for your API,
685-- | If you use 'ReqBody' in one of the endpoints for your API,
615-- this automatically requires your server-side handler to be a function
686-- this automatically requires your server-side handler to be a function
616-- that takes an argument of the type specified by 'ReqBody'.
687-- that takes an argument of the type specified by 'ReqBody'.