diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,54 @@
 # Changelog for http-client
 
+## 0.7.19
+
+* Make mockable via `Network.HTTP.Client.Internal.requestAction` [#554](https://github.com/snoyberg/http-client/pull/554)
+
+## 0.7.18
+
+* Add the `managerSetMaxNumberHeaders` function to the `Client` module to configure `managerMaxNumberHeaders` in `ManagerSettings`.
+
+## 0.7.17
+
+* Add `managerSetMaxHeaderLength` to `Client` to change `ManagerSettings` `MaxHeaderLength`.
+
+## 0.7.16
+
+* Add `responseEarlyHints` field to `Response`, containing a list of all HTTP 103 Early Hints headers received from the server.
+* Add `earlyHintHeadersReceived` callback to `Request`, which will be called on each HTTP 103 Early Hints header section received.
+
+## 0.7.15
+
+* Adds `shouldStripHeaderOnRedirectIfOnDifferentHostOnly` option to `Request` [#520](https://github.com/snoyberg/http-client/pull/520)
+
+## 0.7.14
+
+* Allow customizing max header length [#514](https://github.com/snoyberg/http-client/pull/514)
+
+## 0.7.13
+
+* Create the ability to redact custom header values to censor sensitive information
+
+## 0.7.12
+
+* Fix premature connection closing due to weak reference lifetimes [#490](https://github.com/snoyberg/http-client/pull/490)
+
+## 0.7.11
+
+* Allow making requests to raw IPv6 hosts [#477](https://github.com/snoyberg/http-client/pull/477)
+* Catch "resource vanished" exception on initial response read [#480](https://github.com/snoyberg/http-client/pull/480)
+* Search for reachable IP addresses asynchronously (RFC 6555, 8305) after calling `getAddrInfo` to reduce latency [#472](https://github.com/snoyberg/http-client/pull/472).
+
+## 0.7.10
+
+* Consume trailers and last CRLF of chunked body. The trailers are not exposed,
+  unless the raw body is requested.
+
+## 0.7.9
+
+* Exceptions from streamed request body now cause the request to fail. Previously they were
+  routed through onRequestBodyException and, by default, the IOExceptions were discarded.
+
 ## 0.7.8
 
 * Include the original `Request` in the `Response`. Expose it via `getOriginalRequest`.
diff --git a/Network/HTTP/Client.hs b/Network/HTTP/Client.hs
--- a/Network/HTTP/Client.hs
+++ b/Network/HTTP/Client.hs
@@ -11,7 +11,7 @@
 -- support for things like JSON request and response bodies. For most users,
 -- this will be an easier place to start. You can read the tutorial at:
 --
--- https://haskell-lang.org/library/http-client
+-- https://github.com/snoyberg/http-client/blob/master/TUTORIAL.md
 --
 -- = Lower-level API
 --
@@ -112,6 +112,8 @@
     , managerSetProxy
     , managerSetInsecureProxy
     , managerSetSecureProxy
+    , managerSetMaxHeaderLength
+    , managerSetMaxNumberHeaders
     , ProxyOverride
     , proxyFromRequest
     , noProxy
@@ -162,10 +164,13 @@
     , decompress
     , redirectCount
     , shouldStripHeaderOnRedirect
+    , shouldStripHeaderOnRedirectIfOnDifferentHostOnly
     , checkResponse
     , responseTimeout
     , cookieJar
     , requestVersion
+    , redactHeaders
+    , earlyHintHeadersReceived
       -- ** Request body
     , RequestBody (..)
     , Popper
@@ -182,6 +187,7 @@
     , responseBody
     , responseCookieJar
     , getOriginalRequest
+    , responseEarlyHints
     , throwErrorStatusCodes
       -- ** Response body
     , BodyReader
@@ -203,12 +209,13 @@
     , equivCookieJar
     , Proxy (..)
     , withConnection
+    , strippedHostName
       -- * Cookies
     , module Network.HTTP.Client.Cookies
     ) where
 
 import Network.HTTP.Client.Body
-import Network.HTTP.Client.Connection (makeConnection, socketConnection)
+import Network.HTTP.Client.Connection (makeConnection, socketConnection, strippedHostName)
 import Network.HTTP.Client.Cookies
 import Network.HTTP.Client.Core
 import Network.HTTP.Client.Manager
@@ -223,7 +230,7 @@
 import Network.HTTP.Types (statusCode)
 import GHC.Generics (Generic)
 import Data.Typeable (Typeable)
-import Control.Exception (bracket, handle, throwIO)
+import Control.Exception (bracket, catch, handle, throwIO)
 
 -- | A datatype holding information on redirected requests and the final response.
 --
@@ -262,6 +269,7 @@
                                             (responseBody res')
                     }
             case getRedirectedRequest
+                    req
                     req'
                     (responseHeaders res)
                     (responseCookieJar res)
@@ -270,6 +278,7 @@
                 Just req'' -> do
                     writeIORef reqRef req''
                     body <- brReadSome (responseBody res) 1024
+                        `catch` handleClosedRead
                     modifyIORef historyRef (. ((req, res { responseBody = body }):))
                     return (res, req'', True)
     (_, res) <- httpRedirect' (redirectCount reqOrig) go reqOrig
@@ -313,6 +322,16 @@
 managerSetProxy :: ProxyOverride -> ManagerSettings -> ManagerSettings
 managerSetProxy po = managerSetInsecureProxy po . managerSetSecureProxy po
 
+-- @since 0.7.17
+managerSetMaxHeaderLength :: Int -> ManagerSettings -> ManagerSettings
+managerSetMaxHeaderLength l manager = manager
+    { managerMaxHeaderLength = Just $ MaxHeaderLength l }
+
+-- @since 0.7.18
+managerSetMaxNumberHeaders :: Int -> ManagerSettings -> ManagerSettings
+managerSetMaxNumberHeaders n manager = manager
+    { managerMaxNumberHeaders = Just $ MaxNumberHeaders n }
+
 -- $example1
 -- = Example Usage
 --
@@ -347,9 +366,9 @@
 -- >   -- Create the request
 -- >   let requestObject = object ["name" .= "Michael", "age" .= 30]
 -- >   let requestObject = object
--- >    [ "name" .= ("Michael" :: Text)
--- >    , "age"  .= (30 :: Int)
--- >    ]
+-- >        [ "name" .= ("Michael" :: Text)
+-- >        , "age"  .= (30 :: Int)
+-- >        ]
 -- >
 -- >   initialRequest <- parseRequest "http://httpbin.org/post"
 -- >   let request = initialRequest { method = "POST", requestBody = RequestBodyLBS $ encode requestObject }
diff --git a/Network/HTTP/Client/Body.hs b/Network/HTTP/Client/Body.hs
--- a/Network/HTTP/Client/Body.hs
+++ b/Network/HTTP/Client/Body.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE MultiWayIf #-}
 module Network.HTTP.Client.Body
     ( makeChunkedReader
     , makeLengthReader
@@ -147,11 +148,12 @@
                         return bs
 
 makeChunkedReader
-  :: IO () -- ^ cleanup
+  :: Maybe MaxHeaderLength
+  -> IO () -- ^ cleanup
   -> Bool -- ^ raw
   -> Connection
   -> IO BodyReader
-makeChunkedReader cleanup raw conn@Connection {..} = do
+makeChunkedReader mhl cleanup raw conn@Connection {..} = do
     icount <- newIORef 0
     return $ do
       bs <- go icount
@@ -166,8 +168,11 @@
                 else return (empty, count0)
         if count <= 0
             then do
+                -- count == -1 indicates that all chunks have been consumed
                 writeIORef icount (-1)
-                return $ if count /= (-1) && raw then rawCount else empty
+                if | count /= -1 && raw -> S.append rawCount <$> readTrailersRaw
+                   | count /= -1        -> consumeTrailers *> pure empty
+                   | otherwise          -> pure empty
             else do
                 (bs, count') <- readChunk count
                 writeIORef icount count'
@@ -197,11 +202,11 @@
           | otherwise = return (x, 0)
 
     requireNewline = do
-        bs <- connectionReadLine conn
+        bs <- connectionReadLine mhl conn
         unless (S.null bs) $ throwHttp InvalidChunkHeaders
 
     readHeader = do
-        bs <- connectionReadLine conn
+        bs <- connectionReadLine mhl conn
         case parseHex bs of
             Nothing -> throwHttp InvalidChunkHeaders
             Just hex -> return (bs `S.append` "\r\n", hex)
@@ -222,3 +227,11 @@
         | 65 <= w && w <= 70  = Just $ fromIntegral w - 55
         | 97 <= w && w <= 102 = Just $ fromIntegral w - 87
         | otherwise = Nothing
+
+    readTrailersRaw = do
+        bs <- connectionReadLine mhl conn
+        if S.null bs
+        then pure "\r\n"
+        else (bs `S.append` "\r\n" `S.append`) <$> readTrailersRaw
+
+    consumeTrailers = connectionDropTillBlankLine mhl conn
diff --git a/Network/HTTP/Client/Connection.hs b/Network/HTTP/Client/Connection.hs
--- a/Network/HTTP/Client/Connection.hs
+++ b/Network/HTTP/Client/Connection.hs
@@ -5,47 +5,55 @@
     ( connectionReadLine
     , connectionReadLineWith
     , connectionDropTillBlankLine
+    , connectionUnreadLine
     , dummyConnection
     , openSocketConnection
     , openSocketConnectionSize
     , makeConnection
     , socketConnection
     , withSocket
+    , strippedHostName
     ) where
 
 import Data.ByteString (ByteString, empty)
 import Data.IORef
 import Control.Monad
+import Control.Concurrent
+import Control.Concurrent.Async
 import Network.HTTP.Client.Types
 import Network.Socket (Socket, HostAddress)
 import qualified Network.Socket as NS
 import Network.Socket.ByteString (sendAll, recv)
 import qualified Control.Exception as E
 import qualified Data.ByteString as S
-import Data.Word (Word8)
+import Data.Foldable (for_)
 import Data.Function (fix)
+import Data.Maybe (listToMaybe)
+import Data.Word (Word8)
 
-connectionReadLine :: Connection -> IO ByteString
-connectionReadLine conn = do
+connectionReadLine :: Maybe MaxHeaderLength -> Connection -> IO ByteString
+connectionReadLine mhl conn = do
     bs <- connectionRead conn
     when (S.null bs) $ throwHttp IncompleteHeaders
-    connectionReadLineWith conn bs
+    connectionReadLineWith mhl conn bs
 
 -- | Keep dropping input until a blank line is found.
-connectionDropTillBlankLine :: Connection -> IO ()
-connectionDropTillBlankLine conn = fix $ \loop -> do
-    bs <- connectionReadLine conn
+connectionDropTillBlankLine :: Maybe MaxHeaderLength -> Connection -> IO ()
+connectionDropTillBlankLine mhl conn = fix $ \loop -> do
+    bs <- connectionReadLine mhl conn
     unless (S.null bs) loop
 
-connectionReadLineWith :: Connection -> ByteString -> IO ByteString
-connectionReadLineWith conn bs0 =
+connectionReadLineWith :: Maybe MaxHeaderLength -> Connection -> ByteString -> IO ByteString
+connectionReadLineWith mhl conn bs0 =
     go bs0 id 0
   where
     go bs front total =
         case S.break (== charLF) bs of
             (_, "") -> do
                 let total' = total + S.length bs
-                when (total' > 4096) $ throwHttp OverlongHeaders
+                case fmap unMaxHeaderLength mhl of
+                    Nothing -> pure ()
+                    Just n -> when (total' > n) $ throwHttp OverlongHeaders
                 bs' <- connectionRead conn
                 when (S.null bs') $ throwHttp IncompleteHeaders
                 go bs' (front . (bs:)) total'
@@ -53,6 +61,11 @@
                 unless (S.null y) $! connectionUnread conn y
                 return $! killCR $! S.concat $! front [x]
 
+connectionUnreadLine :: Connection -> ByteString -> IO ()
+connectionUnreadLine conn line = do
+  connectionUnread conn (S.pack [charCR, charLF])
+  connectionUnread conn line
+
 charLF, charCR :: Word8
 charLF = 10
 charCR = 13
@@ -149,6 +162,24 @@
     withSocket tweakSocket hostAddress' host' port' $ \ sock ->
         socketConnection sock chunksize
 
+-- | strippedHostName takes a URI host name, as extracted
+-- by 'Network.URI.regName', and strips square brackets
+-- around IPv6 addresses.
+--
+-- The result is suitable for passing to services such as
+-- name resolution ('Network.Socket.getAddr').
+--
+-- @since
+strippedHostName :: String -> String
+strippedHostName hostName =
+    case hostName of
+        '[':'v':_ -> hostName -- IPvFuture, no obvious way to deal with this
+        '[':rest ->
+            case break (== ']') rest of
+                (ipv6, "]") -> ipv6
+                _ -> hostName -- invalid host name
+        _ -> hostName
+
 withSocket :: (Socket -> IO ())
            -> Maybe HostAddress
            -> String -- ^ host
@@ -159,7 +190,7 @@
     let hints = NS.defaultHints { NS.addrSocketType = NS.Stream }
     addrs <- case hostAddress' of
         Nothing ->
-            NS.getAddrInfo (Just hints) (Just host') (Just $ show port')
+            NS.getAddrInfo (Just hints) (Just $ strippedHostName host') (Just $ show port')
         Just ha ->
             return
                 [NS.AddrInfo
@@ -184,10 +215,30 @@
             NS.connect sock (NS.addrAddress addr)
             return sock)
 
+-- Pick up an IP using an approximation of the happy-eyeballs algorithm:
+-- https://datatracker.ietf.org/doc/html/rfc8305
+--
 firstSuccessful :: [NS.AddrInfo] -> (NS.AddrInfo -> IO a) -> IO a
-firstSuccessful []     _  = error "getAddrInfo returned empty list"
-firstSuccessful (a:as) cb =
-    cb a `E.catch` \(e :: E.IOException) ->
-        case as of
-            [] -> E.throwIO e
-            _  -> firstSuccessful as cb
+firstSuccessful []        _  = error "getAddrInfo returned empty list"
+firstSuccessful addresses cb = do
+    result <- newEmptyMVar
+    either E.throwIO pure =<<
+        withAsync (tryAddresses result)
+            (\_ -> takeMVar result)
+  where
+    -- https://datatracker.ietf.org/doc/html/rfc8305#section-5
+    connectionAttemptDelay = 250 * 1000
+
+    tryAddresses result = do
+        z <- forConcurrently (zip addresses [0..]) $ \(addr, n) -> do
+            when (n > 0) $ threadDelay $ n * connectionAttemptDelay
+            tryAddress addr
+
+        case listToMaybe (reverse z) of
+            Just e@(Left _) -> tryPutMVar result e
+            _               -> error $ "tryAddresses invariant violated: " ++ show addresses
+      where
+        tryAddress addr = do
+            r :: Either E.IOException a <- E.try $! cb addr
+            for_ r $ \_ -> tryPutMVar result r
+            pure r
diff --git a/Network/HTTP/Client/Core.hs b/Network/HTTP/Client/Core.hs
--- a/Network/HTTP/Client/Core.hs
+++ b/Network/HTTP/Client/Core.hs
@@ -6,12 +6,14 @@
     , httpNoBody
     , httpRaw
     , httpRaw'
+    , requestAction
     , getModifiedRequestManager
     , responseOpen
     , responseClose
     , httpRedirect
     , httpRedirect'
     , withConnection
+    , handleClosedRead
     ) where
 
 import Network.HTTP.Types
@@ -24,12 +26,15 @@
 import Network.HTTP.Client.Cookies
 import Data.Maybe (fromMaybe, isJust)
 import Data.Time
+import Data.IORef
 import Control.Exception
 import qualified Data.ByteString.Lazy as L
 import Data.Monoid
 import Control.Monad (void)
 import System.Timeout (timeout)
+import System.IO.Unsafe (unsafePerformIO)
 import Data.KeyedPool
+import GHC.IO.Exception (IOException(..), IOErrorType(..))
 
 -- | Perform a @Request@ using a connection acquired from the given @Manager@,
 -- and then provide the @Response@ to the given function. This function is
@@ -92,67 +97,87 @@
             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)
+    res <- makeRequest req m
+    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)
 
-        getResponse timeout' req mconn cont
+makeRequest
+    :: Request
+    -> Manager
+    -> IO (Response BodyReader)
+makeRequest req m = do
+    action <- readIORef requestAction
+    action req m
 
-    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 -> do
-          -- Explicitly release connection for all real exceptions:
-          -- https://github.com/snoyberg/http-client/pull/454
-          managedRelease mconn DontReuse
-          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)
+requestAction :: IORef (Request -> Manager -> IO (Response BodyReader))
+{-# NOINLINE requestAction #-}
+requestAction = unsafePerformIO (newIORef action)
   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 mConn -> do
-                         now <- getCurrentTime
-                         let timeSpentMicro = diffUTCTime now before * 1000000
-                             remainingTime = round $ fromIntegral timeout' - timeSpentMicro
-                         if remainingTime <= 0
-                             then do
-                                 managedRelease mConn DontReuse
-                                 throwHttp ConnectionTimeout
-                             else return (Just remainingTime, mConn)
+    action
+        :: Request
+        -> Manager
+        -> IO (Response BodyReader)
+    action req m = do
+        (timeout', mconn) <- getConnectionWrapper
+            (responseTimeout' req)
+            (getConn req m)
 
-    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
+        -- 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 (mMaxHeaderLength m) (mMaxNumberHeaders m) 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
+                action req m
+            -- Not reused, or a non-retry, so this is a real exception
+            Left e -> do
+              -- Explicitly release connection for all real exceptions:
+              -- https://github.com/snoyberg/http-client/pull/454
+              managedRelease mconn DontReuse
+              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 -> return 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 mConn -> do
+                            now <- getCurrentTime
+                            let timeSpentMicro = diffUTCTime now before * 1000000
+                                remainingTime = round $ fromIntegral timeout' - timeSpentMicro
+                            if remainingTime <= 0
+                                then do
+                                    managedRelease mConn DontReuse
+                                    throwHttp ConnectionTimeout
+                                else return (Just remainingTime, mConn)
+
+        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
@@ -218,7 +243,7 @@
         (req'', res) <- httpRaw' modReq manager
         let mreq = if redirectCount modReq == 0
               then Nothing
-              else getRedirectedRequest req'' (responseHeaders res) (responseCookieJar res) (statusCode (responseStatus res))
+              else getRedirectedRequest req' req'' (responseHeaders res) (responseCookieJar res) (statusCode (responseStatus res))
         return (res, fromMaybe req'' mreq, isJust mreq))
       req'
 
@@ -235,6 +260,17 @@
         (res, mbReq) <- http0 req'
         return (res, fromMaybe req0 mbReq, isJust mbReq)
 
+handleClosedRead :: SomeException -> IO L.ByteString
+handleClosedRead se
+    | Just ConnectionClosed <- fmap unHttpExceptionContentWrapper (fromException se)
+        = return L.empty
+    | Just (HttpExceptionRequest _ ConnectionClosed) <- fromException se
+        = return L.empty
+    | Just (IOError _ ResourceVanished _ _ _ _) <- fromException se
+        = return L.empty
+    | otherwise
+        = throwIO se
+
 -- | Redirect loop.
 --
 -- This extended version of 'httpRaw' also returns the Request potentially modified by @managerModifyRequest@.
@@ -258,15 +294,7 @@
                 -- 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
+                `Control.Exception.catch` handleClosedRead
             responseClose res
 
             -- And now perform the actual redirect
diff --git a/Network/HTTP/Client/Headers.hs b/Network/HTTP/Client/Headers.hs
--- a/Network/HTTP/Client/Headers.hs
+++ b/Network/HTTP/Client/Headers.hs
@@ -1,4 +1,6 @@
 {-# LANGUAGE DisambiguateRecordFields #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ViewPatterns #-}
 module Network.HTTP.Client.Headers
@@ -14,11 +16,11 @@
 import qualified Data.CaseInsensitive           as CI
 import           Data.Maybe (mapMaybe)
 import           Data.Monoid
+import           Data.Word (Word8)
 import           Network.HTTP.Client.Connection
 import           Network.HTTP.Client.Types
-import           System.Timeout                 (timeout)
 import           Network.HTTP.Types
-import Data.Word (Word8)
+import           System.Timeout                 (timeout)
 
 charSpace, charColon, charPeriod :: Word8
 charSpace = 32
@@ -26,8 +28,8 @@
 charPeriod = 46
 
 
-parseStatusHeaders :: Connection -> Maybe Int -> Maybe (IO ()) -> IO StatusHeaders
-parseStatusHeaders conn timeout' cont
+parseStatusHeaders :: Maybe MaxHeaderLength -> Maybe MaxNumberHeaders -> Connection -> Maybe Int -> ([Header] -> IO ()) -> Maybe (IO ()) -> IO StatusHeaders
+parseStatusHeaders mhl mnh conn timeout' onEarlyHintHeaders cont
     | Just k <- cont = getStatusExpectContinue k
     | otherwise      = getStatus
   where
@@ -45,23 +47,30 @@
             Just  s -> return s
             Nothing -> sendBody >> getStatus
 
+    nextStatusHeaders :: IO (Maybe StatusHeaders)
     nextStatusHeaders = do
-        (s, v) <- nextStatusLine
-        if statusCode s == 100
-            then connectionDropTillBlankLine conn >> return Nothing
-            else Just . StatusHeaders s v A.<$> parseHeaders (0 :: Int) id
+        (s, v) <- nextStatusLine mhl
+        if | statusCode s == 100 -> connectionDropTillBlankLine mhl conn >> return Nothing
+           | statusCode s == 103 -> do
+                 earlyHeaders <- parseEarlyHintHeadersUntilFailure 0 id
+                 onEarlyHintHeaders earlyHeaders
+                 nextStatusHeaders >>= \case
+                     Nothing -> return Nothing
+                     Just (StatusHeaders s' v' earlyHeaders' reqHeaders) ->
+                         return $ Just $ StatusHeaders s' v' (earlyHeaders <> earlyHeaders') reqHeaders
+           | otherwise -> (Just <$>) $ StatusHeaders s v mempty A.<$> parseHeaders 0 id
 
-    nextStatusLine :: IO (Status, HttpVersion)
-    nextStatusLine = do
+    nextStatusLine :: Maybe MaxHeaderLength -> IO (Status, HttpVersion)
+    nextStatusLine mhl = do
         -- Ensure that there is some data coming in. If not, we want to signal
         -- this as a connection problem and not a protocol problem.
         bs <- connectionRead conn
         when (S.null bs) $ throwHttp NoResponseDataReceived
-        connectionReadLineWith conn bs >>= parseStatus 3
+        connectionReadLineWith mhl conn bs >>= parseStatus mhl 3
 
-    parseStatus :: Int -> S.ByteString -> IO (Status, HttpVersion)
-    parseStatus i bs | S.null bs && i > 0 = connectionReadLine conn >>= parseStatus (i - 1)
-    parseStatus _ bs = do
+    parseStatus :: Maybe MaxHeaderLength -> Int -> S.ByteString -> IO (Status, HttpVersion)
+    parseStatus mhl i bs | S.null bs && i > 0 = connectionReadLine mhl conn >>= parseStatus mhl (i - 1)
+    parseStatus _ _ bs = do
         let (ver, bs2) = S.break (== charSpace) bs
             (code, bs3) = S.break (== charSpace) $ S.dropWhile (== charSpace) bs2
             msg = S.dropWhile (== charSpace) bs3
@@ -82,20 +91,39 @@
             Just (i, "") -> Just i
             _ -> Nothing
 
-    parseHeaders 100 _ = throwHttp OverlongHeaders
+    guardMaxNumberHeaders :: Int -> IO ()
+    guardMaxNumberHeaders count = case fmap unMaxNumberHeaders mnh of
+        Nothing -> pure ()
+        Just n -> when (count >= n) $ throwHttp TooManyHeaderFields
+
+    parseHeaders :: Int -> ([Header] -> [Header]) -> IO [Header]
     parseHeaders count front = do
-        line <- connectionReadLine conn
+        guardMaxNumberHeaders count
+        line <- connectionReadLine mhl conn
         if S.null line
             then return $ front []
-            else do
-                mheader <- parseHeader line
-                case mheader of
+            else
+                parseHeader line >>= \case
                     Just header ->
                         parseHeaders (count + 1) $ front . (header:)
                     Nothing ->
                         -- Unparseable header line; rather than throwing
                         -- an exception, ignore it for robustness.
                         parseHeaders count front
+
+    parseEarlyHintHeadersUntilFailure :: Int -> ([Header] -> [Header]) -> IO [Header]
+    parseEarlyHintHeadersUntilFailure count front = do
+        guardMaxNumberHeaders count
+        line <- connectionReadLine mhl conn
+        if S.null line
+            then return $ front []
+            else
+                parseHeader line >>= \case
+                    Just header ->
+                      parseEarlyHintHeadersUntilFailure (count + 1) $ front . (header:)
+                    Nothing -> do
+                      connectionUnreadLine conn line
+                      return $ front []
 
     parseHeader :: S.ByteString -> IO (Maybe Header)
     parseHeader bs = do
diff --git a/Network/HTTP/Client/Manager.hs b/Network/HTTP/Client/Manager.hs
--- a/Network/HTTP/Client/Manager.hs
+++ b/Network/HTTP/Client/Manager.hs
@@ -92,6 +92,8 @@
     , managerModifyResponse = return
     , managerProxyInsecure = defaultProxy
     , managerProxySecure = defaultProxy
+    , managerMaxHeaderLength = Just $ MaxHeaderLength 4096
+    , managerMaxNumberHeaders = Just $ MaxNumberHeaders 100
     }
 
 -- | Create a 'Manager'. The @Manager@ will be shut down automatically via
@@ -131,6 +133,8 @@
                 if secure req
                     then httpsProxy req
                     else httpProxy req
+            , mMaxHeaderLength = managerMaxHeaderLength ms
+            , mMaxNumberHeaders = managerMaxNumberHeaders ms
             }
     return manager
 
@@ -257,7 +261,9 @@
                     , "\r\n"
                     ]
                 parse conn = do
-                    StatusHeaders status _ _ <- parseStatusHeaders conn Nothing Nothing
+                    let mhl = managerMaxHeaderLength ms
+                        mnh = managerMaxNumberHeaders ms
+                    StatusHeaders status _ _ _ <- parseStatusHeaders mhl mnh conn Nothing (\_ -> return ()) Nothing
                     unless (status == status200) $
                         throwHttp $ ProxyConnectException ultHost ultPort status
                 in tlsProxyConnection
diff --git a/Network/HTTP/Client/Request.hs b/Network/HTTP/Client/Request.hs
--- a/Network/HTTP/Client/Request.hs
+++ b/Network/HTTP/Client/Request.hs
@@ -49,6 +49,7 @@
 import Control.Monad (unless, guard)
 import Control.Monad.IO.Class (MonadIO, liftIO)
 import Numeric (showHex)
+import qualified Data.Set as Set
 
 import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString, toByteStringIO, flush)
 import Blaze.ByteString.Builder.Char8 (fromChar, fromShow)
@@ -121,9 +122,9 @@
 -- You can place the request method at the beginning of the URL separated by a
 -- space, e.g.:
 --
--- @@@
+-- @
 -- parseRequest "POST http://httpbin.org/post"
--- @@@
+-- @
 --
 -- Note that the request method must be provided as all capital letters.
 --
@@ -302,7 +303,10 @@
                 Nothing -> throwIO se
         , requestManagerOverride = Nothing
         , shouldStripHeaderOnRedirect = const False
+        , shouldStripHeaderOnRedirectIfOnDifferentHostOnly = False
         , proxySecureMode = ProxySecureWithConnect
+        , redactHeaders = Set.singleton "Authorization"
+        , earlyHintHeadersReceived = \_ -> return ()
         }
 
 -- | Parses a URL via 'parseRequest_'
@@ -415,6 +419,19 @@
      && ("content-encoding", "gzip") `elem` hs'
      && decompress req (fromMaybe "" $ lookup "content-type" hs')
 
+data EncapsulatedPopperException = EncapsulatedPopperException E.SomeException
+    deriving (Show)
+instance E.Exception EncapsulatedPopperException
+
+-- | Encapsulate a thrown exception into a custom type
+--
+-- During streamed body sending, both the Popper and the connection may throw IO exceptions;
+-- however, we don't want to route the Popper exceptions through onRequestBodyException.
+-- https://github.com/snoyberg/http-client/issues/469
+encapsulatePopperException :: IO a -> IO a
+encapsulatePopperException action =
+    action `E.catch` (\(ex :: E.SomeException) -> E.throwIO (EncapsulatedPopperException ex))
+
 requestBuilder :: Request -> Connection -> IO (Maybe (IO ()))
 requestBuilder req Connection {..} = do
     (contentLength, sendNow, sendLater) <- toTriple (requestBody req)
@@ -423,7 +440,10 @@
         else sendNow >> return Nothing
   where
     expectContinue   = Just "100-continue" == lookup "Expect" (requestHeaders req)
-    checkBadSend f   = f `E.catch` onRequestBodyException req
+    checkBadSend f   = f `E.catches` [
+        E.Handler (\(EncapsulatedPopperException ex) -> throwIO ex)
+      , E.Handler (onRequestBodyException req)
+      ]
     writeBuilder     = toByteStringIO connectionWrite
     writeHeadersWith contentLength = writeBuilder . (builder contentLength `Data.Monoid.mappend`)
     flushHeaders contentLength     = writeHeadersWith contentLength flush
@@ -464,7 +484,7 @@
         withStream (loop 0)
       where
         loop !n stream = do
-            bs <- stream
+            bs <- encapsulatePopperException stream
             if S.null bs
                 then case mlen of
                     -- If stream is chunked, no length argument
diff --git a/Network/HTTP/Client/Response.hs b/Network/HTTP/Client/Response.hs
--- a/Network/HTTP/Client/Response.hs
+++ b/Network/HTTP/Client/Response.hs
@@ -14,6 +14,7 @@
 import Control.Arrow (second)
 
 import Data.Monoid (mempty)
+import Data.List (nubBy)
 
 import qualified Network.HTTP.Types as W
 import Network.URI (parseURIReference, escapeURIString, isAllowedInURI)
@@ -43,21 +44,17 @@
 -- >             (\req' -> do
 -- >                res <- http req'{redirectCount=0} man
 -- >                modify (\rqs -> req' : rqs)
--- >                return (res, getRedirectedRequest req' (responseHeaders res) (responseCookieJar res) (W.statusCode (responseStatus res))
+-- >                return (res, getRedirectedRequest req req' (responseHeaders res) (responseCookieJar res) (W.statusCode (responseStatus res))
 -- >                )
 -- >             'lift'
 -- >             req
 -- >    applyCheckStatus (checkStatus req) res
 -- >    return redirectRequests
-getRedirectedRequest :: Request -> W.ResponseHeaders -> CookieJar -> Int -> Maybe Request
-getRedirectedRequest req hs cookie_jar code
+getRedirectedRequest :: Request -> Request -> W.ResponseHeaders -> CookieJar -> Int -> Maybe Request
+getRedirectedRequest origReq req hs cookie_jar code
     | 300 <= code && code < 400 = do
         l' <- lookup "location" hs
         let l = escapeURIString isAllowedInURI (S8.unpack l')
-            stripHeaders r =
-              r{requestHeaders =
-                filter (not . shouldStripHeaderOnRedirect req . fst) $
-                requestHeaders r}
         req' <- fmap stripHeaders <$> setUriRelative req =<< parseURIReference l
         return $
             if code == 302 || code == 303
@@ -73,8 +70,40 @@
                 else req' {cookieJar = cookie_jar'}
     | otherwise = Nothing
   where
+    cookie_jar' :: Maybe CookieJar
     cookie_jar' = fmap (const cookie_jar) $ cookieJar req
 
+    hostDiffer :: Request -> Bool
+    hostDiffer req = host origReq /= host req
+
+    shouldStripOnlyIfHostDiffer :: Bool
+    shouldStripOnlyIfHostDiffer = shouldStripHeaderOnRedirectIfOnDifferentHostOnly req
+
+    mergeHeaders :: W.RequestHeaders -> W.RequestHeaders -> W.RequestHeaders
+    mergeHeaders lhs rhs = nubBy (\(a, _) (a', _) -> a == a') (lhs ++ rhs)
+
+    stripHeaders :: Request -> Request
+    stripHeaders r = do
+        case (hostDiffer r, shouldStripOnlyIfHostDiffer) of
+            (True, True) -> stripHeaders' r
+            (True, False) -> stripHeaders' r
+            (False, False) -> stripHeaders' r
+            (False, True) -> do
+                -- We need to check if we have omitted headers in previous
+                -- request chain. Consider request chain:
+                --
+                --  1. example-1.com
+                --  2. example-2.com (we may have removed some headers here from 1)
+                --  3. example-1.com (since we are back at same host as 1, we need re-add stripped headers)
+                --
+                let strippedHeaders = filter (shouldStripHeaderOnRedirect r . fst) (requestHeaders origReq)
+                r{requestHeaders = mergeHeaders (requestHeaders r) strippedHeaders}
+
+    stripHeaders' :: Request -> Request
+    stripHeaders' r = r{requestHeaders =
+                        filter (not . shouldStripHeaderOnRedirect req . fst) $
+                        requestHeaders r}
+
 -- | Convert a 'Response' that has a 'Source' body to one with a lazy
 -- 'L.ByteString' body.
 lbsResponse :: Response BodyReader -> IO (Response L.ByteString)
@@ -84,21 +113,30 @@
         { responseBody = L.fromChunks bss
         }
 
-getResponse :: Maybe Int
+getResponse :: Maybe MaxHeaderLength
+            -> Maybe MaxNumberHeaders
+            -> Maybe Int
             -> Request
             -> Managed Connection
             -> Maybe (IO ()) -- ^ Action to run in case of a '100 Continue'.
             -> IO (Response BodyReader)
-getResponse timeout' req@(Request {..}) mconn cont = do
+getResponse mhl mnh timeout' req@(Request {..}) mconn cont = do
     let conn = managedResource mconn
-    StatusHeaders s version hs <- parseStatusHeaders conn timeout' cont
+    StatusHeaders s version earlyHs hs <- parseStatusHeaders mhl mnh conn timeout' earlyHintHeadersReceived cont
     let mcl = lookup "content-length" hs >>= readPositiveInt . S8.unpack
         isChunked = ("transfer-encoding", CI.mk "chunked") `elem` map (second CI.mk) hs
 
         -- should we put this connection back into the connection manager?
         toPut = Just "close" /= lookup "connection" hs && version > W.HttpVersion 1 0
-        cleanup bodyConsumed = managedRelease mconn $ if toPut && bodyConsumed then Reuse else DontReuse
+        cleanup bodyConsumed = do
+            managedRelease mconn $ if toPut && bodyConsumed then Reuse else DontReuse
+            -- Keep alive the `Managed Connection` until we're done with it, to prevent an early
+            -- collection.
+            -- Reasoning: as long as someone holds a reference to the explicit cleanup,
+            -- we shouldn't perform an implicit cleanup.
+            keepAlive mconn
 
+
     body <-
         -- RFC 2616 section 4.4_1 defines responses that must not include a body
         if hasNoBody method (W.statusCode s) || (mcl == Just 0 && not isChunked)
@@ -108,7 +146,7 @@
             else do
                 body1 <-
                     if isChunked
-                        then makeChunkedReader (cleanup True) rawBody conn
+                        then makeChunkedReader mhl (cleanup True) rawBody conn
                         else
                             case mcl of
                                 Just len -> makeLengthReader (cleanup True) len conn
@@ -125,6 +163,7 @@
         , responseCookieJar = Data.Monoid.mempty
         , responseClose' = ResponseClose (cleanup False)
         , responseOriginalRequest = req {requestBody = ""}
+        , responseEarlyHints = earlyHs
         }
 
 -- | Does this response have no body?
diff --git a/Network/HTTP/Client/Types.hs b/Network/HTTP/Client/Types.hs
--- a/Network/HTTP/Client/Types.hs
+++ b/Network/HTTP/Client/Types.hs
@@ -1,8 +1,9 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveTraversable #-}
-{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
 module Network.HTTP.Client.Types
     ( BodyReader
     , Connection (..)
@@ -38,6 +39,8 @@
     , StreamFileStatus (..)
     , ResponseTimeout (..)
     , ProxySecureMode (..)
+    , MaxHeaderLength (..)
+    , MaxNumberHeaders (..)
     ) where
 
 import qualified Data.Typeable as T (Typeable)
@@ -59,6 +62,7 @@
 import Data.IORef
 import qualified Network.Socket as NS
 import qualified Data.Map as Map
+import qualified Data.Set as Set
 import Data.Text (Text)
 import Data.Streaming.Zlib (ZlibException)
 import Data.CaseInsensitive as CI
@@ -88,7 +92,7 @@
     }
     deriving T.Typeable
 
-data StatusHeaders = StatusHeaders Status HttpVersion RequestHeaders
+data StatusHeaders = StatusHeaders Status HttpVersion RequestHeaders RequestHeaders
     deriving (Show, Eq, Ord, T.Typeable)
 
 -- | A newtype wrapper which is not exported from this library but is an
@@ -145,12 +149,14 @@
                    --
                    -- @since 0.5.0
                    | OverlongHeaders
-                   -- ^ Either too many headers, or too many total bytes in a
-                   -- single header, were returned by the server, and the
-                   -- memory exhaustion protection in this library has kicked
-                   -- in.
+                   -- ^ Too many total bytes in the HTTP header were returned
+                   -- by the server.
                    --
                    -- @since 0.5.0
+                   | TooManyHeaderFields
+                   -- ^ Too many HTTP header fields were returned by the server.
+                   --
+                   -- @since 0.7.18
                    | ResponseTimeout
                    -- ^ The server took too long to return a response. This can
                    -- be altered via 'responseTimeout' or
@@ -342,7 +348,7 @@
 -- | Define a HTTP proxy, consisting of a hostname and port number.
 
 data Proxy = Proxy
-    { proxyHost :: S.ByteString -- ^ The host name of the HTTP proxy.
+    { proxyHost :: S.ByteString -- ^ The host name of the HTTP proxy in URI format. IPv6 addresses in square brackets.
     , proxyPort :: Int -- ^ The port number of the HTTP proxy.
     }
     deriving (Show, Read, Eq, Ord, T.Typeable)
@@ -494,6 +500,9 @@
     -- ^ Requested host name, used for both the IP address to connect to and
     -- the @host@ request header.
     --
+    -- This is in URI format, with raw IPv6 addresses enclosed in square brackets.
+    -- Use 'strippedHostName' when making network connections.
+    --
     -- Since 0.1.0
     , port :: Int
     -- ^ The port to connect to. Also used for generating the @host@ request header.
@@ -611,12 +620,29 @@
     --
     -- @since 0.6.2
 
+    , shouldStripHeaderOnRedirectIfOnDifferentHostOnly :: Bool
+    -- ^ Decide whether a header must be stripped from the request
+    -- when following a redirect, if host differs from previous request
+    -- in redirect chain. Default: false (always strip regardless of host change)
+    --
+    -- @since 0.7.15
+
     , proxySecureMode :: ProxySecureMode
     -- ^ How to proxy an HTTPS request.
     --
     -- Default: Use HTTP CONNECT.
     --
     -- @since 0.7.2
+
+    , redactHeaders :: Set.Set HeaderName
+    -- ^ List of header values being redacted in case we show Request.
+    --
+    -- @since 0.7.13
+
+    , earlyHintHeadersReceived :: [Header] -> IO ()
+    -- ^ Called every time an HTTP 103 Early Hints header section is received from the server.
+    --
+    -- @since 0.7.16
     }
     deriving T.Typeable
 
@@ -640,7 +666,7 @@
         , "  host                 = " ++ show (host x)
         , "  port                 = " ++ show (port x)
         , "  secure               = " ++ show (secure x)
-        , "  requestHeaders       = " ++ show (DL.map redactSensitiveHeader (requestHeaders x))
+        , "  requestHeaders       = " ++ show (DL.map (redactSensitiveHeader $ redactHeaders x) (requestHeaders x))
         , "  path                 = " ++ show (path x)
         , "  queryString          = " ++ show (queryString x)
         --, "  requestBody          = " ++ show (requestBody x)
@@ -654,9 +680,11 @@
         , "}"
         ]
 
-redactSensitiveHeader :: Header -> Header
-redactSensitiveHeader ("Authorization", _) = ("Authorization", "<REDACTED>")
-redactSensitiveHeader h = h
+redactSensitiveHeader :: Set.Set HeaderName -> Header -> Header
+redactSensitiveHeader toRedact h@(name, _) =
+  if name `Set.member` toRedact
+    then (name, "<REDACTED>")
+    else h
 
 -- | A simple representation of the HTTP response.
 --
@@ -692,10 +720,15 @@
     -- Since 0.1.0
     , responseOriginalRequest :: Request
     -- ^ Holds original @Request@ related to this @Response@ (with an empty body).
-    -- This field is intentionally not exported directly, but made availble
+    -- This field is intentionally not exported directly, but made available
     -- via @getOriginalRequest@ instead.
     --
     -- Since 0.7.8
+    , responseEarlyHints :: ResponseHeaders
+    -- ^ Early response headers sent by the server, as part of an HTTP
+    -- 103 Early Hints section.
+    --
+    -- Since 0.7.16
     }
     deriving (Show, T.Typeable, Functor, Data.Foldable.Foldable, Data.Traversable.Traversable)
 
@@ -791,6 +824,18 @@
     -- Default: respect the @proxy@ value on the @Request@ itself.
     --
     -- Since 0.4.7
+    , managerMaxHeaderLength :: Maybe MaxHeaderLength
+    -- ^ Configure the maximum size, in bytes, of an HTTP header field.
+    --
+    -- Default: 4096
+    --
+    -- @since 0.7.17
+    , managerMaxNumberHeaders :: Maybe MaxNumberHeaders
+    -- ^ Configure the maximum number of HTTP header fields.
+    --
+    -- Default: 100
+    --
+    -- @since 0.7.18
     }
     deriving T.Typeable
 
@@ -815,8 +860,10 @@
     , mWrapException :: forall a. Request -> IO a -> IO a
     , mModifyRequest :: Request -> IO Request
     , mSetProxy :: Request -> Request
-    , mModifyResponse      :: Response BodyReader -> IO (Response BodyReader)
+    , mModifyResponse :: Response BodyReader -> IO (Response BodyReader)
     -- ^ See 'managerProxy'
+    , mMaxHeaderLength :: Maybe MaxHeaderLength
+    , mMaxNumberHeaders :: Maybe MaxNumberHeaders
     }
     deriving T.Typeable
 
@@ -868,3 +915,19 @@
     , thisChunkSize :: Int
     }
     deriving (Eq, Show, Ord, T.Typeable)
+
+-- | The maximum header size in bytes.
+--
+-- @since 0.7.14
+newtype MaxHeaderLength = MaxHeaderLength
+    { unMaxHeaderLength :: Int
+    }
+    deriving (Eq, Show, Ord, Num, Enum, Bounded, T.Typeable)
+
+-- | The maximum number of header fields.
+--
+-- @since 0.7.18
+newtype MaxNumberHeaders = MaxNumberHeaders
+    { unMaxNumberHeaders :: Int
+    }
+    deriving (Eq, Show, Ord, Num, Enum, Bounded, T.Typeable)
diff --git a/http-client.cabal b/http-client.cabal
--- a/http-client.cabal
+++ b/http-client.cabal
@@ -1,5 +1,5 @@
 name:                http-client
-version:             0.7.8
+version:             0.7.19
 synopsis:            An HTTP client engine
 description:         Hackage documentation generation is not reliable. For up to date documentation, please see: <http://www.stackage.org/package/http-client>.
 homepage:            https://github.com/snoyberg/http-client
@@ -47,7 +47,7 @@
                      , streaming-commons >= 0.1.0.2 && < 0.3
                      , containers        >= 0.5
                      , transformers
-                     , deepseq           >= 1.3    && <1.5
+                     , deepseq           >= 1.3    && <1.6
                      , case-insensitive  >= 1.0
                      , base64-bytestring >= 1.0
                      , cookie
@@ -59,6 +59,7 @@
                      , ghc-prim
                      , stm               >= 2.3
                      , iproute           >= 1.7.5
+                     , async             >= 2.0
   if flag(network-uri)
     build-depends: network >= 2.6, network-uri >= 2.6
   else
@@ -87,6 +88,7 @@
   hs-source-dirs:      test
   default-language:    Haskell2010
   other-modules:       Network.HTTP.ClientSpec
+  build-tool-depends:  hspec-discover:hspec-discover
   build-depends:       base
                      , http-client
                      , hspec
@@ -121,6 +123,8 @@
                        Network.HTTP.Client.RequestSpec
                        Network.HTTP.Client.RequestBodySpec
                        Network.HTTP.Client.CookieSpec
+                       Network.HTTP.Client.ConnectionSpec
+  build-tool-depends:  hspec-discover:hspec-discover
   build-depends:       base
                      , http-client
                      , hspec
diff --git a/test-nonet/Network/HTTP/Client/BodySpec.hs b/test-nonet/Network/HTTP/Client/BodySpec.hs
--- a/test-nonet/Network/HTTP/Client/BodySpec.hs
+++ b/test-nonet/Network/HTTP/Client/BodySpec.hs
@@ -20,42 +20,84 @@
 spec = describe "BodySpec" $ do
     it "chunked, single" $ do
         (conn, _, input) <- dummyConnection
-            [ "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed"
+            [ "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\nnot consumed"
             ]
-        reader <- makeChunkedReader (return ()) False conn
+        reader <- makeChunkedReader Nothing (return ()) False conn
         body <- brConsume reader
         S.concat body `shouldBe` "hello world"
         input' <- input
         S.concat input' `shouldBe` "not consumed"
         brComplete reader `shouldReturn` True
 
+    it "chunked, single, with trailers" $ do
+        (conn, _, input) <- dummyConnection
+            [ "5\r\nhello\r\n6\r\n world\r\n0\r\ntrailers-are: ignored\r\nbut: consumed\r\n\r\nnot consumed"
+            ]
+        reader <- makeChunkedReader Nothing (return ()) False conn
+        body <- brConsume reader
+        S.concat body `shouldBe` "hello world"
+        input' <- input
+        S.concat input' `shouldBe` "not consumed"
+        brComplete reader `shouldReturn` True
+
     it "chunked, pieces" $ do
         (conn, _, input) <- dummyConnection $ map S.singleton $ S.unpack
-            "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed"
-        reader <- makeChunkedReader (return ()) False conn
+            "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\nnot consumed"
+        reader <- makeChunkedReader Nothing (return ()) False conn
         body <- brConsume reader
         S.concat body `shouldBe` "hello world"
         input' <- input
         S.concat input' `shouldBe` "not consumed"
         brComplete reader `shouldReturn` True
 
+    it "chunked, pieces, with trailers" $ do
+        (conn, _, input) <- dummyConnection $ map S.singleton $ S.unpack
+            "5\r\nhello\r\n6\r\n world\r\n0\r\ntrailers-are: ignored\r\nbut: consumed\r\n\r\nnot consumed"
+        reader <- makeChunkedReader Nothing (return ()) False conn
+        body <- brConsume reader
+        S.concat body `shouldBe` "hello world"
+        input' <- input
+        S.concat input' `shouldBe` "not consumed"
+        brComplete reader `shouldReturn` True
+
     it "chunked, raw" $ do
         (conn, _, input) <- dummyConnection
-            [ "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed"
+            [ "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\nnot consumed"
             ]
-        reader <- makeChunkedReader (return ()) True conn
+        reader <- makeChunkedReader Nothing (return ()) True conn
         body <- brConsume reader
-        S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\n"
+        S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n"
         input' <- input
         S.concat input' `shouldBe` "not consumed"
         brComplete reader `shouldReturn` True
 
+    it "chunked, raw, with trailers" $ do
+        (conn, _, input) <- dummyConnection
+            [ "5\r\nhello\r\n6\r\n world\r\n0\r\ntrailers-are: returned\r\nin-raw: body\r\n\r\nnot consumed"
+            ]
+        reader <- makeChunkedReader Nothing (return ()) True conn
+        body <- brConsume reader
+        S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\ntrailers-are: returned\r\nin-raw: body\r\n\r\n"
+        input' <- input
+        S.concat input' `shouldBe` "not consumed"
+        brComplete reader `shouldReturn` True
+
     it "chunked, pieces, raw" $ do
         (conn, _, input) <- dummyConnection $ map S.singleton $ S.unpack
-            "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed"
-        reader <- makeChunkedReader (return ()) True conn
+            "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\nnot consumed"
+        reader <- makeChunkedReader Nothing (return ()) True conn
         body <- brConsume reader
-        S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\n"
+        S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n"
+        input' <- input
+        S.concat input' `shouldBe` "not consumed"
+        brComplete reader `shouldReturn` True
+
+    it "chunked, pieces, raw, with trailers" $ do
+        (conn, _, input) <- dummyConnection $ map S.singleton $ S.unpack
+            "5\r\nhello\r\n6\r\n world\r\n0\r\ntrailers-are: returned\r\nin-raw: body\r\n\r\nnot consumed"
+        reader <- makeChunkedReader Nothing (return ()) True conn
+        body <- brConsume reader
+        S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\ntrailers-are: returned\r\nin-raw: body\r\n\r\n"
         input' <- input
         S.concat input' `shouldBe` "not consumed"
         brComplete reader `shouldReturn` True
diff --git a/test-nonet/Network/HTTP/Client/ConnectionSpec.hs b/test-nonet/Network/HTTP/Client/ConnectionSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-nonet/Network/HTTP/Client/ConnectionSpec.hs
@@ -0,0 +1,23 @@
+module Network.HTTP.Client.ConnectionSpec where
+
+import Network.HTTP.Client (strippedHostName)
+import Test.Hspec
+
+spec :: Spec
+spec = do
+    describe "strippedHostName" $ do
+        it "passes along a normal domain name" $ do
+            strippedHostName "example.com" `shouldBe` "example.com"
+        it "passes along an IPv4 address" $ do
+            strippedHostName "127.0.0.1" `shouldBe` "127.0.0.1"
+        it "strips brackets of an IPv4 address" $ do
+            strippedHostName "[::1]" `shouldBe` "::1"
+            strippedHostName "[::127.0.0.1]" `shouldBe` "::127.0.0.1"
+
+        describe "pathological cases" $ do
+            -- just need to handle these gracefully, it's unclear
+            -- what the result should be
+            it "doesn't touch future ip address formats" $ do
+                strippedHostName "[v2.huh]" `shouldBe` "[v2.huh]"
+            it "doesn't strip trailing stuff" $ do
+                strippedHostName "[::1]foo" `shouldBe` "[::1]foo"
diff --git a/test-nonet/Network/HTTP/Client/HeadersSpec.hs b/test-nonet/Network/HTTP/Client/HeadersSpec.hs
--- a/test-nonet/Network/HTTP/Client/HeadersSpec.hs
+++ b/test-nonet/Network/HTTP/Client/HeadersSpec.hs
@@ -1,6 +1,9 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 module Network.HTTP.Client.HeadersSpec where
 
+import           Control.Concurrent.MVar
+import qualified Data.Sequence as Seq
 import           Network.HTTP.Client.Internal
 import           Network.HTTP.Types
 import           Test.Hspec
@@ -20,8 +23,8 @@
                 , "\nignored"
                 ]
         (connection, _, _) <- dummyConnection input
-        statusHeaders <- parseStatusHeaders connection Nothing Nothing
-        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1)
+        statusHeaders <- parseStatusHeaders Nothing Nothing connection Nothing (\_ -> return ()) Nothing
+        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1) mempty
             [ ("foo", "bar")
             , ("baz", "bin")
             ]
@@ -34,8 +37,8 @@
                 ]
         (conn, out, _) <- dummyConnection input
         let sendBody = connectionWrite conn "data"
-        statusHeaders <- parseStatusHeaders conn Nothing (Just sendBody)
-        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1) [ ("foo", "bar") ]
+        statusHeaders <- parseStatusHeaders Nothing Nothing conn Nothing (\_ -> return ()) (Just sendBody)
+        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1) [] [ ("foo", "bar") ]
         out >>= (`shouldBe` ["data"])
 
     it "Expect: 100-continue (failure)" $ do
@@ -44,8 +47,8 @@
                 ]
         (conn, out, _) <- dummyConnection input
         let sendBody = connectionWrite conn "data"
-        statusHeaders <- parseStatusHeaders conn Nothing (Just sendBody)
-        statusHeaders `shouldBe` StatusHeaders status417 (HttpVersion 1 1) []
+        statusHeaders <- parseStatusHeaders Nothing Nothing conn Nothing (\_ -> return ()) (Just sendBody)
+        statusHeaders `shouldBe` StatusHeaders status417 (HttpVersion 1 1) [] []
         out >>= (`shouldBe` [])
 
     it "100 Continue without expectation is OK" $ do
@@ -56,7 +59,72 @@
                 , "result"
                 ]
         (conn, out, inp) <- dummyConnection input
-        statusHeaders <- parseStatusHeaders conn Nothing Nothing
-        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1) [ ("foo", "bar") ]
+        statusHeaders <- parseStatusHeaders Nothing Nothing conn Nothing (\_ -> return ()) Nothing
+        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1) [] [ ("foo", "bar") ]
         out >>= (`shouldBe` [])
         inp >>= (`shouldBe` ["result"])
+
+    it "103 early hints" $ do
+        let input =
+                [ "HTTP/1.1 103 Early Hints\r\n"
+                , "Link: </foo.js>\r\n"
+                , "Link: </bar.js>\r\n\r\n"
+                , "HTTP/1.1 200 OK\r\n"
+                , "Content-Type: text/html\r\n\r\n"
+                , "<div></div>"
+                ]
+        (conn, _, inp) <- dummyConnection input
+
+        callbackResults :: MVar (Seq.Seq [Header]) <- newMVar mempty
+        let onEarlyHintHeader h = modifyMVar_ callbackResults (return . (Seq.|> h))
+
+        statusHeaders <- parseStatusHeaders Nothing Nothing conn Nothing onEarlyHintHeader Nothing
+        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1)
+            [("Link", "</foo.js>")
+            , ("Link", "</bar.js>")
+            ]
+            [("Content-Type", "text/html")
+            ]
+
+        inp >>= (`shouldBe` ["<div></div>"])
+
+        readMVar callbackResults
+          >>= (`shouldBe` Seq.fromList [
+                  [("Link", "</foo.js>")
+                  , ("Link", "</bar.js>")
+                  ]])
+
+    it "103 early hints (multiple sections)" $ do
+        let input =
+                [ "HTTP/1.1 103 Early Hints\r\n"
+                , "Link: </foo.js>\r\n"
+                , "Link: </bar.js>\r\n\r\n"
+                , "HTTP/1.1 103 Early Hints\r\n"
+                , "Link: </baz.js>\r\n\r\n"
+                , "HTTP/1.1 200 OK\r\n"
+                , "Content-Type: text/html\r\n\r\n"
+                , "<div></div>"
+                ]
+        (conn, _, inp) <- dummyConnection input
+
+        callbackResults :: MVar (Seq.Seq [Header]) <- newMVar mempty
+        let onEarlyHintHeader h = modifyMVar_ callbackResults (return . (Seq.|> h))
+
+        statusHeaders <- parseStatusHeaders Nothing Nothing conn Nothing onEarlyHintHeader Nothing
+        statusHeaders `shouldBe` StatusHeaders status200 (HttpVersion 1 1)
+            [("Link", "</foo.js>")
+            , ("Link", "</bar.js>")
+            , ("Link", "</baz.js>")
+            ]
+            [("Content-Type", "text/html")
+            ]
+
+        inp >>= (`shouldBe` ["<div></div>"])
+
+        readMVar callbackResults
+          >>= (`shouldBe` Seq.fromList [
+                  [("Link", "</foo.js>")
+                  , ("Link", "</bar.js>")
+                  ]
+                  , [("Link", "</baz.js>")]
+                  ])
diff --git a/test-nonet/Network/HTTP/Client/ResponseSpec.hs b/test-nonet/Network/HTTP/Client/ResponseSpec.hs
--- a/test-nonet/Network/HTTP/Client/ResponseSpec.hs
+++ b/test-nonet/Network/HTTP/Client/ResponseSpec.hs
@@ -16,7 +16,7 @@
 
 spec :: Spec
 spec = describe "ResponseSpec" $ do
-    let getResponse' conn = getResponse Nothing req (dummyManaged conn) Nothing
+    let getResponse' conn = getResponse Nothing Nothing Nothing req (dummyManaged conn) Nothing
         req = parseRequest_ "http://localhost"
     it "basic" $ do
         (conn, _, _) <- dummyConnection
@@ -59,7 +59,7 @@
             , "Transfer-encoding: chunked\r\n\r\n"
             , "5\r\nHello\r"
             , "\n2\r\n W"
-            , "\r\n4  ignored\r\norld\r\n0\r\nHTTP/1.1"
+            , "\r\n4  ignored\r\norld\r\n0\r\n\r\nHTTP/1.1"
             ]
         Response {..} <- getResponse' conn
         responseStatus `shouldBe` status200
diff --git a/test-nonet/Network/HTTP/ClientSpec.hs b/test-nonet/Network/HTTP/ClientSpec.hs
--- a/test-nonet/Network/HTTP/ClientSpec.hs
+++ b/test-nonet/Network/HTTP/ClientSpec.hs
@@ -31,6 +31,9 @@
 notWindows x = x
 #endif
 
+crlf :: S.ByteString
+crlf = "\r\n"
+
 main :: IO ()
 main = hspec spec
 
@@ -39,6 +42,27 @@
   let _ = e :: IOError
   return ()
 
+redirectServerToDifferentHost :: Maybe Int -> (Int -> IO a) -> IO a
+redirectServerToDifferentHost maxRedirects inner = bracket
+    (N.bindRandomPortTCP "*4")
+    (NS.close . snd)
+    $ \(port, lsocket) -> withAsync
+        (N.runTCPServer (N.serverSettingsTCPSocket lsocket) app)
+        (const $ inner port)
+    where
+    redirect ad = do
+        N.appWrite ad "HTTP/1.1 301 Redirect\r\nLocation: http://example.com\r\ncontent-length: 5\r\n\r\n"
+        threadDelay 10000
+        N.appWrite ad "hello\r\n"
+        threadDelay 10000
+    app ad = Async.race_
+        (silentIOError $ forever (N.appRead ad))
+        (silentIOError $ case maxRedirects of
+            Nothing -> forever $ redirect ad
+            Just n ->
+              replicateM_ n (redirect ad) >>
+              N.appWrite ad "HTTP/1.1 200 OK\r\ncontent-length: 5\r\n\r\nhello\r\n")
+
 redirectServer :: Maybe Int
                -- ^ If Just, stop redirecting after that many hops.
                -> (Int -> IO a) -> IO a
@@ -177,6 +201,30 @@
           print $ map (requestHeaders . fst) $ hrRedirects hr
           mapM_ (\r -> requestHeaders r `shouldBe` []) $
             map fst $ tail $ hrRedirects hr
+    it "does strips header on redirect, if hosts are different and set to strip them if host differ" $ redirectServerToDifferentHost (Just 1) $ \port -> do
+        req' <- parseUrlThrow $ "http://127.0.0.1:" ++ show port
+        let req = req' { requestHeaders = [(hAuthorization, "abguvatgbfrrurer")]
+                       , redirectCount = 10
+                       , shouldStripHeaderOnRedirect = (== hAuthorization)
+                       , shouldStripHeaderOnRedirectIfOnDifferentHostOnly = True
+                       }
+        man <- newManager defaultManagerSettings
+        withResponseHistory req man $ \hr -> do
+          print $ map (requestHeaders . fst) $ hrRedirects hr
+          mapM_ (\r -> requestHeaders r `shouldBe` []) $
+            map fst $ tail $ hrRedirects hr
+    it "does NOT strips header on redirect, if hosts are same and set to strip them if host differ" $ redirectServerToDifferentHost (Just 1) $ \port -> do
+        req' <- parseUrlThrow $ "http://127.0.0.1:" ++ show port
+        let req = req' { requestHeaders = [(hAuthorization, "abguvatgbfrrurer")]
+                       , redirectCount = 10
+                       , shouldStripHeaderOnRedirect = (== hAuthorization)
+                       , shouldStripHeaderOnRedirectIfOnDifferentHostOnly = True
+                       }
+        man <- newManager defaultManagerSettings
+        withResponseHistory req man $ \hr -> do
+          print $ map (requestHeaders . fst) $ hrRedirects hr
+          mapM_ (\r -> requestHeaders r `shouldBe` [("Authorization","abguvatgbfrrurer")]) $
+            map fst $ tail $ hrRedirects hr
     it "redirecting #41" $ redirectServer Nothing $ \port -> do
         req' <- parseUrlThrow $ "http://127.0.0.1:" ++ show port
         let req = req' { redirectCount = 1 }
@@ -278,3 +326,24 @@
       case parseRequest "https://o_O:18446744072699450606" of
         Left _ -> pure () :: IO ()
         Right req -> error $ "Invalid request: " ++ show req
+
+    it "too many header fields" $ do
+        let message = S.concat $
+                ["HTTP/1.1 200 OK", crlf] <> replicate 120 ("foo: bar" <> crlf) <> [crlf, "body"]
+
+        serveWith message $ \port -> do
+            man <- newManager $ managerSetMaxNumberHeaders 120 defaultManagerSettings
+            req <- parseUrlThrow $ "http://127.0.0.1:" ++ show port
+            httpLbs req man `shouldThrow` \e -> case e of
+                HttpExceptionRequest _ TooManyHeaderFields -> True
+                _otherwise -> False
+
+    it "not too many header fields" $ do
+        let message = S.concat $
+                ["HTTP/1.1 200 OK", crlf] <> replicate 120 ("foo: bar" <> crlf) <> [crlf, "body"]
+
+        serveWith message $ \port -> do
+            man <- newManager $ managerSetMaxNumberHeaders 121 defaultManagerSettings
+            req <- parseUrlThrow $ "http://127.0.0.1:" ++ show port
+            res <- httpLbs req man
+            responseBody res `shouldBe` "body"
diff --git a/test/Network/HTTP/ClientSpec.hs b/test/Network/HTTP/ClientSpec.hs
--- a/test/Network/HTTP/ClientSpec.hs
+++ b/test/Network/HTTP/ClientSpec.hs
@@ -1,14 +1,17 @@
 {-# LANGUAGE OverloadedStrings #-}
 module Network.HTTP.ClientSpec where
 
+import           Control.Concurrent (threadDelay)
 import qualified Data.ByteString.Char8        as BS
 import           Network.HTTP.Client
 import           Network.HTTP.Client.Internal
 import           Network.HTTP.Types        (status200, found302, status405)
 import           Network.HTTP.Types.Status
+import qualified Network.Socket               as NS
 import           Test.Hspec
 import           Control.Applicative       ((<$>))
 import           Data.ByteString.Lazy.Char8 () -- orphan instance
+import           System.Mem (performGC)
 
 main :: IO ()
 main = hspec spec
@@ -21,6 +24,31 @@
         res <- httpLbs req man
         responseStatus res `shouldBe` status200
 
+    -- Test the failure condition described in https://github.com/snoyberg/http-client/issues/489
+    it "keeps connection alive long enough" $ do
+        req <- parseUrlThrow "http://httpbin.org/"
+        man <- newManager defaultManagerSettings
+        res <- responseOpen req man
+        responseStatus res `shouldBe` status200
+        let
+            getChunk = responseBody res
+            drainAll = do
+                chunk <- getChunk
+                if BS.null chunk then pure () else drainAll
+
+        -- The returned `BodyReader` used to not contain a reference to the `Managed Connection`,
+        -- only to the extracted connection and to the release action. Therefore, triggering a GC
+        -- would close the connection even though we were not done reading.
+        performGC
+        -- Not ideal, but weak finalizers run on a separate thread, so it's racing with our drain
+        -- call
+        threadDelay 500000
+
+        drainAll
+        -- Calling `responseClose res` here prevents the early collection from happening in this
+        -- test, but in a larger production application that did involve a `responseClose`, it still
+        -- occurred.
+
     describe "method in URL" $ do
         it "success" $ do
             req <- parseUrlThrow "POST http://httpbin.org/post"
@@ -106,3 +134,23 @@
             man <- newManager settings
             response <- httpLbs "http://httpbin.org/redirect-to?url=foo" man
             responseStatus response `shouldBe` found302
+
+    -- skipped because CI doesn't have working IPv6
+    xdescribe "raw IPV6 address as hostname" $ do
+        it "works" $ do
+            -- We rely on example.com serving a web page over IPv6.
+            -- The request (currently) actually ends up as 404 due to
+            -- virtual hosting, but we just care that the networking
+            -- side works.
+            (addr:_) <- NS.getAddrInfo
+                (Just NS.defaultHints { NS.addrFamily = NS.AF_INET6 })
+                (Just "example.com")
+                (Just "http")
+            -- ipv6Port will be of the form [::1]:80, which is good enough
+            -- for our purposes; ideally we'd easily get just the ::1.
+            let ipv6Port = show $ NS.addrAddress addr
+            ipv6Port `shouldStartWith` "["
+            req <- parseUrlThrow $ "http://" ++ ipv6Port
+            man <- newManager defaultManagerSettings
+            _ <- httpLbs (setRequestIgnoreStatus req) man
+            return ()
