{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.HTTP.Client.Core
( withResponse
, httpLbs
, httpNoBody
, httpRaw
, responseOpen
, responseClose
, applyCheckStatus
, httpRedirect
) where
import Network.HTTP.Types
import Network.HTTP.Client.Manager
import Network.HTTP.Client.Types
import Network.HTTP.Client.Body
import Network.HTTP.Client.Request
import Network.HTTP.Client.Response
import Network.HTTP.Client.Cookies
import Data.Time
import Control.Exception
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L
import Data.Monoid
import Control.Monad (void)
withResponse :: Request
-> Manager
-> (Response BodyReader -> IO a)
-> IO a
withResponse req man f = bracket (responseOpen req man) responseClose f
httpLbs :: Request -> Manager -> IO (Response L.ByteString)
httpLbs req man = withResponse req man $ \res -> do
bss <- brConsume $ responseBody res
return res { responseBody = L.fromChunks bss }
httpNoBody :: Request -> Manager -> IO (Response ())
httpNoBody req man = withResponse req man $ return . void
httpRaw
:: Request
-> Manager
-> IO (Response BodyReader)
httpRaw req' m = do
(req, cookie_jar') <- case cookieJar req' of
Just cj -> do
now <- getCurrentTime
return $ insertCookiesIntoRequest req' (evictExpiredCookies cj now) now
Nothing -> return (req', mempty)
(timeout', (connRelease, ci, isManaged)) <- getConnectionWrapper
req
(responseTimeout' req)
(failedConnectionException req)
(getConn req m)
ex <- try $ do
requestBuilder req ci
getResponse connRelease timeout' req ci
case (ex, isManaged) of
(Left e, Reused) | mRetryableException m e -> do
connRelease DontReuse
responseOpen req m
(Left e, _) -> throwIO e
(Right res, _) -> case cookieJar req' of
Just _ -> do
now' <- getCurrentTime
let (cookie_jar, _) = updateCookieJar res req now' cookie_jar'
return $ res {responseCookieJar = cookie_jar}
Nothing -> return res
where
responseTimeout' req
| rt == useDefaultTimeout = mResponseTimeout m
| otherwise = rt
where
rt = responseTimeout req
responseOpen :: Request -> Manager -> IO (Response BodyReader)
responseOpen req0 manager = mWrapIOException manager $ do
res <-
if redirectCount req0 == 0
then httpRaw req0 manager
else go (redirectCount req0) req0
maybe (return res) throwIO =<< applyCheckStatus (checkStatus req0) res
where
go count req' = httpRedirect
count
(\req -> do
res <- httpRaw req manager
let mreq = getRedirectedRequest req (responseHeaders res) (responseCookieJar res) (statusCode (responseStatus res))
return (res, mreq))
req'
applyCheckStatus
:: (Status -> ResponseHeaders -> CookieJar -> Maybe SomeException)
-> Response BodyReader
-> IO (Maybe SomeException)
applyCheckStatus checkStatus' res =
case checkStatus' (responseStatus res) (responseHeaders res) (responseCookieJar res) of
Nothing -> return Nothing
Just exc -> do
exc' <-
case fromException exc of
Just (StatusCodeException s hdrs cookie_jar) -> do
lbs <- brReadSome (responseBody res) 1024
return $ toException $ StatusCodeException s (hdrs ++
[("X-Response-Body-Start", toStrict' lbs)]) cookie_jar
_ -> return exc
responseClose res
return (Just exc')
where
#if MIN_VERSION_bytestring(0,10,0)
toStrict' = L.toStrict
#else
toStrict' = S.concat . L.toChunks
#endif
httpRedirect
:: Int
-> (Request -> IO (Response BodyReader, Maybe Request))
-> Request
-> IO (Response BodyReader)
httpRedirect count0 http' req0 = go count0 req0 []
where
go count _ ress | count < 0 = throwIO $ TooManyRedirects ress
go count req' ress = do
(res, mreq) <- http' req'
case mreq of
Just req -> do
let maxFlush = 1024
lbs <- brReadSome (responseBody res) maxFlush
responseClose res
go (count - 1) req (res { responseBody = lbs }:ress)
Nothing -> return res
responseClose :: Response a -> IO ()
responseClose = runResponseClose . responseClose'