@@ -4,6 +4,7 @@
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE InstanceSigs #-}
5{-# LANGUAGE InstanceSigs #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7+{-# LANGUAGE RankNTypes #-}
7{-# LANGUAGE OverloadedStrings #-}
8{-# LANGUAGE OverloadedStrings #-}
8{-# LANGUAGE PolyKinds #-}
9{-# LANGUAGE PolyKinds #-}
9{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE ScopedTypeVariables #-}
@@ -17,9 +18,12 @@ module Servant.Client.Core.Internal.HasClient where
17import Prelude ()
18import Prelude ()
18import Prelude.Compat
19import Prelude.Compat
21+import Control.Concurrent (newMVar, modifyMVar)
22+import Control.Monad (when)
20import Data.Foldable (toList)
23import Data.Foldable (toList)
21import qualified Data.ByteString.Lazy as BL
24import qualified Data.ByteString.Lazy as BL
22import Data.List (foldl')
25import Data.List (foldl')
26+import Data.Monoid ((<>))
23import Data.Proxy (Proxy (Proxy))
27import Data.Proxy (Proxy (Proxy))
24import Data.Sequence (fromList)
28import Data.Sequence (fromList)
25import Data.String (fromString)
29import Data.String (fromString)
@@ -31,6 +35,7 @@ import Servant.API ((:<|>) ((:<|>)), (:>),
31 BasicAuthData,
35 BasicAuthData,
32 BuildHeadersTo (..),
36 BuildHeadersTo (..),
33 BuildFromStream (..),
37 BuildFromStream (..),
38+ ByteStringParser (..),
34 Capture, CaptureAll,
39 Capture, CaptureAll,
35 Description, EmptyAPI,
40 Description, EmptyAPI,
36 FramingUnrender (..),
41 FramingUnrender (..),
@@ -248,44 +253,54 @@ instance OVERLAPPING_
248 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
253 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
256+data ResultStream a = ResultStream ((forall b. (IO (Maybe (Either String a)) -> IO b) -> IO b))
251instance OVERLAPPABLE_
258instance OVERLAPPABLE_
252 ( RunClient m, MimeUnrender ct a, ReflectMethod method,
259 ( RunClient m, MimeUnrender ct a, ReflectMethod method,
253 FramingUnrender framing a, BuildFromStream a (f a)
260 FramingUnrender framing a, BuildFromStream a (f a)
254 ) => HasClient m (Stream method framing ct (f a)) where
261 ) => HasClient m (Stream method framing ct (f a)) where
255− type Client m (Stream method framing ct (f a)) = m (f a)
256− clientWithRoute _pm Proxy req = do
257− response <- runRequest req
258− { requestAccept = fromList [contentType (Proxy :: Proxy ct)]
259− , requestMethod = reflectMethod (Proxy :: Proxy method)
261− return $ decodeFramed (Proxy :: Proxy framing) (Proxy :: Proxy ct) (Proxy :: Proxy a) response
263−instance OVERLAPPING_
264− ( RunClient m, BuildHeadersTo ls, MimeUnrender ct a, ReflectMethod method,
265− FramingUnrender framing a, BuildFromStream a (f a)
266− ) => HasClient m (Stream method framing ct (Headers ls (f a))) where
267− type Client m (Stream method framing ct (Headers ls (f a))) = m (Headers ls (f a))
263+ type Client m (Stream method framing ct (f a)) = m (ResultStream a)
268 clientWithRoute _pm Proxy req = do
265 clientWithRoute _pm Proxy req = do
269− response <- runRequest req
266+ sresp <- streamingRequest req
270 { requestAccept = fromList [contentType (Proxy :: Proxy ct)]
267 { requestAccept = fromList [contentType (Proxy :: Proxy ct)]
271 , requestMethod = reflectMethod (Proxy :: Proxy method)
268 , requestMethod = reflectMethod (Proxy :: Proxy method)
273− return Headers { getResponse = decodeFramed (Proxy :: Proxy framing) (Proxy :: Proxy ct) (Proxy :: Proxy a) response
270+ return $ ResultStream $ \k ->
274− , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
271+ runStreamingResponse sresp $ \(status,_headers,_httpversion,reader) -> do
275− }
272+ when (H.statusCode status /= 200) $ error "bad status" --fixme
276−
273+ let unrender = unrenderFrames (Proxy :: Proxy framing) (Proxy :: Proxy a)
277−decodeFramed :: forall ctype strategy a b.
274+ loop bs = do
278− (MimeUnrender ctype a, FramingUnrender strategy a, BuildFromStream a b) =>
275+ res <- BL.fromStrict <$> reader
279− Proxy strategy -> Proxy ctype -> Proxy a -> Response -> b
276+ if BL.null res
280−decodeFramed framingproxy ctypeproxy typeproxy response =
277+ then return $ parseEOF unrender res
281− let (body, uncons) = unrenderFrames framingproxy typeproxy (responseBody response)
278+ else let sofar = (bs <> res)
282− loop b | BL.null b = []
279+ in case parseIncremental unrender sofar of
283− | otherwise = case uncons b of
280+ Just x -> return x
281+ Nothing -> loop sofar
282+ (frameParser, remainder) <- loop BL.empty
283+ state <- newMVar remainder
284+ let frameLoop bs = do
285+ res <- BL.fromStrict <$> reader
286+ let addIsEmptyInfo (a, r) = (r, (a, BL.null r && BL.null res))
288+ then return . addIsEmptyInfo $ parseEOF frameParser res
289+ else let sofar = (bs <> res)
290+ in case parseIncremental frameParser res of
291+ Just x -> return $ addIsEmptyInfo x
292+ Nothing -> frameLoop sofar
294+ go = processResult <$> modifyMVar state frameLoop
295+ processResult (Right bs,isDone) =
296+ if BL.null bs && isDone
284− (Right x, r) -> case mimeUnrender ctypeproxy x :: Either String a of
298+ else Just $ case mimeUnrender (Proxy :: Proxy ct) bs :: Either String a of
285− Left err -> Left err : loop r
299+ Left err -> Left err
286− Right x' -> Right x' : loop r
300+ Right x -> Right x
287− (Left err, r) -> Left err : loop r
301+ processResult (Left err, _) = Just (Left err)
288− in buildFromStream $ loop body
302+ k go
290-- | If you use a 'Header' in one of your endpoints in your API,
305-- | If you use a 'Header' in one of your endpoints in your API,
291-- the corresponding querying function will automatically take
306-- the corresponding querying function will automatically take