{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} -- | This is the main entry point for using http-client. Used by itself, this -- module provides low-level access for streaming request and response bodies, -- and only non-secure HTTP connections. Helper packages such as http-conduit -- provided higher level streaming approaches, while other helper packages like -- http-client-tls provide secure connections. -- -- There are three core components to be understood here: requests, responses, -- and managers. A @Manager@ keeps track of open connections to various hosts, -- and when requested, will provide either an existing open connection or -- create a new connection on demand. A @Manager@ also automatically reaps -- connections which have been unused for a certain period of time. A @Manager@ -- allows for more efficient HTTP usage by allowing for keep-alive connections. -- Secure HTTP connections can be allowed by modifying the settings used for -- creating a manager. The simplest way to create a @Manager@ is with: -- -- @ -- 'newManager' 'defaultManagerSettings' -- @ -- -- or using the 'bracket' pattern with -- -- @ -- 'withManager' 'defaultManagerSettings' -- @ -- -- While generally speaking it is a good idea to share a single @Manager@ -- throughout your application, there are cases where it makes more sense to -- create and destroy @Manager@s more frequently. As an example, if you have an -- application which will make a large number of requests to different hosts, -- and will never make more than one connection to a single host, then sharing -- a @Manager@ will result in idle connections being kept open longer than -- necessary. In such a situation, it makes sense to use @withManager@ around -- each new request, to avoid running out of file descriptors. (Note that the -- 'managerIdleConnectionCount' setting mitigates the risk of leaking too many -- file descriptors.) -- -- The next core component is a @Request@, which represents a single HTTP -- request to be sent to a specific server. @Request@s allow for many settings -- to control exact how they function, but usually the simplest approach for -- creating a @Request@ is to use 'parseUrl'. -- -- Finally, a @Response@ is the result of sending a single @Request@ to a -- server, over a connection which was acquired from a @Manager@. Note that you -- must close the response when you're done with it to ensure that the -- connection is recycled to the @Manager@ to either be used by another -- request, or to be reaped. Usage of @withResponse@ will ensure that this -- happens automatically. -- -- Helper packages may provide replacements for various recommendations listed -- above. For example, if using http-client-tls, instead of using -- 'defaultManagerSettings', you would want to use @tlsManagerSettings@. Be -- sure to read the relevant helper library documentation for more information. -- -- A note on exceptions: for the most part, all actions that perform I/O should -- be assumed to throw an @HttpException@ in the event of some problem, and all -- pure functions will be total. For example, @withResponse@, @httpLbs@, and -- @BodyReader@ can all throw exceptions. Functions like @responseStatus@ and -- @applyBasicAuth@ are guaranteed to be total (or there\'s a bug in the -- library). -- -- One thing to be cautioned about: the type of @parseUrl@ allows it to work in -- different monads. If used in the @IO@ monad, it will throw an exception in -- the case of an invalid URI. In addition, if you leverage the @IsString@ -- instance of the @Request@ value via @OverloadedStrings@, an invalid URI will -- result in a partial value. Caveat emptor! -- -- Non-2xx responses: the default behavior of all functions in http-client is -- to automatically perform up to 10 redirects (response codes 301, 302, 303, -- and 307), and to throw a 'StatusCodeException' on all responses whose status -- are not in the 2xx range. These behaviors can be overridden by the -- 'redirectCount' and 'checkStatus' settings on a request, respectively. module Network.HTTP.Client ( -- * Performing requests withResponse , httpLbs , httpNoBody , responseOpen , responseClose -- ** Tracking redirect history , withResponseHistory , responseOpenHistory , HistoriedResponse , hrRedirects , hrFinalRequest , hrFinalResponse -- * Connection manager , Manager , newManager , closeManager , withManager -- ** Connection manager settings , ManagerSettings , defaultManagerSettings , managerConnCount , managerRawConnection , managerTlsConnection , managerResponseTimeout , managerRetryableException , managerWrapIOException , managerIdleConnectionCount -- *** Helpers , rawConnectionModifySocket -- * Request , parseUrl , applyBasicAuth , urlEncodedBody , getUri , setQueryString -- ** Request type and fields , Request , method , secure , host , port , path , queryString , requestHeaders , requestBody , proxy , applyBasicProxyAuth , decompress , redirectCount , checkStatus , responseTimeout , cookieJar , requestVersion -- ** Request body , RequestBody (..) , Popper , NeedsPopper , GivesPopper -- * Response , Response , responseStatus , responseVersion , responseHeaders , responseBody , responseCookieJar -- ** Response body , BodyReader , brRead , brConsume -- * Misc , HttpException (..) , Cookie (..) , CookieJar , Proxy (..) -- * Cookies , module Network.HTTP.Client.Cookies ) where import Network.HTTP.Client.Body import Network.HTTP.Client.Cookies import Network.HTTP.Client.Core import Network.HTTP.Client.Manager import Network.HTTP.Client.Request import Network.HTTP.Client.Response import Network.HTTP.Client.Types import Data.IORef (newIORef, writeIORef, readIORef, modifyIORef) import qualified Data.ByteString.Lazy as L import Data.Foldable (Foldable) import Data.Traversable (Traversable) import Network.HTTP.Types (statusCode) import GHC.Generics (Generic) import Data.Typeable (Typeable) import Control.Exception (bracket) -- | A datatype holding information on redirected requests and the final response. -- -- Since 0.4.1 data HistoriedResponse body = HistoriedResponse { hrRedirects :: [(Request, Response L.ByteString)] -- ^ Requests which resulted in a redirect, together with their responses. -- The response contains the first 1024 bytes of the body. -- -- Since 0.4.1 , hrFinalRequest :: Request -- ^ The final request performed. -- -- Since 0.4.1 , hrFinalResponse :: Response body -- ^ The response from the final request. -- -- Since 0.4.1 } deriving (Functor, Traversable, Foldable, Show, Typeable, Generic) -- | A variant of @responseOpen@ which keeps a history of all redirects -- performed in the interim, together with the first 1024 bytes of their -- response bodies. -- -- Since 0.4.1 responseOpenHistory :: Request -> Manager -> IO (HistoriedResponse BodyReader) responseOpenHistory req0 man = do reqRef <- newIORef req0 historyRef <- newIORef id let go req = do res <- httpRaw req man case getRedirectedRequest req (responseHeaders res) (responseCookieJar res) (statusCode $ responseStatus res) of Nothing -> return (res, Nothing) Just req' -> do writeIORef reqRef req' body <- brReadSome (responseBody res) 1024 modifyIORef historyRef (. ((req, res { responseBody = body }):)) return (res, Just req') res <- httpRedirect (redirectCount req0) go req0 reqFinal <- readIORef reqRef history <- readIORef historyRef return HistoriedResponse { hrRedirects = history [] , hrFinalRequest = reqFinal , hrFinalResponse = res } -- | A variant of @withResponse@ which keeps a history of all redirects -- performed in the interim, together with the first 1024 bytes of their -- response bodies. -- -- Since 0.4.1 withResponseHistory :: Request -> Manager -> (HistoriedResponse BodyReader -> IO a) -> IO a withResponseHistory req man = bracket (responseOpenHistory req man) (responseClose . hrFinalResponse)