module Network.HTTP.Client.Manager
( ManagerSettings (..)
, newManager
, closeManager
, withManager
, getConn
, defaultManagerSettings
, rawConnectionModifySocket
, rawConnectionModifySocketSize
, proxyFromRequest
, noProxy
, useProxy
, proxyEnvironment
, proxyEnvironmentNamed
, defaultProxy
, dropProxyAuthSecure
) where
import qualified Data.ByteString.Char8 as S8
import Data.Text (Text)
import Control.Monad (unless)
import Control.Exception (throwIO, fromException, IOException, Exception (..), handle)
import qualified Network.Socket as NS
import Network.HTTP.Types (status200)
import Network.HTTP.Client.Types
import Network.HTTP.Client.Connection
import Network.HTTP.Client.Headers (parseStatusHeaders)
import Network.HTTP.Proxy
import Data.KeyedPool
import Data.Maybe (isJust)
rawConnectionModifySocket :: (NS.Socket -> IO ())
-> IO (Maybe NS.HostAddress -> String -> Int -> IO Connection)
rawConnectionModifySocket = return . openSocketConnection
rawConnectionModifySocketSize :: (NS.Socket -> IO ())
-> IO (Int -> Maybe NS.HostAddress -> String -> Int -> IO Connection)
rawConnectionModifySocketSize = return . openSocketConnectionSize
defaultManagerSettings :: ManagerSettings
defaultManagerSettings = ManagerSettings
{ managerConnCount = 10
, managerRawConnection = return $ openSocketConnection (const $ return ())
, managerTlsConnection = return $ \_ _ _ -> throwHttp TlsNotSupported
, managerTlsProxyConnection = return $ \_ _ _ _ _ _ -> throwHttp TlsNotSupported
, managerResponseTimeout = ResponseTimeoutDefault
, managerRetryableException = \e ->
case fromException e of
Just (_ :: IOException) -> True
_ ->
case fmap unHttpExceptionContentWrapper $ fromException e of
Just NoResponseDataReceived -> True
Just IncompleteHeaders -> True
_ -> False
, managerWrapException = \_req ->
let wrapper se =
case fromException se of
Just (_ :: IOException) -> throwHttp $ InternalException se
Nothing -> throwIO se
in handle wrapper
, managerIdleConnectionCount = 512
, managerModifyRequest = return
, managerModifyResponse = return
, managerProxyInsecure = defaultProxy
, managerProxySecure = defaultProxy
}
newManager :: ManagerSettings -> IO Manager
newManager ms = do
NS.withSocketsDo $ return ()
httpProxy <- runProxyOverride (managerProxyInsecure ms) False
httpsProxy <- runProxyOverride (managerProxySecure ms) True
createConnection <- mkCreateConnection ms
keyedPool <- createKeyedPool
createConnection
connectionClose
(managerConnCount ms)
(managerIdleConnectionCount ms)
(const (return ()))
let manager = Manager
{ mConns = keyedPool
, mResponseTimeout = managerResponseTimeout ms
, mRetryableException = managerRetryableException ms
, mWrapException = managerWrapException ms
, mModifyRequest = managerModifyRequest ms
, mModifyResponse = managerModifyResponse ms
, mSetProxy = \req ->
if secure req
then httpsProxy req
else httpProxy req
}
return manager
closeManager :: Manager -> IO ()
closeManager _ = return ()
withManager :: ManagerSettings -> (Manager -> IO a) -> IO a
withManager settings f = newManager settings >>= f
dropProxyAuthSecure :: Request -> Request
dropProxyAuthSecure req
| secure req && useProxy' = req
{ requestHeaders = filter (\(k, _) -> k /= "Proxy-Authorization")
(requestHeaders req)
}
| otherwise = req
where
useProxy' = isJust (proxy req)
getConn :: Request
-> Manager
-> IO (Managed Connection)
getConn req m
| S8.null h = throwHttp $ InvalidDestinationHost h
| otherwise = takeKeyedPool (mConns m) connkey
where
h = host req
connkey = connKey req
connKey :: Request -> ConnKey
connKey req =
case proxy req of
Nothing
| secure req -> simple CKSecure
| otherwise -> simple CKRaw
Just p
| secure req -> CKProxy
(proxyHost p)
(proxyPort p)
(lookup "Proxy-Authorization" (requestHeaders req))
(host req)
(port req)
| otherwise -> CKRaw Nothing (proxyHost p) (proxyPort p)
where
simple con = con (hostAddress req) (host req) (port req)
mkCreateConnection :: ManagerSettings -> IO (ConnKey -> IO Connection)
mkCreateConnection ms = do
rawConnection <- managerRawConnection ms
tlsConnection <- managerTlsConnection ms
tlsProxyConnection <- managerTlsProxyConnection ms
return $ \ck -> wrapConnectExc $ case ck of
CKRaw connaddr connhost connport ->
rawConnection connaddr (S8.unpack connhost) connport
CKSecure connaddr connhost connport ->
tlsConnection connaddr (S8.unpack connhost) connport
CKProxy connhost connport mProxyAuthHeader ultHost ultPort ->
let proxyAuthorizationHeader = maybe
""
(\h' -> S8.concat ["Proxy-Authorization: ", h', "\r\n"])
mProxyAuthHeader
hostHeader = S8.concat ["Host: ", ultHost, ":", (S8.pack $ show ultPort), "\r\n"]
connstr = S8.concat
[ "CONNECT "
, ultHost
, ":"
, S8.pack $ show ultPort
, " HTTP/1.1\r\n"
, proxyAuthorizationHeader
, hostHeader
, "\r\n"
]
parse conn = do
StatusHeaders status _ _ <- parseStatusHeaders conn Nothing Nothing
unless (status == status200) $
throwHttp $ ProxyConnectException ultHost ultPort status
in tlsProxyConnection
connstr
parse
(S8.unpack ultHost)
Nothing
(S8.unpack connhost)
connport
where
wrapConnectExc = handle $ \e ->
throwHttp $ ConnectionFailure (toException (e :: IOException))
proxyFromRequest :: ProxyOverride
proxyFromRequest = ProxyOverride $ const $ return id
noProxy :: ProxyOverride
noProxy = ProxyOverride $ const $ return $ \req -> req { proxy = Nothing }
useProxy :: Proxy -> ProxyOverride
useProxy p = ProxyOverride $ const $ return $ \req -> req { proxy = Just p }
proxyEnvironment :: Maybe Proxy
-> ProxyOverride
proxyEnvironment mp = ProxyOverride $ \secure' ->
systemProxyHelper Nothing (httpProtocol secure') $ maybe EHNoProxy EHUseProxy mp
proxyEnvironmentNamed
:: Text
-> Maybe Proxy
-> ProxyOverride
proxyEnvironmentNamed name mp = ProxyOverride $ \secure' ->
systemProxyHelper (Just name) (httpProtocol secure') $ maybe EHNoProxy EHUseProxy mp
defaultProxy :: ProxyOverride
defaultProxy = ProxyOverride $ \secure' ->
systemProxyHelper Nothing (httpProtocol secure') EHFromRequest