@@ -26,12 +26,15 @@ module Servant.Server.Internal
2626 , module Servant.Server.Internal.ServantErr
2727 ) where
2828
29+import Control.Monad (when)
2930import Control.Monad.Trans (liftIO)
3031import Control.Monad.Trans.Resource (runResourceT)
3132import qualified Data.ByteString as B
33+import qualified Data.ByteString.Builder as BB
3234import qualified Data.ByteString.Char8 as BC8
3335import qualified Data.ByteString.Lazy as BL
34−import Data.Maybe (fromMaybe, mapMaybe)
36+import Data.Maybe (fromMaybe, mapMaybe,
37+ isNothing, maybeToList)
3538import Data.Either (partitionEithers)
3639import Data.String (fromString)
3740import Data.String.Conversions (cs, (<>))
@@ -41,13 +44,15 @@ import Data.Typeable
4144import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
4245 symbolVal)
4346import Network.HTTP.Types hiding (Header, ResponseHeaders)
47+import qualified Network.HTTP.Media as NHM
4448import Network.Socket (SockAddr)
4549import Network.Wai (Application, Request,
4650 httpVersion, isSecure,
4751 lazyRequestBody,
4852 rawQueryString, remoteHost,
4953 requestHeaders, requestMethod,
50− responseLBS, vault)
54+ responseLBS, responseStream,
55+ vault)
5156import Prelude ()
5257import Prelude.Compat
5358import Web.HttpApiData (FromHttpApiData, parseHeader,
@@ -61,11 +66,16 @@ import Servant.API ((:<|>) (..), (:>), BasicAuth, Capt
6166 QueryParam, QueryParams, Raw,
6267 RemoteHost, ReqBody, Vault,
6368 WithNamedContext,
64− Description, Summary)
69+ Description, Summary,
70+ Accept(..),
71+ FramingRender(..), Stream,
72+ StreamGenerator(..), ToStreamGenerator(..),
73+ BoundaryStrategy(..))
6574import Servant.API.ContentTypes (AcceptHeader (..),
6675 AllCTRender (..),
6776 AllCTUnrender (..),
6877 AllMime,
78+ MimeRender(..),
6979 canHandleAcceptH)
7080import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
7181 getResponse)
@@ -257,6 +267,70 @@ instance OVERLAPPING_
257267 where method = reflectMethod (Proxy :: Proxy method)
258268 status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
259269
270+
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
275+
276+ type ServerT (Stream method framing ctype (f a)) m = m (f a)
277+ hoistServerWithContext _ _ nt s = nt s
278+
279+ route Proxy _ = streamRouter ([],) method (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
280+ where method = reflectMethod (Proxy :: Proxy method)
281+
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
287+
288+ type ServerT (Stream method framing ctype (Headers h (f a))) m = m (Headers h (f a))
289+ hoistServerWithContext _ _ nt s = nt s
290+
291+ route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
292+ where method = reflectMethod (Proxy :: Proxy method)
293+
294+
295+streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenerator f a) =>
296+ (b -> ([(HeaderName, B.ByteString)], f a))
297+ -> Method
298+ -> Proxy framing
299+ -> Proxy ctype
300+ -> Delayed env (Handler b)
301+ -> Router env
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
321+ in k go go
322+ BoundaryStrategyIntersperse sep -> k
323+ (\x -> do
324+ write . BB.lazyByteString . mimeRender ctypeproxy $ x
325+ flush)
326+ (\x -> do
327+ write . (BB.lazyByteString sep <>) . BB.lazyByteString . mimeRender ctypeproxy $ x
328+ flush)
329+ BoundaryStrategyGeneral f ->
330+ let go = (>> flush) . write . BB.lazyByteString . f . mimeRender ctypeproxy
331+ in k go go
332+ write . BB.lazyByteString $ trailer framingproxy ctypeproxy
333+
260334-- | If you use 'Header' in one of the endpoints for your API,
261335-- this automatically requires your server-side handler to be a function
262336-- that takes an argument of the type specified by 'Header'.
@@ -299,7 +373,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
299373 <> fromString headerName
300374 <> " failed: " <> e
301375 }
302− Right header -> return $ Just header
376+ Right hdr -> return $ Just hdr
303377
304378-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
305379-- this automatically requires your server-side handler to be a function