@@ -26,12 +26,15 @@ module Servant.Server.Internal
26 , module Servant.Server.Internal.ServantErr
26 , module Servant.Server.Internal.ServantErr
29+import Control.Monad (when)
29import Control.Monad.Trans (liftIO)
30import Control.Monad.Trans (liftIO)
30import Control.Monad.Trans.Resource (runResourceT)
31import Control.Monad.Trans.Resource (runResourceT)
31import qualified Data.ByteString as B
32import qualified Data.ByteString as B
33+import qualified Data.ByteString.Builder as BB
32import qualified Data.ByteString.Char8 as BC8
34import qualified Data.ByteString.Char8 as BC8
33import qualified Data.ByteString.Lazy as BL
35import qualified Data.ByteString.Lazy as BL
34−import Data.Maybe (fromMaybe, mapMaybe)
36+import Data.Maybe (fromMaybe, mapMaybe,
37+ isNothing, maybeToList)
35import Data.Either (partitionEithers)
38import Data.Either (partitionEithers)
36import Data.String (fromString)
39import Data.String (fromString)
37import Data.String.Conversions (cs, (<>))
40import Data.String.Conversions (cs, (<>))
@@ -41,13 +44,15 @@ import Data.Typeable
41import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
44import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
42 symbolVal)
45 symbolVal)
43import Network.HTTP.Types hiding (Header, ResponseHeaders)
46import Network.HTTP.Types hiding (Header, ResponseHeaders)
47+import qualified Network.HTTP.Media as NHM
44import Network.Socket (SockAddr)
48import Network.Socket (SockAddr)
45import Network.Wai (Application, Request,
49import Network.Wai (Application, Request,
46 httpVersion, isSecure,
50 httpVersion, isSecure,
47 lazyRequestBody,
51 lazyRequestBody,
48 rawQueryString, remoteHost,
52 rawQueryString, remoteHost,
49 requestHeaders, requestMethod,
53 requestHeaders, requestMethod,
50− responseLBS, vault)
54+ responseLBS, responseStream,
51import Prelude ()
56import Prelude ()
52import Prelude.Compat
57import Prelude.Compat
53import Web.HttpApiData (FromHttpApiData, parseHeader,
58import Web.HttpApiData (FromHttpApiData, parseHeader,
@@ -61,11 +66,16 @@ import Servant.API ((:<|>) (..), (:>), BasicAuth, Capt
61 QueryParam, QueryParams, Raw,
66 QueryParam, QueryParams, Raw,
62 RemoteHost, ReqBody, Vault,
67 RemoteHost, ReqBody, Vault,
63 WithNamedContext,
68 WithNamedContext,
64− Description, Summary)
69+ Description, Summary,
71+ FramingRender(..), Stream,
72+ StreamGenerator(..), ToStreamGenerator(..),
73+ BoundaryStrategy(..))
65import Servant.API.ContentTypes (AcceptHeader (..),
74import Servant.API.ContentTypes (AcceptHeader (..),
66 AllCTRender (..),
75 AllCTRender (..),
67 AllCTUnrender (..),
76 AllCTUnrender (..),
69 canHandleAcceptH)
79 canHandleAcceptH)
70import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
80import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
71 getResponse)
81 getResponse)
@@ -257,6 +267,70 @@ instance OVERLAPPING_
257 where method = reflectMethod (Proxy :: Proxy method)
267 where method = reflectMethod (Proxy :: Proxy method)
258 status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
268 status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
271+instance OVERLAPPABLE_
272+ ( MimeRender ctype a, ReflectMethod method,
273+ FramingRender framing ctype, ToStreamGenerator f a
274+ ) => HasServer (Stream method framing ctype (f a)) context where
276+ type ServerT (Stream method framing ctype (f a)) m = m (f a)
277+ hoistServerWithContext _ _ nt s = nt s
279+ route Proxy _ = streamRouter ([],) method (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
280+ where method = reflectMethod (Proxy :: Proxy method)
282+instance OVERLAPPING_
283+ ( MimeRender ctype a, ReflectMethod method,
284+ FramingRender framing ctype, ToStreamGenerator f a,
285+ GetHeaders (Headers h (f a))
286+ ) => HasServer (Stream method framing ctype (Headers h (f a))) context where
288+ type ServerT (Stream method framing ctype (Headers h (f a))) m = m (Headers h (f a))
289+ hoistServerWithContext _ _ nt s = nt s
291+ route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
292+ where method = reflectMethod (Proxy :: Proxy method)
295+streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenerator f a) =>
296+ (b -> ([(HeaderName, B.ByteString)], f a))
300+ -> Delayed env (Handler b)
302+streamRouter splitHeaders method framingproxy ctypeproxy action = leafRouter $ \env request respond ->
303+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
304+ cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
305+ accCheck = when (isNothing cmediatype) $ delayedFail err406
306+ contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
307+ in runAction (action `addMethodCheck` methodCheck method request
308+ `addAcceptCheck` accCheck
309+ ) env request respond $ \ output ->
310+ let (headers, fa) = splitHeaders output
311+ k = getStreamGenerator . toStreamGenerator $ fa in
312+ Route $ responseStream status200 (contentHeader : headers) $ \write flush -> do
313+ write . BB.lazyByteString $ header framingproxy ctypeproxy
314+ case boundary framingproxy ctypeproxy of
315+ BoundaryStrategyBracket f ->
316+ let go x = let bs = mimeRender ctypeproxy $ x
317+ (before, after) = f bs
318+ in write ( BB.lazyByteString before
319+ <> BB.lazyByteString bs
320+ <> BB.lazyByteString after) >> flush
322+ BoundaryStrategyIntersperse sep -> k
324+ write . BB.lazyByteString . mimeRender ctypeproxy $ x
327+ write . (BB.lazyByteString sep <>) . BB.lazyByteString . mimeRender ctypeproxy $ x
329+ BoundaryStrategyGeneral f ->
330+ let go = (>> flush) . write . BB.lazyByteString . f . mimeRender ctypeproxy
332+ write . BB.lazyByteString $ trailer framingproxy ctypeproxy
260-- | If you use 'Header' in one of the endpoints for your API,
334-- | If you use 'Header' in one of the endpoints for your API,
261-- this automatically requires your server-side handler to be a function
335-- this automatically requires your server-side handler to be a function
262-- that takes an argument of the type specified by 'Header'.
336-- that takes an argument of the type specified by 'Header'.
@@ -299,7 +373,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
299 <> fromString headerName
373 <> fromString headerName
300 <> " failed: " <> e
374 <> " failed: " <> e
302− Right header -> return $ Just header
376+ Right hdr -> return $ Just hdr
304-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
378-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
305-- this automatically requires your server-side handler to be a function
379-- this automatically requires your server-side handler to be a function