@@ -6,6 +6,7 @@
66{-# LANGUAGE FlexibleContexts #-}
77{-# LANGUAGE FlexibleInstances #-}
88{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE LambdaCase #-}
910{-# LANGUAGE MultiParamTypeClasses #-}
1011{-# LANGUAGE OverloadedStrings #-}
1112{-# LANGUAGE PolyKinds #-}
@@ -35,9 +36,10 @@ module Servant.Server.Internal
3536import Control.Monad
3637 (join, when)
3738import Control.Monad.Trans
38− (liftIO)
39+ (liftIO, lift)
3940import Control.Monad.Trans.Resource
40− (runResourceT)
41+ (runResourceT, ReleaseKey)
42+import Data.Acquire
4143import qualified Data.ByteString as B
4244import qualified Data.ByteString.Builder as BB
4345import qualified Data.ByteString.Char8 as BC8
@@ -64,7 +66,7 @@ import Network.HTTP.Types hiding
6466import Network.Socket
6567 (SockAddr)
6668import Network.Wai
67− (Application, Request, httpVersion, isSecure, lazyRequestBody,
69+ (Application, Request, Response, ResponseReceived, httpVersion, isSecure, lazyRequestBody,
6870 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
6971 requestMethod, responseLBS, responseStream, vault)
7072import Prelude ()
@@ -74,10 +76,10 @@ import Servant.API
7476 CaptureAll, Description, EmptyAPI, Fragment,
7577 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
7678 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
77− QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
79+ QueryParam', QueryParams, Raw, RawM, ReflectMethod (reflectMethod),
7880 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
7981 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
80− WithNamedContext, NamedRoutes)
82+ WithNamedContext, WithResource, NamedRoutes)
8183import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
8284import Servant.API.ContentTypes
8385 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
@@ -115,6 +117,10 @@ import Servant.API.TypeLevel
115117 (AtLeastOneFragment, FragmentUnique)
116118
117119class HasServer api context where
120+ -- | The type of a server for this API, given a monad to run effects in.
121+ --
122+ -- Note that the result kind is @*@, so it is /not/ a monad transformer, unlike
123+ -- what the @T@ in the name might suggest.
118124 type ServerT api (m :: * -> *) :: *
119125
120126 route ::
@@ -244,6 +250,42 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
244250 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
245251 hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
246252
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.
258+
259+-- The creation and destruction are done using a 'Data.Acquire.Acquire'
260+-- provided via server 'Context'.
261+--
262+-- Example
263+--
264+-- > type MyApi = WithResource Handle :> "writeToFile" :> Post '[JSON] NoContent
265+-- >
266+-- > server :: Server MyApi
267+-- > server = writeToFile
268+-- > where writeToFile :: (ReleaseKey, Handle) -> Handler NoContent
269+-- > writeToFile (_, h) = hPutStrLn h "message"
270+--
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
273+-- if desired.
274+
275+instance (HasServer api ctx, HasContextEntry ctx (Acquire a))
276+ => HasServer (WithResource a :> api) ctx where
277+
278+ type ServerT (WithResource a :> api) m = (ReleaseKey, a) -> ServerT api m
279+
280+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy @api) pc nt . s
281+
282+ route Proxy context d = route (Proxy @api) context (d `addParameterCheck` allocateResource)
283+ where
284+ allocateResource :: DelayedIO (ReleaseKey, a)
285+ allocateResource = DelayedIO $ lift $ allocateAcquire (getContextEntry context)
286+
287+
288+
247289allowedMethodHead :: Method -> Request -> Bool
248290allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
249291
@@ -611,6 +653,35 @@ instance HasServer Raw context where
611653 Fail a -> respond $ Fail a
612654 FailFatal e -> respond $ FailFatal e
613655
656+-- | Just pass the request to the underlying application and serve its response.
657+--
658+-- Example:
659+--
660+-- > type MyApi = "images" :> Raw
661+-- >
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
666+
667+ route
668+ :: Proxy RawM
669+ -> Context context
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)) >>=
676+ \case
677+ Left e -> respond' $ FailFatal e
678+ Right a -> pure a
679+ Fail e -> respond' $ Fail e
680+ FailFatal e -> respond' $ FailFatal e
681+
682+ hoistServerWithContext _ _ f srvM = \req respond -> f (srvM req respond)
683+
684+
614685-- | If you use 'ReqBody' in one of the endpoints for your API,
615686-- this automatically requires your server-side handler to be a function
616687-- that takes an argument of the type specified by 'ReqBody'.