@@ -14,6 +14,9 @@ module Servant.Client.Internal.HttpClient where
14import Prelude ()
14import Prelude ()
15import Prelude.Compat
15import Prelude.Compat
17+import Control.Concurrent.MVar
18+ (modifyMVar, newMVar)
19+import qualified Data.ByteString as BS
17import Control.Concurrent.STM.TVar
20import Control.Concurrent.STM.TVar
18import Control.Exception
21import Control.Exception
19import Control.Monad
22import Control.Monad
@@ -52,7 +55,6 @@ import Data.Sequence
52 (fromList)
55 (fromList)
53import Data.String
56import Data.String
54 (fromString)
57 (fromString)
55−import qualified Data.Text as T
56import Data.Time.Clock
58import Data.Time.Clock
57 (UTCTime, getCurrentTime)
59 (UTCTime, getCurrentTime)
58import GHC.Generics
60import GHC.Generics
@@ -62,6 +64,7 @@ import Network.HTTP.Types
62 (hContentType, renderQuery, statusCode)
64 (hContentType, renderQuery, statusCode)
63import Servant.Client.Core
65import Servant.Client.Core
67+import qualified Servant.Types.SourceT as S
65import qualified Network.HTTP.Client as Client
68import qualified Network.HTTP.Client as Client
67-- | The environment in which a request is run.
70-- | The environment in which a request is run.
@@ -167,7 +170,7 @@ performRequest req = do
167 response <- maybe (requestWithoutCookieJar m request) (requestWithCookieJar m request) cookieJar'
170 response <- maybe (requestWithoutCookieJar m request) (requestWithCookieJar m request) cookieJar'
168 let status = Client.responseStatus response
171 let status = Client.responseStatus response
169 status_code = statusCode status
172 status_code = statusCode status
170− ourResponse = clientResponseToResponse response
173+ ourResponse = clientResponseToResponse id response
171 unless (status_code >= 200 && status_code < 300) $
174 unless (status_code >= 200 && status_code < 300) $
172 throwError $ mkFailureResponse burl req ourResponse
175 throwError $ mkFailureResponse burl req ourResponse
173 return ourResponse
176 return ourResponse
@@ -197,34 +200,34 @@ performRequest req = do
197 fReq = Client.hrFinalRequest responses
200 fReq = Client.hrFinalRequest responses
198 fRes = Client.hrFinalResponse responses
201 fRes = Client.hrFinalResponse responses
200−mkFailureResponse :: BaseUrl -> Request -> GenResponse BSL.ByteString -> ServantError
203+mkFailureResponse :: BaseUrl -> Request -> ResponseF BSL.ByteString -> ServantError
201mkFailureResponse burl request =
204mkFailureResponse burl request =
202 FailureResponse (bimap (const ()) f request)
205 FailureResponse (bimap (const ()) f request)
204 f b = (burl, BSL.toStrict $ toLazyByteString b)
207 f b = (burl, BSL.toStrict $ toLazyByteString b)
206−clientResponseToResponse :: Client.Response a -> GenResponse a
209+clientResponseToResponse :: (a -> b) -> Client.Response a -> ResponseF b
207−clientResponseToResponse r = Response
210+clientResponseToResponse f r = Response
208− { responseStatusCode = Client.responseStatus r
211+ { responseStatusCode = Client.responseStatus r
209− , responseBody = Client.responseBody r
212+ , responseBody = f (Client.responseBody r)
210− , responseHeaders = fromList $ Client.responseHeaders r
213+ , responseHeaders = fromList $ Client.responseHeaders r
211− , responseHttpVersion = Client.responseVersion r
214+ , responseHttpVersion = Client.responseVersion r
214requestToClientRequest :: BaseUrl -> Request -> Client.Request
217requestToClientRequest :: BaseUrl -> Request -> Client.Request
215requestToClientRequest burl r = Client.defaultRequest
218requestToClientRequest burl r = Client.defaultRequest
216− { Client.method = requestMethod r
219+ { Client.method = requestMethod r
217− , Client.host = fromString $ baseUrlHost burl
220+ , Client.host = fromString $ baseUrlHost burl
218− , Client.port = baseUrlPort burl
221+ , Client.port = baseUrlPort burl
219− , Client.path = BSL.toStrict
222+ , Client.path = BSL.toStrict
220− $ fromString (baseUrlPath burl)
223+ $ fromString (baseUrlPath burl)
221− <> toLazyByteString (requestPath r)
224+ <> toLazyByteString (requestPath r)
222− , Client.queryString = renderQuery True . toList $ requestQueryString r
225+ , Client.queryString = renderQuery True . toList $ requestQueryString r
223− , Client.requestHeaders =
226+ , Client.requestHeaders =
224− maybeToList acceptHdr ++ maybeToList contentTypeHdr ++ headers
227+ maybeToList acceptHdr ++ maybeToList contentTypeHdr ++ headers
225− , Client.requestBody = body
228+ , Client.requestBody = body
226− , Client.secure = isSecure
229+ , Client.secure = isSecure
229 -- Content-Type and Accept are specified by requestBody and requestAccept
232 -- Content-Type and Accept are specified by requestBody and requestAccept
230 headers = filter (\(h, _) -> h /= "Accept" && h /= "Content-Type") $
233 headers = filter (\(h, _) -> h /= "Accept" && h /= "Content-Type") $
@@ -237,21 +240,38 @@ requestToClientRequest burl r = Client.defaultRequest
237 hs = toList $ requestAccept r
240 hs = toList $ requestAccept r
239 convertBody bd = case bd of
242 convertBody bd = case bd of
240− RequestBodyLBS body' -> Client.RequestBodyLBS body'
243+ RequestBodyLBS body' -> Client.RequestBodyLBS body'
241− RequestBodyBS body' -> Client.RequestBodyBS body'
244+ RequestBodyBS body' -> Client.RequestBodyBS body'
242− RequestBodyBuilder size body' -> Client.RequestBodyBuilder size body'
245+ RequestBodySource sourceIO -> Client.RequestBodyStreamChunked givesPopper
243− RequestBodyStream size body' -> Client.RequestBodyStream size body'
246+ where
244− RequestBodyStreamChunked body' -> Client.RequestBodyStreamChunked body'
247+ givesPopper :: (IO BS.ByteString -> IO ()) -> IO ()
245− RequestBodyIO body' -> Client.RequestBodyIO (convertBody <$> body')
248+ givesPopper needsPopper = S.unSourceT sourceIO $ \step0 -> do
249+ ref <- newMVar step0
251+ -- Note sure we need locking, but it's feels safer.
252+ let popper :: IO BS.ByteString
253+ popper = modifyMVar ref nextBs
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
263+ (x:xs) | BS.null x -> nextBs step'
264+ | otherwise -> return (step', x)
266+ step' = S.Yield (BSL.fromChunks xs) s
247 (body, contentTypeHdr) = case requestBody r of
268 (body, contentTypeHdr) = case requestBody r of
248− Nothing -> (Client.RequestBodyLBS "", Nothing)
269+ Nothing -> (Client.RequestBodyBS "", Nothing)
250− -> (convertBody body', Just (hContentType, renderHeader typ))
270+ Just (body', typ) -> (convertBody body', Just (hContentType, renderHeader typ))
252 isSecure = case baseUrlScheme burl of
272 isSecure = case baseUrlScheme burl of
253− Http -> False
273+ Http -> False
254− Https -> True
274+ Https -> True
256catchConnectionError :: IO a -> IO (Either ServantError a)
276catchConnectionError :: IO a -> IO (Either ServantError a)
257catchConnectionError action =
277catchConnectionError action =