@@ -14,6 +14,9 @@ module Servant.Client.Internal.HttpClient where
1414import Prelude ()
1515import Prelude.Compat
1616
17+import Control.Concurrent.MVar
18+ (modifyMVar, newMVar)
19+import qualified Data.ByteString as BS
1720import Control.Concurrent.STM.TVar
1821import Control.Exception
1922import Control.Monad
@@ -52,7 +55,6 @@ import Data.Sequence
5255 (fromList)
5356import Data.String
5457 (fromString)
55−import qualified Data.Text as T
5658import Data.Time.Clock
5759 (UTCTime, getCurrentTime)
5860import GHC.Generics
@@ -62,6 +64,7 @@ import Network.HTTP.Types
6264 (hContentType, renderQuery, statusCode)
6365import Servant.Client.Core
6466
67+import qualified Servant.Types.SourceT as S
6568import qualified Network.HTTP.Client as Client
6669
6770-- | The environment in which a request is run.
@@ -167,7 +170,7 @@ performRequest req = do
167170 response <- maybe (requestWithoutCookieJar m request) (requestWithCookieJar m request) cookieJar'
168171 let status = Client.responseStatus response
169172 status_code = statusCode status
170− ourResponse = clientResponseToResponse response
173+ ourResponse = clientResponseToResponse id response
171174 unless (status_code >= 200 && status_code < 300) $
172175 throwError $ mkFailureResponse burl req ourResponse
173176 return ourResponse
@@ -197,34 +200,34 @@ performRequest req = do
197200 fReq = Client.hrFinalRequest responses
198201 fRes = Client.hrFinalResponse responses
199202
200−mkFailureResponse :: BaseUrl -> Request -> GenResponse BSL.ByteString -> ServantError
203+mkFailureResponse :: BaseUrl -> Request -> ResponseF BSL.ByteString -> ServantError
201204mkFailureResponse burl request =
202205 FailureResponse (bimap (const ()) f request)
203206 where
204207 f b = (burl, BSL.toStrict $ toLazyByteString b)
205208
206−clientResponseToResponse :: Client.Response a -> GenResponse a
207−clientResponseToResponse r = Response
208− { responseStatusCode = Client.responseStatus r
209− , responseBody = Client.responseBody r
210− , responseHeaders = fromList $ Client.responseHeaders r
211− , responseHttpVersion = Client.responseVersion r
212− }
209+clientResponseToResponse :: (a -> b) -> Client.Response a -> ResponseF b
210+clientResponseToResponse f r = Response
211+ { responseStatusCode = Client.responseStatus r
212+ , responseBody = f (Client.responseBody r)
213+ , responseHeaders = fromList $ Client.responseHeaders r
214+ , responseHttpVersion = Client.responseVersion r
215+ }
213216
214217requestToClientRequest :: BaseUrl -> Request -> Client.Request
215218requestToClientRequest burl r = Client.defaultRequest
216− { Client.method = requestMethod r
217− , Client.host = fromString $ baseUrlHost burl
218− , Client.port = baseUrlPort burl
219− , Client.path = BSL.toStrict
220− $ fromString (baseUrlPath burl)
221− <> toLazyByteString (requestPath r)
222− , Client.queryString = renderQuery True . toList $ requestQueryString r
223− , Client.requestHeaders =
224− maybeToList acceptHdr ++ maybeToList contentTypeHdr ++ headers
225− , Client.requestBody = body
226− , Client.secure = isSecure
227− }
219+ { Client.method = requestMethod r
220+ , Client.host = fromString $ baseUrlHost burl
221+ , Client.port = baseUrlPort burl
222+ , Client.path = BSL.toStrict
223+ $ fromString (baseUrlPath burl)
224+ <> toLazyByteString (requestPath r)
225+ , Client.queryString = renderQuery True . toList $ requestQueryString r
226+ , Client.requestHeaders =
227+ maybeToList acceptHdr ++ maybeToList contentTypeHdr ++ headers
228+ , Client.requestBody = body
229+ , Client.secure = isSecure
230+ }
228231 where
229232 -- Content-Type and Accept are specified by requestBody and requestAccept
230233 headers = filter (\(h, _) -> h /= "Accept" && h /= "Content-Type") $
@@ -237,21 +240,38 @@ requestToClientRequest burl r = Client.defaultRequest
237240 hs = toList $ requestAccept r
238241
239242 convertBody bd = case bd of
240− RequestBodyLBS body' -> Client.RequestBodyLBS body'
241− RequestBodyBS body' -> Client.RequestBodyBS body'
242− RequestBodyBuilder size body' -> Client.RequestBodyBuilder size body'
243− RequestBodyStream size body' -> Client.RequestBodyStream size body'
244− RequestBodyStreamChunked body' -> Client.RequestBodyStreamChunked body'
245− RequestBodyIO body' -> Client.RequestBodyIO (convertBody <$> body')
243+ RequestBodyLBS body' -> Client.RequestBodyLBS body'
244+ RequestBodyBS body' -> Client.RequestBodyBS body'
245+ RequestBodySource sourceIO -> Client.RequestBodyStreamChunked givesPopper
246+ where
247+ givesPopper :: (IO BS.ByteString -> IO ()) -> IO ()
248+ givesPopper needsPopper = S.unSourceT sourceIO $ \step0 -> do
249+ ref <- newMVar step0
250+
251+ -- Note sure we need locking, but it's feels safer.
252+ let popper :: IO BS.ByteString
253+ popper = modifyMVar ref nextBs
254+
255+ needsPopper popper
256+
257+ nextBs S.Stop = return (S.Stop, BS.empty)
258+ nextBs (S.Error err) = fail err
259+ nextBs (S.Skip s) = nextBs s
260+ nextBs (S.Effect ms) = ms >>= nextBs
261+ nextBs (S.Yield lbs s) = case BSL.toChunks lbs of
262+ [] -> nextBs s
263+ (x:xs) | BS.null x -> nextBs step'
264+ | otherwise -> return (step', x)
265+ where
266+ step' = S.Yield (BSL.fromChunks xs) s
246267
247268 (body, contentTypeHdr) = case requestBody r of
248− Nothing -> (Client.RequestBodyLBS "", Nothing)
249− Just (body', typ)
250− -> (convertBody body', Just (hContentType, renderHeader typ))
269+ Nothing -> (Client.RequestBodyBS "", Nothing)
270+ Just (body', typ) -> (convertBody body', Just (hContentType, renderHeader typ))
251271
252272 isSecure = case baseUrlScheme burl of
253− Http -> False
254− Https -> True
273+ Http -> False
274+ Https -> True
255275
256276catchConnectionError :: IO a -> IO (Either ServantError a)
257277catchConnectionError action =