{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module Network.HTTP.Client.Core ( withResponse , httpLbs , httpNoBody , httpRaw , httpRaw' , getModifiedRequestManager , responseOpen , responseClose , httpRedirect , 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.Maybe (fromMaybe, isJust) import Data.Time import Control.Exception import qualified Data.ByteString.Lazy as L import Data.Monoid import Control.Monad (void) import System.Timeout (timeout) import Data.KeyedPool -- | Perform a @Request@ using a connection acquired from the given @Manager@, -- and then provide the @Response@ to the given function. This function is -- fully exception safe, guaranteeing that the response will be closed when the -- inner function exits. It is defined as: -- -- > withResponse req man f = bracket (responseOpen req man) responseClose f -- -- It is recommended that you use this function in place of explicit calls to -- 'responseOpen' and 'responseClose'. -- -- You will need to use functions such as 'brRead' to consume the response -- body. -- -- Since 0.1.0 withResponse :: Request -> Manager -> (Response BodyReader -> IO a) -> IO a withResponse req man f = bracket (responseOpen req man) responseClose f -- | A convenience wrapper around 'withResponse' which reads in the entire -- response body and immediately closes the connection. Note that this function -- performs fully strict I\/O, and only uses a lazy ByteString in its response -- for memory efficiency. If you are anticipating a large response body, you -- are encouraged to use 'withResponse' and 'brRead' instead. -- -- Since 0.1.0 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 } -- | A convenient wrapper around 'withResponse' which ignores the response -- body. This is useful, for example, when performing a HEAD request. -- -- Since 0.3.2 httpNoBody :: Request -> Manager -> IO (Response ()) httpNoBody req man = withResponse req man $ return . void -- | Get a 'Response' without any redirect following. httpRaw :: Request -> Manager -> IO (Response BodyReader) httpRaw = fmap (fmap snd) . httpRaw' -- | Get a 'Response' without any redirect following. -- -- This extended version of 'httpRaw' also returns the potentially modified Request. httpRaw' :: Request -> Manager -> IO (Request, Response BodyReader) httpRaw' req0 m = do let req' = mSetProxy m req0 (req, cookie_jar') <- case cookieJar req' of Just cj -> do now <- getCurrentTime return $ insertCookiesIntoRequest req' (evictExpiredCookies cj now) now Nothing -> return (req', Data.Monoid.mempty) (timeout', mconn) <- getConnectionWrapper (responseTimeout' req) (getConn req m) -- Originally, we would only test for exceptions when sending the request, -- not on calling @getResponse@. However, some servers seem to close -- connections after accepting the request headers, so we need to check for -- exceptions in both. ex <- try $ do cont <- requestBuilder (dropProxyAuthSecure req) (managedResource mconn) getResponse timeout' req mconn cont case ex of -- Connection was reused, and might have been closed. Try again Left e | managedReused mconn && mRetryableException m e -> do managedRelease mconn DontReuse httpRaw' req m -- Not reused, or a non-retry, so this is a real exception Left e -> throwIO e -- Everything went ok, so the connection is good. If any exceptions get -- thrown in the response body, just throw them as normal. Right res -> case cookieJar req' of Just _ -> do now' <- getCurrentTime let (cookie_jar, _) = updateCookieJar res req now' cookie_jar' return (req, res {responseCookieJar = cookie_jar}) Nothing -> return (req, res) where getConnectionWrapper mtimeout f = case mtimeout of Nothing -> fmap ((,) Nothing) f Just timeout' -> do before <- getCurrentTime mres <- timeout timeout' f case mres of Nothing -> throwHttp ConnectionTimeout Just res -> do now <- getCurrentTime let timeSpentMicro = diffUTCTime now before * 1000000 remainingTime = round $ fromIntegral timeout' - timeSpentMicro if remainingTime <= 0 then throwHttp ConnectionTimeout else return (Just remainingTime, res) responseTimeout' req = case responseTimeout req of ResponseTimeoutDefault -> case mResponseTimeout m of ResponseTimeoutDefault -> Just 30000000 ResponseTimeoutNone -> Nothing ResponseTimeoutMicro u -> Just u ResponseTimeoutNone -> Nothing ResponseTimeoutMicro u -> Just u -- | The used Manager can be overridden (by requestManagerOverride) and the used -- Request can be modified (through managerModifyRequest). This function allows -- to retrieve the possibly overridden Manager and the possibly modified -- Request. -- -- (In case the Manager is overridden by requestManagerOverride, the Request is -- being modified by managerModifyRequest of the new Manager, not the old one.) getModifiedRequestManager :: Manager -> Request -> IO (Manager, Request) getModifiedRequestManager manager0 req0 = do let manager = fromMaybe manager0 (requestManagerOverride req0) req <- mModifyRequest manager req0 return (manager, req) -- | The most low-level function for initiating an HTTP request. -- -- The first argument to this function gives a full specification -- on the request: the host to connect to, whether to use SSL, -- headers, etc. Please see 'Request' for full details. The -- second argument specifies which 'Manager' should be used. -- -- This function then returns a 'Response' with a -- 'BodyReader'. The 'Response' contains the status code -- and headers that were sent back to us, and the -- 'BodyReader' contains the body of the request. Note -- that this 'BodyReader' allows you to have fully -- interleaved IO actions during your HTTP download, making it -- possible to download very large responses in constant memory. -- -- An important note: the response body returned by this function represents a -- live HTTP connection. As such, if you do not use the response body, an open -- socket will be retained indefinitely. You must be certain to call -- 'responseClose' on this response to free up resources. -- -- This function automatically performs any necessary redirects, as specified -- by the 'redirectCount' setting. -- -- When implementing a (reverse) proxy using this function or relating -- functions, it's wise to remove Transfer-Encoding:, Content-Length:, -- Content-Encoding: and Accept-Encoding: from request and response -- headers to be relayed. -- -- Since 0.1.0 responseOpen :: Request -> Manager -> IO (Response BodyReader) responseOpen inputReq manager' = do (manager, req0) <- getModifiedRequestManager manager' inputReq wrapExc req0 $ mWrapException manager req0 $ do (req, res) <- go manager (redirectCount req0) req0 checkResponse req req res mModifyResponse manager res { responseBody = wrapExc req0 (responseBody res) } where wrapExc :: Request -> IO a -> IO a wrapExc req0 = handle $ throwIO . toHttpException req0 go manager0 count req' = httpRedirect' count (\req -> do (manager, modReq) <- getModifiedRequestManager manager0 req (req'', res) <- httpRaw' modReq manager let mreq = if redirectCount modReq == 0 then Nothing else getRedirectedRequest req'' (responseHeaders res) (responseCookieJar res) (statusCode (responseStatus res)) return (res, fromMaybe req'' mreq, isJust mreq)) req' -- | Redirect loop. httpRedirect :: Int -- ^ 'redirectCount' -> (Request -> IO (Response BodyReader, Maybe Request)) -- ^ function which performs a request and returns a response, and possibly another request if there's a redirect. -> Request -> IO (Response BodyReader) httpRedirect count0 http0 req0 = fmap snd $ httpRedirect' count0 http' req0 where -- adapt callback API http' req' = do (res, mbReq) <- http0 req' return (res, fromMaybe req0 mbReq, isJust mbReq) -- | Redirect loop. -- -- This extended version of 'httpRaw' also returns the Request potentially modified by @managerModifyRequest@. httpRedirect' :: Int -- ^ 'redirectCount' -> (Request -> IO (Response BodyReader, Request, Bool)) -- ^ function which performs a request and returns a response, the potentially modified request, and a Bool indicating if there was a redirect. -> Request -> IO (Request, Response BodyReader) httpRedirect' count0 http' req0 = go count0 req0 [] where go count _ ress | count < 0 = throwHttp $ TooManyRedirects ress go count req' ress = do (res, req, isRedirect) <- http' req' if isRedirect then do -- Allow the original connection to return to the -- connection pool immediately by flushing the body. -- If the response body is too large, don't flush, but -- instead just close the connection. let maxFlush = 1024 lbs <- brReadSome (responseBody res) maxFlush -- The connection may already be closed, e.g. -- when using withResponseHistory. See -- https://github.com/snoyberg/http-client/issues/169 `Control.Exception.catch` \se -> case () of () | Just ConnectionClosed <- fmap unHttpExceptionContentWrapper (fromException se) -> return L.empty | Just (HttpExceptionRequest _ ConnectionClosed) <- fromException se -> return L.empty _ -> throwIO se responseClose res -- And now perform the actual redirect go (count - 1) req (res { responseBody = lbs }:ress) else return (req, res) -- | Close any open resources associated with the given @Response@. In general, -- this will either close an active @Connection@ or return it to the @Manager@ -- to be reused. -- -- Since 0.1.0 responseClose :: Response a -> IO () responseClose = runResponseClose . responseClose'