packages feed

http-conduit 1.1.1 → 1.1.2.2

raw patch · 12 files changed

+1650/−1647 lines, 12 filesdep ~failuresetup-changedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: failure

API changes (from Hackage documentation)

Files

LICENSE view
@@ -1,25 +1,25 @@-The following license covers this documentation, and the source code, except
-where otherwise indicated.
-
-Copyright 2010, Michael Snoyman. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
-IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+The following license covers this documentation, and the source code, except+where otherwise indicated.++Copyright 2010, Michael Snoyman. All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Network/HTTP/Conduit.hs view
@@ -1,254 +1,253 @@-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE CPP #-}
--- | This module contains everything you need to initiate HTTP connections.  If
--- you want a simple interface based on URLs, you can use 'simpleHttp'. If you
--- want raw power, 'http' is the underlying workhorse of this package. Some
--- examples:
---
--- > -- Just download an HTML document and print it.
--- > import Network.HTTP.Conduit
--- > import qualified Data.ByteString.Lazy as L
--- >
--- > main = simpleHttp "http://www.haskell.org/" >>= L.putStr
---
--- This example uses interleaved IO to write the response body to a file in
--- constant memory space.
---
--- > import Data.Conduit.Binary (sinkFile)
--- > import Network.HTTP.Conduit
--- > import System.IO
--- > import qualified Data.Conduit as C
--- >
--- > main :: IO ()
--- > main = do
--- >     request <- parseUrl "http://google.com/"
--- >     withManager $ \manager -> do
--- >         Response _ _ bsrc <- http request handler manager
--- >         bsrc C.$$ sinkFile "google.html"
---
--- The following headers are automatically set by this module, and should not
--- be added to 'requestHeaders':
---
--- * Content-Length
---
--- * Host
---
--- * Accept-Encoding (not currently set, but client usage of this variable /will/ cause breakage).
---
--- Any network code on Windows requires some initialization, and the network
--- library provides withSocketsDo to perform it. Therefore, proper usage of
--- this library will always involve calling that function at some point.  The
--- best approach is to simply call them at the beginning of your main function,
--- such as:
---
--- > import Network.HTTP.Conduit
--- > import qualified Data.ByteString.Lazy as L
--- > import Network (withSocketsDo)
--- >
--- > main = withSocketsDo
--- >      $ simpleHttp "http://www.haskell.org/" >>= L.putStr
-module Network.HTTP.Conduit
-    ( -- * Perform a request
-      simpleHttp
-    , httpLbs
-    , http
-      -- * Datatypes
-    , Proxy (..)
-    , RequestBody (..)
-    , Response (..)
-      -- ** Request
-    , Request
-    , def
-    , method
-    , secure
-    , checkCerts
-    , host
-    , port
-    , path
-    , queryString
-    , requestHeaders
-    , requestBody
-    , proxy
-    , rawBody
-    , decompress
-    , redirectCount
-    , checkStatus
-      -- *** Defaults
-    , defaultCheckCerts
-      -- * Manager
-    , Manager
-    , newManager
-    , newManagerCount
-    , newManagerIO
-    , withManager
-      -- * Utility functions
-    , parseUrl
-    , applyBasicAuth
-    , addProxy
-    , lbsResponse
-      -- * Decompression predicates
-    , alwaysDecompress
-    , browserDecompress
-      -- * Request bodies
-    , urlEncodedBody
-      -- * Exceptions
-    , HttpException (..)
-#if DEBUG
-      -- * Debug
-    , printOpenSockets
-#endif
-    ) where
-
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Lazy as L
-import qualified Data.ByteString.Char8 as S8
-
-import qualified Network.HTTP.Types as W
-import Data.Default (def)
-
-import Control.Exception.Lifted (throwIO)
-import Control.Monad.Base (liftBase)
-import Control.Monad.IO.Class (MonadIO (liftIO))
-
-import qualified Data.Conduit as C
-import Data.Conduit.Blaze (builderToByteString)
-import Control.Monad.Trans.Resource (ResourceT, runResourceT, ResourceIO)
-import Control.Exception.Lifted (try, SomeException)
-
-import Network.HTTP.Conduit.Request
-import Network.HTTP.Conduit.Response
-import Network.HTTP.Conduit.Manager
-import Network.HTTP.Conduit.ConnInfo
-
--- | 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
--- 'C.BufferedSource'.  The 'Response' contains the status code
--- and headers that were sent back to us, and the
--- 'C.BufferedSource' contains the body of the request.  Note
--- that this 'C.BufferedSource' allows you to have fully
--- interleaved IO actions during your HTTP download, making it
--- possible to download very large responses in constant memory.
--- You may also directly connect the returned 'C.BufferedSource'
--- into a 'C.Sink', perhaps a file or another socket.
---
--- Note: Unlike previous versions, this function will perform redirects, as
--- specified by the 'redirectCount' setting.
-http
-    :: ResourceIO m
-    => Request m
-    -> Manager
-    -> ResourceT m (Response (C.BufferedSource m S.ByteString))
-http req0 manager = do
-    res@(Response status hs body) <-
-        if redirectCount req0 == 0
-            then httpRaw req0 manager
-            else go (redirectCount req0) req0
-    case checkStatus req0 status hs of
-        Nothing -> return res
-        Just exc -> do
-            C.bsourceClose body
-            liftBase $ throwIO exc
-  where
-    go 0 _ = liftBase $ throwIO TooManyRedirects
-    go count req = do
-        res@(Response (W.Status code _) hs _) <- httpRaw req manager
-        case (300 <= code && code < 400, lookup "location" hs) of
-            (True, Just l'') -> do
-                -- Prepend scheme, host and port if missing
-                let l' =
-                        case S8.uncons l'' of
-                            Just ('/', _) -> concat
-                                [ "http"
-                                , if secure req then "s" else ""
-                                , "://"
-                                , S8.unpack $ host req
-                                , ":"
-                                , show $ port req
-                                , S8.unpack l''
-                                ]
-                            _ -> S8.unpack l''
-                l <- liftBase $ parseUrl l'
-                let req' = req
-                        { host = host l
-                        , port = port l
-                        , secure = secure l
-                        , path = path l
-                        , queryString = queryString l
-                        , method =
-                            -- According to the spec, this should *only* be for
-                            -- status code 303. However, almost all clients
-                            -- mistakenly implement it for 302 as well. So we
-                            -- have to be wrong like everyone else...
-                            if code == 302 || code == 303
-                                then "GET"
-                                else method l
-                        }
-                go (count - 1) req'
-            _ -> return res
-
--- | Get a 'Response' without any redirect following.
-httpRaw
-     :: ResourceIO m
-     => Request m
-     -> Manager
-     -> ResourceT m (Response (C.BufferedSource m S.ByteString))
-httpRaw req m = do
-    (connRelease, ci, isManaged) <- getConn req m
-    bsrc <- C.bufferSource $ connSource ci
-    ex <- try $ requestBuilder req C.$$ builderToByteString C.=$ connSink ci
-    case (ex :: Either SomeException (), isManaged) of
-        -- Connection was reused, and might be been closed. Try again
-        (Left _, Reused) -> do
-            connRelease DontReuse
-            http req m
-        -- Not reused, so this is a real exception
-        (Left e, Fresh) -> liftBase $ throwIO e
-        -- Everything went ok, so the connection is good. If any exceptions get
-        -- thrown in the rest of the code, just throw them as normal.
-        (Right (), _) -> getResponse connRelease req bsrc
-
--- | Download the specified 'Request', returning the results as a 'Response'.
---
--- This is a simplified version of 'http' for the common case where you simply
--- want the response data as a simple datatype. If you want more power, such as
--- interleaved actions on the response body during download, you'll need to use
--- 'http' directly. This function is defined as:
---
--- @httpLbs = 'lbsResponse' . 'http'@
---
--- Even though the 'Response' contains a lazy bytestring, this
--- function does /not/ utilize lazy I/O, and therefore the entire
--- response body will live in memory. If you want constant memory
--- usage, you'll need to use @conduit@ packages's
--- 'C.BufferedSource' returned by 'http'.
---
--- Note: Unlike previous versions, this function will perform redirects, as
--- specified by the 'redirectCount' setting.
-httpLbs :: ResourceIO m => Request m -> Manager -> ResourceT m (Response L.ByteString)
-httpLbs r = lbsResponse . http r
-
--- | Download the specified URL, following any redirects, and
--- return the response body.
---
--- This function will 'throwIO' an 'HttpException' for any
--- response with a non-2xx status code (besides 3xx redirects up
--- to a limit of 10 redirects). It uses 'parseUrl' to parse the
--- input. This function essentially wraps 'httpLbsRedirect'.
---
--- Note: Even though this function returns a lazy bytestring, it
--- does /not/ utilize lazy I/O, and therefore the entire response
--- body will live in memory. If you want constant memory usage,
--- you'll need to use the @conduit@ package and 'http' or
--- 'httpRedirect' directly.
-simpleHttp :: MonadIO m => String -> m L.ByteString
-simpleHttp url = liftIO $ runResourceT $ do
-    url' <- liftBase $ parseUrl url
-    man <- newManager
-    fmap responseBody $ httpLbs url' man
+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE CPP #-}+-- | This module contains everything you need to initiate HTTP connections.  If+-- you want a simple interface based on URLs, you can use 'simpleHttp'. If you+-- want raw power, 'http' is the underlying workhorse of this package. Some+-- examples:+--+-- > -- Just download an HTML document and print it.+-- > import Network.HTTP.Conduit+-- > import qualified Data.ByteString.Lazy as L+-- >+-- > main = simpleHttp "http://www.haskell.org/" >>= L.putStr+--+-- This example uses interleaved IO to write the response body to a file in+-- constant memory space.+--+-- > import Data.Conduit.Binary (sinkFile)+-- > import Network.HTTP.Conduit+-- > import qualified Data.Conduit as C+-- >+-- > main :: IO ()+-- > main = do+-- >      request <- parseUrl "http://google.com/"+-- >      withManager $ \manager -> do+-- >          Response _ _ bsrc <- http request manager+-- >          bsrc C.$$ sinkFile "google.html"+--+-- The following headers are automatically set by this module, and should not+-- be added to 'requestHeaders':+--+-- * Content-Length+--+-- * Host+--+-- * Accept-Encoding (not currently set, but client usage of this variable /will/ cause breakage).+--+-- Any network code on Windows requires some initialization, and the network+-- library provides withSocketsDo to perform it. Therefore, proper usage of+-- this library will always involve calling that function at some point.  The+-- best approach is to simply call them at the beginning of your main function,+-- such as:+--+-- > import Network.HTTP.Conduit+-- > import qualified Data.ByteString.Lazy as L+-- > import Network (withSocketsDo)+-- >+-- > main = withSocketsDo+-- >      $ simpleHttp "http://www.haskell.org/" >>= L.putStr+module Network.HTTP.Conduit+    ( -- * Perform a request+      simpleHttp+    , httpLbs+    , http+      -- * Datatypes+    , Proxy (..)+    , RequestBody (..)+    , Response (..)+      -- ** Request+    , Request+    , def+    , method+    , secure+    , checkCerts+    , host+    , port+    , path+    , queryString+    , requestHeaders+    , requestBody+    , proxy+    , rawBody+    , decompress+    , redirectCount+    , checkStatus+      -- *** Defaults+    , defaultCheckCerts+      -- * Manager+    , Manager+    , newManager+    , newManagerCount+    , newManagerIO+    , withManager+      -- * Utility functions+    , parseUrl+    , applyBasicAuth+    , addProxy+    , lbsResponse+      -- * Decompression predicates+    , alwaysDecompress+    , browserDecompress+      -- * Request bodies+    , urlEncodedBody+      -- * Exceptions+    , HttpException (..)+#if DEBUG+      -- * Debug+    , printOpenSockets+#endif+    ) where++import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Char8 as S8++import qualified Network.HTTP.Types as W+import Data.Default (def)++import Control.Exception.Lifted (throwIO)+import Control.Monad.Base (liftBase)+import Control.Monad.IO.Class (MonadIO (liftIO))++import qualified Data.Conduit as C+import Data.Conduit.Blaze (builderToByteString)+import Control.Monad.Trans.Resource (ResourceT, runResourceT, ResourceIO)+import Control.Exception.Lifted (try, SomeException)++import Network.HTTP.Conduit.Request+import Network.HTTP.Conduit.Response+import Network.HTTP.Conduit.Manager+import Network.HTTP.Conduit.ConnInfo++-- | 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+-- 'C.BufferedSource'.  The 'Response' contains the status code+-- and headers that were sent back to us, and the+-- 'C.BufferedSource' contains the body of the request.  Note+-- that this 'C.BufferedSource' allows you to have fully+-- interleaved IO actions during your HTTP download, making it+-- possible to download very large responses in constant memory.+-- You may also directly connect the returned 'C.BufferedSource'+-- into a 'C.Sink', perhaps a file or another socket.+--+-- Note: Unlike previous versions, this function will perform redirects, as+-- specified by the 'redirectCount' setting.+http+    :: ResourceIO m+    => Request m+    -> Manager+    -> ResourceT m (Response (C.BufferedSource m S.ByteString))+http req0 manager = do+    res@(Response status hs body) <-+        if redirectCount req0 == 0+            then httpRaw req0 manager+            else go (redirectCount req0) req0+    case checkStatus req0 status hs of+        Nothing -> return res+        Just exc -> do+            C.bsourceClose body+            liftBase $ throwIO exc+  where+    go 0 _ = liftBase $ throwIO TooManyRedirects+    go count req = do+        res@(Response (W.Status code _) hs _) <- httpRaw req manager+        case (300 <= code && code < 400, lookup "location" hs) of+            (True, Just l'') -> do+                -- Prepend scheme, host and port if missing+                let l' =+                        case S8.uncons l'' of+                            Just ('/', _) -> concat+                                [ "http"+                                , if secure req then "s" else ""+                                , "://"+                                , S8.unpack $ host req+                                , ":"+                                , show $ port req+                                , S8.unpack l''+                                ]+                            _ -> S8.unpack l''+                l <- liftBase $ parseUrl l'+                let req' = req+                        { host = host l+                        , port = port l+                        , secure = secure l+                        , path = path l+                        , queryString = queryString l+                        , method =+                            -- According to the spec, this should *only* be for+                            -- status code 303. However, almost all clients+                            -- mistakenly implement it for 302 as well. So we+                            -- have to be wrong like everyone else...+                            if code == 302 || code == 303+                                then "GET"+                                else method l+                        }+                go (count - 1) req'+            _ -> return res++-- | Get a 'Response' without any redirect following.+httpRaw+     :: ResourceIO m+     => Request m+     -> Manager+     -> ResourceT m (Response (C.BufferedSource m S.ByteString))+httpRaw req m = do+    (connRelease, ci, isManaged) <- getConn req m+    bsrc <- C.bufferSource $ connSource ci+    ex <- try $ requestBuilder req C.$$ builderToByteString C.=$ connSink ci+    case (ex :: Either SomeException (), isManaged) of+        -- Connection was reused, and might be been closed. Try again+        (Left _, Reused) -> do+            connRelease DontReuse+            http req m+        -- Not reused, so this is a real exception+        (Left e, Fresh) -> liftBase $ throwIO e+        -- Everything went ok, so the connection is good. If any exceptions get+        -- thrown in the rest of the code, just throw them as normal.+        (Right (), _) -> getResponse connRelease req bsrc++-- | Download the specified 'Request', returning the results as a 'Response'.+--+-- This is a simplified version of 'http' for the common case where you simply+-- want the response data as a simple datatype. If you want more power, such as+-- interleaved actions on the response body during download, you'll need to use+-- 'http' directly. This function is defined as:+--+-- @httpLbs = 'lbsResponse' . 'http'@+--+-- Even though the 'Response' contains a lazy bytestring, this+-- function does /not/ utilize lazy I/O, and therefore the entire+-- response body will live in memory. If you want constant memory+-- usage, you'll need to use @conduit@ packages's+-- 'C.BufferedSource' returned by 'http'.+--+-- Note: Unlike previous versions, this function will perform redirects, as+-- specified by the 'redirectCount' setting.+httpLbs :: ResourceIO m => Request m -> Manager -> ResourceT m (Response L.ByteString)+httpLbs r = lbsResponse . http r++-- | Download the specified URL, following any redirects, and+-- return the response body.+--+-- This function will 'throwIO' an 'HttpException' for any+-- response with a non-2xx status code (besides 3xx redirects up+-- to a limit of 10 redirects). It uses 'parseUrl' to parse the+-- input. This function essentially wraps 'httpLbsRedirect'.+--+-- Note: Even though this function returns a lazy bytestring, it+-- does /not/ utilize lazy I/O, and therefore the entire response+-- body will live in memory. If you want constant memory usage,+-- you'll need to use the @conduit@ package and 'http' or+-- 'httpRedirect' directly.+simpleHttp :: MonadIO m => String -> m L.ByteString+simpleHttp url = liftIO $ runResourceT $ do+    url' <- liftBase $ parseUrl url+    man <- newManager+    fmap responseBody $ httpLbs url' man
Network/HTTP/Conduit/Chunk.hs view
@@ -1,82 +1,82 @@-{-# LANGUAGE FlexibleContexts #-}
-module Network.HTTP.Conduit.Chunk
-    ( chunkedConduit
-    , chunkIt
-    ) where
-
-import Control.Exception (assert)
-import Numeric (showHex)
-
-import Control.Monad.Trans.Class (lift)
-
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Char8 as S8
-
-import Blaze.ByteString.Builder.HTTP
-import qualified Blaze.ByteString.Builder as Blaze
-
-import qualified Data.Attoparsec.ByteString as A
-
-import qualified Data.Conduit as C
-import Data.Conduit.Attoparsec (ParseError (ParseError))
-
-import Network.HTTP.Conduit.Parser
-
-
-data CState = NeedHeader (S.ByteString -> A.Result Int)
-            | Isolate Int
-            | NeedNewline (S.ByteString -> A.Result ())
-            | Complete
-
-chunkedConduit :: C.ResourceThrow m
-               => Bool -- ^ send the headers as well, necessary for a proxy
-               -> C.Conduit S.ByteString m S.ByteString
-chunkedConduit sendHeaders = C.conduitState
-    (NeedHeader $ A.parse parseChunkHeader)
-    (push id)
-    close
-  where
-    push front (NeedHeader f) x =
-        case f x of
-            A.Done x' i
-                | i == 0 -> push front Complete x'
-                | otherwise -> do
-                    let header = S8.pack $ showHex i "\r\n"
-                    let addHeader = if sendHeaders then (header:) else id
-                    push (front . addHeader) (Isolate i) x'
-            A.Partial f' -> return (NeedHeader f', C.Producing $ front [])
-            A.Fail _ contexts msg -> lift $ C.resourceThrow $ ParseError contexts msg
-    push front (Isolate i) x = do
-        let (a, b) = S.splitAt i x
-            i' = i - S.length a
-        if i' == 0
-            then push
-                    (front . (a:))
-                    (NeedNewline $ A.parse newline)
-                    b
-            else assert (S.null b) $ return
-                ( Isolate i'
-                , C.Producing (front [a])
-                )
-    push front (NeedNewline f) x =
-        case f x of
-            A.Done x' () -> do
-                let header = S8.pack "\r\n"
-                let addHeader = if sendHeaders then (header:) else id
-                push
-                    (front . addHeader)
-                    (NeedHeader $ A.parse parseChunkHeader)
-                    x'
-            A.Partial f' -> return (NeedNewline f', C.Producing $ front [])
-            A.Fail _ contexts msg -> lift $ C.resourceThrow $ ParseError contexts msg
-    push front Complete leftover = do
-        let end = if sendHeaders then [S8.pack "0\r\n"] else []
-            lo = if S.null leftover then Nothing else Just leftover
-        return (Complete, C.Finished lo $ front end)
-    close _ = return []
-
-chunkIt :: C.Resource m => C.Conduit Blaze.Builder m Blaze.Builder
-chunkIt = C.Conduit $ return $ C.PreparedConduit
-    { C.conduitPush = \xs -> return $ C.Producing [chunkedTransferEncoding xs]
-    , C.conduitClose = return [chunkedTransferTerminator]
-    }
+{-# LANGUAGE FlexibleContexts #-}+module Network.HTTP.Conduit.Chunk+    ( chunkedConduit+    , chunkIt+    ) where++import Control.Exception (assert)+import Numeric (showHex)++import Control.Monad.Trans.Class (lift)++import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8++import Blaze.ByteString.Builder.HTTP+import qualified Blaze.ByteString.Builder as Blaze++import qualified Data.Attoparsec.ByteString as A++import qualified Data.Conduit as C+import Data.Conduit.Attoparsec (ParseError (ParseError))++import Network.HTTP.Conduit.Parser+++data CState = NeedHeader (S.ByteString -> A.Result Int)+            | Isolate Int+            | NeedNewline (S.ByteString -> A.Result ())+            | Complete++chunkedConduit :: C.ResourceThrow m+               => Bool -- ^ send the headers as well, necessary for a proxy+               -> C.Conduit S.ByteString m S.ByteString+chunkedConduit sendHeaders = C.conduitState+    (NeedHeader $ A.parse parseChunkHeader)+    (push id)+    close+  where+    push front (NeedHeader f) x =+        case f x of+            A.Done x' i+                | i == 0 -> push front Complete x'+                | otherwise -> do+                    let header = S8.pack $ showHex i "\r\n"+                    let addHeader = if sendHeaders then (header:) else id+                    push (front . addHeader) (Isolate i) x'+            A.Partial f' -> return (NeedHeader f', C.Producing $ front [])+            A.Fail _ contexts msg -> lift $ C.resourceThrow $ ParseError contexts msg+    push front (Isolate i) x = do+        let (a, b) = S.splitAt i x+            i' = i - S.length a+        if i' == 0+            then push+                    (front . (a:))+                    (NeedNewline $ A.parse newline)+                    b+            else assert (S.null b) $ return+                ( Isolate i'+                , C.Producing (front [a])+                )+    push front (NeedNewline f) x =+        case f x of+            A.Done x' () -> do+                let header = S8.pack "\r\n"+                let addHeader = if sendHeaders then (header:) else id+                push+                    (front . addHeader)+                    (NeedHeader $ A.parse parseChunkHeader)+                    x'+            A.Partial f' -> return (NeedNewline f', C.Producing $ front [])+            A.Fail _ contexts msg -> lift $ C.resourceThrow $ ParseError contexts msg+    push front Complete leftover = do+        let end = if sendHeaders then [S8.pack "0\r\n"] else []+            lo = if S.null leftover then Nothing else Just leftover+        return (Complete, C.Finished lo $ front end)+    close _ = return []++chunkIt :: C.Resource m => C.Conduit Blaze.Builder m Blaze.Builder+chunkIt = C.Conduit $ return $ C.PreparedConduit+    { C.conduitPush = \xs -> return $ C.Producing [chunkedTransferEncoding xs]+    , C.conduitClose = return [chunkedTransferTerminator]+    }
Network/HTTP/Conduit/ConnInfo.hs view
@@ -1,169 +1,169 @@-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE CPP #-}
-module Network.HTTP.Conduit.ConnInfo
-    ( ConnInfo
-    , connClose
-    , connSink
-    , connSource
-    , sslClientConn
-    , socketConn
-    , TLSCertificateRejectReason(..)
-    , TLSCertificateUsage(..)
-    , getSocket
-#if DEBUG
-    , printOpenSockets
-    , requireAllSocketsClosed
-    , clearSocketsList
-#endif
-    ) where
-
-import Control.Exception (SomeException, throwIO, try)
-import System.IO (Handle, hClose)
-
-import Control.Monad.Base (MonadBase, liftBase)
-
-import Data.ByteString (ByteString)
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Lazy as L
-
-import Network.Socket (Socket, sClose)
-import Network.Socket.ByteString (recv, sendAll)
-import qualified Network.Socket as NS
-
-import Network.TLS
-import Network.TLS.Extra (ciphersuite_all)
-
-import Data.Certificate.X509 (X509)
-
-import Crypto.Random.AESCtr (makeSystem)
-
-import qualified Data.Conduit as C
-
-#if DEBUG
-import qualified Data.IntMap as IntMap
-import qualified Data.IORef as I
-import System.IO.Unsafe (unsafePerformIO)
-#endif
-
-data ConnInfo = ConnInfo
-    { connRead :: IO ByteString
-    , connWrite :: ByteString -> IO ()
-    , connClose :: IO ()
-    }
-
-connSink :: C.ResourceIO m => ConnInfo -> C.Sink ByteString m ()
-connSink ConnInfo { connWrite = write } = C.Sink $ return $ C.SinkData
-    { C.sinkPush = \bss -> liftBase (write bss) >> return C.Processing
-    , C.sinkClose = return ()
-    }
-
-connSource :: C.ResourceIO m => ConnInfo -> C.Source m ByteString
-connSource ConnInfo { connRead = read' } = C.Source $ return $ C.PreparedSource
-    { C.sourcePull = do
-        bs <- liftBase read'
-        if S.null bs
-            then return C.Closed
-            else return $ C.Open bs
-    , C.sourceClose = return ()
-    }
-
-#if DEBUG
-allOpenSockets :: I.IORef (Int, IntMap.IntMap String)
-allOpenSockets = unsafePerformIO $ I.newIORef (0, IntMap.empty)
-
-addSocket :: String -> IO Int
-addSocket desc = I.atomicModifyIORef allOpenSockets $ \(next, m) ->
-    ((next + 1, IntMap.insert next desc m), next)
-
-removeSocket :: Int -> IO ()
-removeSocket i = I.atomicModifyIORef allOpenSockets $ \(next, m) ->
-    ((next, IntMap.delete i m), ())
-
-printOpenSockets :: IO ()
-printOpenSockets = do
-    (_, m) <- I.readIORef allOpenSockets
-    putStrLn "\n\nOpen sockets:"
-    if IntMap.null m
-        then putStrLn "** No open sockets!"
-        else mapM_ putStrLn $ IntMap.elems m
-
-requireAllSocketsClosed :: IO ()
-requireAllSocketsClosed = do
-    (_, m) <- I.readIORef allOpenSockets
-    if IntMap.null m
-        then return ()
-        else error $ unlines
-            $ "requireAllSocketsClosed: there are open sockets"
-            : IntMap.elems m
-
-clearSocketsList :: IO ()
-clearSocketsList = I.writeIORef allOpenSockets (0, IntMap.empty)
-#endif
-
-socketConn :: String -> Socket -> IO ConnInfo
-socketConn _desc sock = do
-#if DEBUG
-    i <- addSocket _desc
-#endif
-    return ConnInfo
-        { connRead  = recv sock 4096
-        , connWrite = sendAll sock
-        , connClose = do
-#if DEBUG
-            removeSocket i
-#endif
-            sClose sock
-        }
-
-sslClientConn :: String -> ([X509] -> IO TLSCertificateUsage) -> Handle -> IO ConnInfo
-sslClientConn _desc onCerts h = do
-#if DEBUG
-    i <- addSocket _desc
-#endif
-    let tcp = defaultParams
-            { pConnectVersion = TLS10
-            , pAllowedVersions = [ TLS10, TLS11 ]
-            , pCiphers = ciphersuite_all
-            , onCertificatesRecv = onCerts
-            }
-    gen <- makeSystem
-    istate <- client tcp gen h
-    _ <- handshake istate
-    return ConnInfo
-        { connRead = recvD istate
-        , connWrite = sendData istate . L.fromChunks . (:[])
-        , connClose = do
-#if DEBUG
-            removeSocket i
-#endif
-            bye istate
-            hClose h
-        }
-  where
-    recvD istate = do
-        x <- recvData istate
-        if L.null x
-            then recvD istate
-            else return $ S.concat $ L.toChunks x
-            -- Although a 'concat' seems like a bad idea, at
-            -- least on tls-0.8.4 it's guaranteed to always
-            -- return a lazy bytestring with a single chunk.
-
-getSocket :: String -> Int -> IO NS.Socket
-getSocket host' port' = do
-    let hints = NS.defaultHints {
-                          NS.addrFlags = [NS.AI_ADDRCONFIG]
-                        , NS.addrSocketType = NS.Stream
-                        }
-    (addr:_) <- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')
-    sock <- NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)
-                      (NS.addrProtocol addr)
-    ee <- try' $ NS.connect sock (NS.addrAddress addr)
-    case ee of
-        Left e -> NS.sClose sock >> throwIO e
-        Right () -> return sock
-  where
-    try' :: IO a -> IO (Either SomeException a)
-    try' = try
+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE CPP #-}+module Network.HTTP.Conduit.ConnInfo+    ( ConnInfo+    , connClose+    , connSink+    , connSource+    , sslClientConn+    , socketConn+    , TLSCertificateRejectReason(..)+    , TLSCertificateUsage(..)+    , getSocket+#if DEBUG+    , printOpenSockets+    , requireAllSocketsClosed+    , clearSocketsList+#endif+    ) where++import Control.Exception (SomeException, throwIO, try)+import System.IO (Handle, hClose)++import Control.Monad.Base (MonadBase, liftBase)++import Data.ByteString (ByteString)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L++import Network.Socket (Socket, sClose)+import Network.Socket.ByteString (recv, sendAll)+import qualified Network.Socket as NS++import Network.TLS+import Network.TLS.Extra (ciphersuite_all)++import Data.Certificate.X509 (X509)++import Crypto.Random.AESCtr (makeSystem)++import qualified Data.Conduit as C++#if DEBUG+import qualified Data.IntMap as IntMap+import qualified Data.IORef as I+import System.IO.Unsafe (unsafePerformIO)+#endif++data ConnInfo = ConnInfo+    { connRead :: IO ByteString+    , connWrite :: ByteString -> IO ()+    , connClose :: IO ()+    }++connSink :: C.ResourceIO m => ConnInfo -> C.Sink ByteString m ()+connSink ConnInfo { connWrite = write } = C.Sink $ return $ C.SinkData+    { C.sinkPush = \bss -> liftBase (write bss) >> return C.Processing+    , C.sinkClose = return ()+    }++connSource :: C.ResourceIO m => ConnInfo -> C.Source m ByteString+connSource ConnInfo { connRead = read' } = C.Source $ return $ C.PreparedSource+    { C.sourcePull = do+        bs <- liftBase read'+        if S.null bs+            then return C.Closed+            else return $ C.Open bs+    , C.sourceClose = return ()+    }++#if DEBUG+allOpenSockets :: I.IORef (Int, IntMap.IntMap String)+allOpenSockets = unsafePerformIO $ I.newIORef (0, IntMap.empty)++addSocket :: String -> IO Int+addSocket desc = I.atomicModifyIORef allOpenSockets $ \(next, m) ->+    ((next + 1, IntMap.insert next desc m), next)++removeSocket :: Int -> IO ()+removeSocket i = I.atomicModifyIORef allOpenSockets $ \(next, m) ->+    ((next, IntMap.delete i m), ())++printOpenSockets :: IO ()+printOpenSockets = do+    (_, m) <- I.readIORef allOpenSockets+    putStrLn "\n\nOpen sockets:"+    if IntMap.null m+        then putStrLn "** No open sockets!"+        else mapM_ putStrLn $ IntMap.elems m++requireAllSocketsClosed :: IO ()+requireAllSocketsClosed = do+    (_, m) <- I.readIORef allOpenSockets+    if IntMap.null m+        then return ()+        else error $ unlines+            $ "requireAllSocketsClosed: there are open sockets"+            : IntMap.elems m++clearSocketsList :: IO ()+clearSocketsList = I.writeIORef allOpenSockets (0, IntMap.empty)+#endif++socketConn :: String -> Socket -> IO ConnInfo+socketConn _desc sock = do+#if DEBUG+    i <- addSocket _desc+#endif+    return ConnInfo+        { connRead  = recv sock 4096+        , connWrite = sendAll sock+        , connClose = do+#if DEBUG+            removeSocket i+#endif+            sClose sock+        }++sslClientConn :: String -> ([X509] -> IO TLSCertificateUsage) -> Handle -> IO ConnInfo+sslClientConn _desc onCerts h = do+#if DEBUG+    i <- addSocket _desc+#endif+    let tcp = defaultParams+            { pConnectVersion = TLS10+            , pAllowedVersions = [ TLS10, TLS11 ]+            , pCiphers = ciphersuite_all+            , onCertificatesRecv = onCerts+            }+    gen <- makeSystem+    istate <- client tcp gen h+    _ <- handshake istate+    return ConnInfo+        { connRead = recvD istate+        , connWrite = sendData istate . L.fromChunks . (:[])+        , connClose = do+#if DEBUG+            removeSocket i+#endif+            bye istate+            hClose h+        }+  where+    recvD istate = do+        x <- recvData istate+        if L.null x+            then recvD istate+            else return $ S.concat $ L.toChunks x+            -- Although a 'concat' seems like a bad idea, at+            -- least on tls-0.8.4 it's guaranteed to always+            -- return a lazy bytestring with a single chunk.++getSocket :: String -> Int -> IO NS.Socket+getSocket host' port' = do+    let hints = NS.defaultHints {+                          NS.addrFlags = [NS.AI_ADDRCONFIG]+                        , NS.addrSocketType = NS.Stream+                        }+    (addr:_) <- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')+    sock <- NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)+                      (NS.addrProtocol addr)+    ee <- try' $ NS.connect sock (NS.addrAddress addr)+    case ee of+        Left e -> NS.sClose sock >> throwIO e+        Right () -> return sock+  where+    try' :: IO a -> IO (Either SomeException a)+    try' = try
Network/HTTP/Conduit/Manager.hs view
@@ -1,316 +1,316 @@-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-module Network.HTTP.Conduit.Manager
-    ( Manager
-    , ConnKey (..)
-    , newManager
-    , newManagerCount
-    , newManagerIO
-    , getConn
-    , ConnReuse (..)
-    , withManager
-    , ConnRelease
-    , ManagedConn (..)
-    ) where
-
-import Prelude hiding (catch)
-import Control.Applicative ((<$>))
-import Data.Monoid (mappend)
-import System.IO (hClose, hFlush)
-import qualified Data.IORef as I
-import qualified Data.Map as Map
-
-import qualified Data.ByteString.Char8 as S8
-import qualified Data.ByteString.Lazy as L
-
-import qualified Blaze.ByteString.Builder as Blaze
-
-import Data.Text (Text)
-import qualified Data.Text as T
-
-import Control.Monad.Base (liftBase)
-import Control.Exception.Lifted (mask)
-import Control.Exception (mask_, SomeException, catch)
-import Control.Monad.Trans.Resource
-    ( ResourceT, runResourceT, ResourceIO, withIO
-    , register, release
-    , newRef, readRef', writeRef
-    , safeFromIOBase
-    )
-import Control.Concurrent (forkIO, threadDelay)
-import Data.Time (UTCTime, getCurrentTime, addUTCTime)
-
-import Network (connectTo, PortID (PortNumber))
-import Data.Certificate.X509 (X509)
-
-import Network.HTTP.Conduit.ConnInfo
-import Network.HTTP.Conduit.Util (hGetSome)
-import Network.HTTP.Conduit.Parser (parserHeadersFromByteString)
-import Network.HTTP.Conduit.Request
-
--- | Keeps track of open connections for keep-alive.  May be used
--- concurrently by multiple threads.
-data Manager = Manager
-    { mConns :: !(I.IORef (Maybe (Map.Map ConnKey (NonEmptyList ConnInfo))))
-    -- ^ @Nothing@ indicates that the manager is closed.
-    , mMaxConns :: !Int
-    -- ^ This is a per-@ConnKey@ value.
-    }
-
-data NonEmptyList a =
-    One !a !UTCTime |
-    Cons !a !Int !UTCTime !(NonEmptyList a)
-
--- | @ConnKey@ consists of a hostname, a port and a @Bool@
--- specifying whether to use keepalive.
-data ConnKey = ConnKey !Text !Int !Bool
-    deriving (Eq, Show, Ord)
-
-takeSocket :: Manager -> ConnKey -> IO (Maybe ConnInfo)
-takeSocket man key =
-    I.atomicModifyIORef (mConns man) go
-  where
-    go Nothing = (Nothing, Nothing)
-    go (Just m) =
-        case Map.lookup key m of
-            Nothing -> (Just m, Nothing)
-            Just (One a _) -> (Just $ Map.delete key m, Just a)
-            Just (Cons a _ _ rest) -> (Just $ Map.insert key rest m, Just a)
-
-putSocket :: Manager -> ConnKey -> ConnInfo -> IO ()
-putSocket man key ci = do
-    now <- getCurrentTime
-    msock <- I.atomicModifyIORef (mConns man) (go now)
-    maybe (return ()) connClose msock
-  where
-    go _ Nothing = (Nothing, Just ci)
-    go now (Just m) =
-        case Map.lookup key m of
-            Nothing -> (Just $ Map.insert key (One ci now) m, Nothing)
-            Just l ->
-                let (l', mx) = addToList now (mMaxConns man) ci l
-                 in (Just $ Map.insert key l' m, mx)
-
--- | Add a new element to the list, up to the given maximum number. If we're
--- already at the maximum, return the new value as leftover.
-addToList :: UTCTime -> Int -> a -> NonEmptyList a -> (NonEmptyList a, Maybe a)
-addToList _ i x l | i <= 1 = (l, Just x)
-addToList now _ x l@One{} = (Cons x 2 now l, Nothing)
-addToList now maxCount x l@(Cons _ currCount _ _)
-    | maxCount > currCount = (Cons x (currCount + 1) now l, Nothing)
-    | otherwise = (l, Just x)
-
--- | Create a new 'Manager' with no open connections and a maximum of 10 open connections..
-newManager :: ResourceIO m => ResourceT m Manager
-newManager = newManagerCount 10
-
--- | Create a new 'Manager' with the specified max connection count.
-newManagerCount :: ResourceIO m => Int -> ResourceT m Manager
-newManagerCount count = snd <$> withIO (newManagerIO count) closeManager
-
--- | Create a 'Manager' which will never be destroyed.
-newManagerIO :: Int -> IO Manager
-newManagerIO count = do
-    mapRef <- I.newIORef (Just Map.empty)
-    _ <- forkIO $ reap mapRef
-    return $ Manager mapRef count
-
--- | Collect and destroy any stale connections.
-reap :: I.IORef (Maybe (Map.Map ConnKey (NonEmptyList ConnInfo))) -> IO ()
-reap mapRef =
-    mask_ loop
-  where
-    loop = do
-        threadDelay (5 * 1000 * 1000)
-        now <- getCurrentTime
-        let isNotStale time = 30 `addUTCTime` time >= now
-        mtoDestroy <- I.atomicModifyIORef mapRef (findStaleWrap isNotStale)
-        case mtoDestroy of
-            Nothing -> return () -- manager is closed
-            Just toDestroy -> do
-                mapM_ safeConnClose toDestroy
-                loop
-    findStaleWrap _ Nothing = (Nothing, Nothing)
-    findStaleWrap isNotStale (Just m) =
-        let (x, y) = findStale isNotStale m
-         in (Just x, Just y)
-    findStale isNotStale =
-        findStale' id id . Map.toList
-      where
-        findStale' destroy keep [] = (Map.fromList $ keep [], destroy [])
-        findStale' destroy keep ((connkey, nelist):rest) =
-            findStale' destroy' keep' rest
-          where
-            -- Note: By definition, the timestamps must be in descending order,
-            -- so we don't need to traverse the whole list.
-            (notStale, stale) = span (isNotStale . fst) $ neToList nelist
-            destroy' = destroy . (map snd stale++)
-            keep' =
-                case neFromList notStale of
-                    Nothing -> keep
-                    Just x -> keep . ((connkey, x):)
-
-neToList :: NonEmptyList a -> [(UTCTime, a)]
-neToList (One a t) = [(t, a)]
-neToList (Cons a _ t nelist) = (t, a) : neToList nelist
-
-neFromList :: [(UTCTime, a)] -> Maybe (NonEmptyList a)
-neFromList [] = Nothing
-neFromList [(t, a)] = Just (One a t)
-neFromList xs =
-    Just . snd . go $ xs
-  where
-    go [] = error "neFromList.go []"
-    go [(t, a)] = (2, One a t)
-    go ((t, a):rest) =
-        let (i, rest') = go rest
-            i' = i + 1
-         in i' `seq` (i', Cons a i t rest')
-
-withManager :: ResourceIO m => (Manager -> ResourceT m a) -> m a
-withManager f = runResourceT $ newManager >>= f
-
--- | Close all connections in a 'Manager'. Afterwards, the
--- 'Manager' can be reused if desired.
-closeManager :: Manager -> IO ()
-closeManager manager = mask_ $ do
-    m <- I.atomicModifyIORef (mConns manager) $ \x -> (Nothing, x)
-    mapM_ (nonEmptyMapM_ safeConnClose) $ maybe [] Map.elems m
-
-safeConnClose :: ConnInfo -> IO ()
-safeConnClose ci = connClose ci `catch` \(_::SomeException) -> return ()
-
-nonEmptyMapM_ :: Monad m => (a -> m ()) -> NonEmptyList a -> m ()
-nonEmptyMapM_ f (One x _) = f x
-nonEmptyMapM_ f (Cons x _ _ l) = f x >> nonEmptyMapM_ f l
-
-getSocketConn
-    :: ResourceIO m
-    => Manager
-    -> String
-    -> Int
-    -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)
-getSocketConn man host' port' =
-    getManagedConn man (ConnKey (T.pack host') port' False) $
-        getSocket host' port' >>= socketConn desc
-  where
-    desc = socketDesc host' port' "unsecured"
-
-socketDesc :: String -> Int -> String -> String
-socketDesc h p t = unwords [h, show p, t]
-
-getSslConn :: ResourceIO m
-            => ([X509] -> IO TLSCertificateUsage)
-            -> Manager
-            -> String -- ^ host
-            -> Int -- ^ port
-            -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)
-getSslConn checkCert man host' port' =
-    getManagedConn man (ConnKey (T.pack host') port' True) $
-        (connectTo host' (PortNumber $ fromIntegral port') >>= sslClientConn desc checkCert)
-  where
-    desc = socketDesc host' port' "secured"
-
-getSslProxyConn
-            :: ResourceIO m
-            => ([X509] -> IO TLSCertificateUsage)
-            -> S8.ByteString -- ^ Target host
-            -> Int -- ^ Target port
-            -> Manager
-            -> String -- ^ Proxy host
-            -> Int -- ^ Proxy port
-            -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)
-getSslProxyConn checkCert thost tport man phost pport =
-    getManagedConn man (ConnKey (T.pack phost) pport True) $
-        doConnect >>= sslClientConn desc checkCert
-  where
-    desc = socketDesc phost pport "secured-proxy"
-    doConnect = do
-        h <- connectTo phost (PortNumber $ fromIntegral pport)
-        L.hPutStr h $ Blaze.toLazyByteString connectRequest
-        hFlush h
-        r <- hGetSome h 2048
-        res <- parserHeadersFromByteString r
-        case res of
-            Right ((_, 200, _), _) -> return h
-            Right ((_, _, msg), _) -> hClose h >> proxyError (S8.unpack msg)
-            Left s -> hClose h >> proxyError s
-
-    connectRequest =
-        Blaze.fromByteString "CONNECT "
-            `mappend` Blaze.fromByteString thost
-            `mappend` Blaze.fromByteString (S8.pack (':' : show tport))
-            `mappend` Blaze.fromByteString " HTTP/1.1\r\n\r\n"
-    proxyError s =
-        error $ "Proxy failed to CONNECT to '"
-                ++ S8.unpack thost ++ ":" ++ show tport ++ "' : " ++ s
-
-data ManagedConn = Fresh | Reused
-
--- | This function needs to acquire a @ConnInfo@- either from the @Manager@ or
--- via I\/O, and register it with the @ResourceT@ so it is guaranteed to be
--- either released or returned to the manager.
-getManagedConn
-    :: ResourceIO m
-    => Manager
-    -> ConnKey
-    -> IO ConnInfo
-    -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)
--- We want to avoid any holes caused by async exceptions, so let's mask.
-getManagedConn man key open = mask $ \restore -> do
-    -- Try to take the socket out of the manager.
-    mci <- liftBase $ takeSocket man key
-    (ci, isManaged) <-
-        case mci of
-            -- There wasn't a matching connection in the manager, so create a
-            -- new one.
-            Nothing -> do
-                ci <- restore $ liftBase open
-                return (ci, Fresh)
-            -- Return the existing one
-            Just ci -> return (ci, Reused)
-
-    -- When we release this connection, we can either reuse it (put it back in
-    -- the manager) or not reuse it (close the socket). We set up a mutable
-    -- reference to track what we want to do. By default, we say not to reuse
-    -- it, that way if an exception is thrown, the connection won't be reused.
-    toReuseRef <- newRef DontReuse
-
-    -- Now register our release action.
-    releaseKey <- register $ do
-        toReuse <- readRef' toReuseRef
-        -- Determine what action to take based on the value stored in the
-        -- toReuseRef variable.
-        case toReuse of
-            Reuse -> safeFromIOBase $ putSocket man key ci
-            DontReuse -> safeFromIOBase $ connClose ci
-
-    -- When the connection is explicitly released, we update our toReuseRef to
-    -- indicate what action should be taken, and then call release.
-    let connRelease x = do
-            writeRef toReuseRef x
-            release releaseKey
-    return (connRelease, ci, isManaged)
-
-data ConnReuse = Reuse | DontReuse
-
-type ConnRelease m = ConnReuse -> ResourceT m ()
-
-getConn :: ResourceIO m
-        => Request m
-        -> Manager
-        -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)
-getConn req m =
-    go m connhost connport
-  where
-    h = host req
-    (useProxy, connhost, connport) =
-        case proxy req of
-            Just p -> (True, S8.unpack (proxyHost p), proxyPort p)
-            Nothing -> (False, S8.unpack h, port req)
-    go =
-        case (secure req, useProxy) of
-            (False, _) -> getSocketConn
-            (True, False) -> getSslConn $ checkCerts req h
-            (True, True) -> getSslProxyConn (checkCerts req h) h (port req)
+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Network.HTTP.Conduit.Manager+    ( Manager+    , ConnKey (..)+    , newManager+    , newManagerCount+    , newManagerIO+    , getConn+    , ConnReuse (..)+    , withManager+    , ConnRelease+    , ManagedConn (..)+    ) where++import Prelude hiding (catch)+import Control.Applicative ((<$>))+import Data.Monoid (mappend)+import System.IO (hClose, hFlush)+import qualified Data.IORef as I+import qualified Data.Map as Map++import qualified Data.ByteString.Char8 as S8+import qualified Data.ByteString.Lazy as L++import qualified Blaze.ByteString.Builder as Blaze++import Data.Text (Text)+import qualified Data.Text as T++import Control.Monad.Base (liftBase)+import Control.Exception.Lifted (mask)+import Control.Exception (mask_, SomeException, catch)+import Control.Monad.Trans.Resource+    ( ResourceT, runResourceT, ResourceIO, withIO+    , register, release+    , newRef, readRef', writeRef+    , safeFromIOBase+    )+import Control.Concurrent (forkIO, threadDelay)+import Data.Time (UTCTime, getCurrentTime, addUTCTime)++import Network (connectTo, PortID (PortNumber))+import Data.Certificate.X509 (X509)++import Network.HTTP.Conduit.ConnInfo+import Network.HTTP.Conduit.Util (hGetSome)+import Network.HTTP.Conduit.Parser (parserHeadersFromByteString)+import Network.HTTP.Conduit.Request++-- | Keeps track of open connections for keep-alive.  May be used+-- concurrently by multiple threads.+data Manager = Manager+    { mConns :: !(I.IORef (Maybe (Map.Map ConnKey (NonEmptyList ConnInfo))))+    -- ^ @Nothing@ indicates that the manager is closed.+    , mMaxConns :: !Int+    -- ^ This is a per-@ConnKey@ value.+    }++data NonEmptyList a =+    One !a !UTCTime |+    Cons !a !Int !UTCTime !(NonEmptyList a)++-- | @ConnKey@ consists of a hostname, a port and a @Bool@+-- specifying whether to use keepalive.+data ConnKey = ConnKey !Text !Int !Bool+    deriving (Eq, Show, Ord)++takeSocket :: Manager -> ConnKey -> IO (Maybe ConnInfo)+takeSocket man key =+    I.atomicModifyIORef (mConns man) go+  where+    go Nothing = (Nothing, Nothing)+    go (Just m) =+        case Map.lookup key m of+            Nothing -> (Just m, Nothing)+            Just (One a _) -> (Just $ Map.delete key m, Just a)+            Just (Cons a _ _ rest) -> (Just $ Map.insert key rest m, Just a)++putSocket :: Manager -> ConnKey -> ConnInfo -> IO ()+putSocket man key ci = do+    now <- getCurrentTime+    msock <- I.atomicModifyIORef (mConns man) (go now)+    maybe (return ()) connClose msock+  where+    go _ Nothing = (Nothing, Just ci)+    go now (Just m) =+        case Map.lookup key m of+            Nothing -> (Just $ Map.insert key (One ci now) m, Nothing)+            Just l ->+                let (l', mx) = addToList now (mMaxConns man) ci l+                 in (Just $ Map.insert key l' m, mx)++-- | Add a new element to the list, up to the given maximum number. If we're+-- already at the maximum, return the new value as leftover.+addToList :: UTCTime -> Int -> a -> NonEmptyList a -> (NonEmptyList a, Maybe a)+addToList _ i x l | i <= 1 = (l, Just x)+addToList now _ x l@One{} = (Cons x 2 now l, Nothing)+addToList now maxCount x l@(Cons _ currCount _ _)+    | maxCount > currCount = (Cons x (currCount + 1) now l, Nothing)+    | otherwise = (l, Just x)++-- | Create a new 'Manager' with no open connections and a maximum of 10 open connections..+newManager :: ResourceIO m => ResourceT m Manager+newManager = newManagerCount 10++-- | Create a new 'Manager' with the specified max connection count.+newManagerCount :: ResourceIO m => Int -> ResourceT m Manager+newManagerCount count = snd <$> withIO (newManagerIO count) closeManager++-- | Create a 'Manager' which will never be destroyed.+newManagerIO :: Int -> IO Manager+newManagerIO count = do+    mapRef <- I.newIORef (Just Map.empty)+    _ <- forkIO $ reap mapRef+    return $ Manager mapRef count++-- | Collect and destroy any stale connections.+reap :: I.IORef (Maybe (Map.Map ConnKey (NonEmptyList ConnInfo))) -> IO ()+reap mapRef =+    mask_ loop+  where+    loop = do+        threadDelay (5 * 1000 * 1000)+        now <- getCurrentTime+        let isNotStale time = 30 `addUTCTime` time >= now+        mtoDestroy <- I.atomicModifyIORef mapRef (findStaleWrap isNotStale)+        case mtoDestroy of+            Nothing -> return () -- manager is closed+            Just toDestroy -> do+                mapM_ safeConnClose toDestroy+                loop+    findStaleWrap _ Nothing = (Nothing, Nothing)+    findStaleWrap isNotStale (Just m) =+        let (x, y) = findStale isNotStale m+         in (Just x, Just y)+    findStale isNotStale =+        findStale' id id . Map.toList+      where+        findStale' destroy keep [] = (Map.fromList $ keep [], destroy [])+        findStale' destroy keep ((connkey, nelist):rest) =+            findStale' destroy' keep' rest+          where+            -- Note: By definition, the timestamps must be in descending order,+            -- so we don't need to traverse the whole list.+            (notStale, stale) = span (isNotStale . fst) $ neToList nelist+            destroy' = destroy . (map snd stale++)+            keep' =+                case neFromList notStale of+                    Nothing -> keep+                    Just x -> keep . ((connkey, x):)++neToList :: NonEmptyList a -> [(UTCTime, a)]+neToList (One a t) = [(t, a)]+neToList (Cons a _ t nelist) = (t, a) : neToList nelist++neFromList :: [(UTCTime, a)] -> Maybe (NonEmptyList a)+neFromList [] = Nothing+neFromList [(t, a)] = Just (One a t)+neFromList xs =+    Just . snd . go $ xs+  where+    go [] = error "neFromList.go []"+    go [(t, a)] = (2, One a t)+    go ((t, a):rest) =+        let (i, rest') = go rest+            i' = i + 1+         in i' `seq` (i', Cons a i t rest')++withManager :: ResourceIO m => (Manager -> ResourceT m a) -> m a+withManager f = runResourceT $ newManager >>= f++-- | Close all connections in a 'Manager'. Afterwards, the+-- 'Manager' can be reused if desired.+closeManager :: Manager -> IO ()+closeManager manager = mask_ $ do+    m <- I.atomicModifyIORef (mConns manager) $ \x -> (Nothing, x)+    mapM_ (nonEmptyMapM_ safeConnClose) $ maybe [] Map.elems m++safeConnClose :: ConnInfo -> IO ()+safeConnClose ci = connClose ci `catch` \(_::SomeException) -> return ()++nonEmptyMapM_ :: Monad m => (a -> m ()) -> NonEmptyList a -> m ()+nonEmptyMapM_ f (One x _) = f x+nonEmptyMapM_ f (Cons x _ _ l) = f x >> nonEmptyMapM_ f l++getSocketConn+    :: ResourceIO m+    => Manager+    -> String+    -> Int+    -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)+getSocketConn man host' port' =+    getManagedConn man (ConnKey (T.pack host') port' False) $+        getSocket host' port' >>= socketConn desc+  where+    desc = socketDesc host' port' "unsecured"++socketDesc :: String -> Int -> String -> String+socketDesc h p t = unwords [h, show p, t]++getSslConn :: ResourceIO m+            => ([X509] -> IO TLSCertificateUsage)+            -> Manager+            -> String -- ^ host+            -> Int -- ^ port+            -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)+getSslConn checkCert man host' port' =+    getManagedConn man (ConnKey (T.pack host') port' True) $+        (connectTo host' (PortNumber $ fromIntegral port') >>= sslClientConn desc checkCert)+  where+    desc = socketDesc host' port' "secured"++getSslProxyConn+            :: ResourceIO m+            => ([X509] -> IO TLSCertificateUsage)+            -> S8.ByteString -- ^ Target host+            -> Int -- ^ Target port+            -> Manager+            -> String -- ^ Proxy host+            -> Int -- ^ Proxy port+            -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)+getSslProxyConn checkCert thost tport man phost pport =+    getManagedConn man (ConnKey (T.pack phost) pport True) $+        doConnect >>= sslClientConn desc checkCert+  where+    desc = socketDesc phost pport "secured-proxy"+    doConnect = do+        h <- connectTo phost (PortNumber $ fromIntegral pport)+        L.hPutStr h $ Blaze.toLazyByteString connectRequest+        hFlush h+        r <- hGetSome h 2048+        res <- parserHeadersFromByteString r+        case res of+            Right ((_, 200, _), _) -> return h+            Right ((_, _, msg), _) -> hClose h >> proxyError (S8.unpack msg)+            Left s -> hClose h >> proxyError s++    connectRequest =+        Blaze.fromByteString "CONNECT "+            `mappend` Blaze.fromByteString thost+            `mappend` Blaze.fromByteString (S8.pack (':' : show tport))+            `mappend` Blaze.fromByteString " HTTP/1.1\r\n\r\n"+    proxyError s =+        error $ "Proxy failed to CONNECT to '"+                ++ S8.unpack thost ++ ":" ++ show tport ++ "' : " ++ s++data ManagedConn = Fresh | Reused++-- | This function needs to acquire a @ConnInfo@- either from the @Manager@ or+-- via I\/O, and register it with the @ResourceT@ so it is guaranteed to be+-- either released or returned to the manager.+getManagedConn+    :: ResourceIO m+    => Manager+    -> ConnKey+    -> IO ConnInfo+    -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)+-- We want to avoid any holes caused by async exceptions, so let's mask.+getManagedConn man key open = mask $ \restore -> do+    -- Try to take the socket out of the manager.+    mci <- liftBase $ takeSocket man key+    (ci, isManaged) <-+        case mci of+            -- There wasn't a matching connection in the manager, so create a+            -- new one.+            Nothing -> do+                ci <- restore $ liftBase open+                return (ci, Fresh)+            -- Return the existing one+            Just ci -> return (ci, Reused)++    -- When we release this connection, we can either reuse it (put it back in+    -- the manager) or not reuse it (close the socket). We set up a mutable+    -- reference to track what we want to do. By default, we say not to reuse+    -- it, that way if an exception is thrown, the connection won't be reused.+    toReuseRef <- newRef DontReuse++    -- Now register our release action.+    releaseKey <- register $ do+        toReuse <- readRef' toReuseRef+        -- Determine what action to take based on the value stored in the+        -- toReuseRef variable.+        case toReuse of+            Reuse -> safeFromIOBase $ putSocket man key ci+            DontReuse -> safeFromIOBase $ connClose ci++    -- When the connection is explicitly released, we update our toReuseRef to+    -- indicate what action should be taken, and then call release.+    let connRelease x = do+            writeRef toReuseRef x+            release releaseKey+    return (connRelease, ci, isManaged)++data ConnReuse = Reuse | DontReuse++type ConnRelease m = ConnReuse -> ResourceT m ()++getConn :: ResourceIO m+        => Request m+        -> Manager+        -> ResourceT m (ConnRelease m, ConnInfo, ManagedConn)+getConn req m =+    go m connhost connport+  where+    h = host req+    (useProxy, connhost, connport) =+        case proxy req of+            Just p -> (True, S8.unpack (proxyHost p), proxyPort p)+            Nothing -> (False, S8.unpack h, port req)+    go =+        case (secure req, useProxy) of+            (False, _) -> getSocketConn+            (True, False) -> getSslConn $ checkCerts req h+            (True, True) -> getSslProxyConn (checkCerts req h) h (port req)
Network/HTTP/Conduit/Parser.hs view
@@ -1,122 +1,122 @@-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts #-}
-module Network.HTTP.Conduit.Parser
-    ( sinkHeaders
-    , newline
-    , parserHeadersFromByteString
-    , parseChunkHeader
-    ) where
-
-import Prelude hiding (take, takeWhile)
-import Control.Applicative
-import Data.Word (Word8)
-
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Char8 as S8
-
-import Data.Attoparsec
-
-import Data.Conduit.Attoparsec (sinkParser)
-import Data.Conduit (Sink, ResourceIO)
-import Control.Monad (when)
-
-
-type Header = (S.ByteString, S.ByteString)
-
-parseHeader :: Parser Header
-parseHeader = do
-    k <- takeWhile1 notNewlineColon
-    _ <- word8 58 -- colon
-    skipWhile isSpace
-    v <- takeWhile notNewline
-    newline
-    return (k, v)
-
-notNewlineColon, isSpace, notNewline :: Word8 -> Bool
-
-notNewlineColon 10 = False -- LF
-notNewlineColon 13 = False -- CR
-notNewlineColon 58 = False -- colon
-notNewlineColon _  = True
-
-isSpace 32 = True
-isSpace _  = False
-
-notNewline 10 = False
-notNewline 13 = False
-notNewline _  = True
-
-newline :: Parser ()
-newline =
-    lf <|> (cr >> lf)
-  where
-    word8' x = word8 x >> return ()
-    lf = word8' 10
-    cr = word8' 13
-
-parseHeaders :: Parser (Status, [Header])
-parseHeaders = do
-    s <- parseStatus <?> "HTTP status line"
-    h <- manyTill parseHeader newline <?> "Response headers"
-    return (s, h)
-
-sinkHeaders :: ResourceIO m => Sink S.ByteString m (Status, [Header])
-sinkHeaders = sinkParser parseHeaders
-
-
-parserHeadersFromByteString :: Monad m => S.ByteString -> m (Either String (Status, [Header]))
-parserHeadersFromByteString s = return $ parseOnly parseHeaders s
-
-
-type Status = (S.ByteString, Int, S.ByteString)
-
-parseStatus :: Parser Status
-parseStatus = do
-    end <- atEnd
-    when end $ fail "EOF reached"
-    _ <- manyTill (take 1 >> return ()) (try $ string "HTTP/") <?> "HTTP/"
-    ver <- takeWhile1 $ not . isSpace
-    _ <- word8 32 -- space
-    statCode <- takeWhile1 $ not . isSpace
-    statCode' <-
-        case reads $ S8.unpack statCode of
-            [] -> fail $ "Invalid status code: " ++ S8.unpack statCode
-            (x, _):_ -> return x
-    _ <- word8 32
-    statMsg <- takeWhile1 $ notNewline
-    newline
-    if (statCode == "100")
-        then newline >> parseStatus
-        else return (ver, statCode', statMsg)
-
-parseChunkHeader :: Parser Int
-parseChunkHeader = do
-    len <- hexs
-    skipWhile isSpace
-    newline <|> attribs
-    return len
-
-attribs :: Parser ()
-attribs = do
-    _ <- word8 59 -- colon
-    skipWhile notNewline
-    newline
-
-hexs :: Parser Int
-hexs = do
-    ws <- many1 hex
-    return $ foldl1 (\a b -> a * 16 + b) $ map fromIntegral ws
-
-hex :: Parser Word8
-hex =
-    (digit <|> upper <|> lower) <?> "Hexadecimal digit"
-  where
-    digit = do
-        d <- satisfy $ \w -> (w >= 48 && w <= 57)
-        return $ d - 48
-    upper = do
-        d <- satisfy $ \w -> (w >= 65 && w <= 70)
-        return $ d - 55
-    lower = do
-        d <- satisfy $ \w -> (w >= 97 && w <= 102)
-        return $ d - 87
+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+module Network.HTTP.Conduit.Parser+    ( sinkHeaders+    , newline+    , parserHeadersFromByteString+    , parseChunkHeader+    ) where++import Prelude hiding (take, takeWhile)+import Control.Applicative+import Data.Word (Word8)++import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8++import Data.Attoparsec++import Data.Conduit.Attoparsec (sinkParser)+import Data.Conduit (Sink, ResourceIO)+import Control.Monad (when)+++type Header = (S.ByteString, S.ByteString)++parseHeader :: Parser Header+parseHeader = do+    k <- takeWhile1 notNewlineColon+    _ <- word8 58 -- colon+    skipWhile isSpace+    v <- takeWhile notNewline+    newline+    return (k, v)++notNewlineColon, isSpace, notNewline :: Word8 -> Bool++notNewlineColon 10 = False -- LF+notNewlineColon 13 = False -- CR+notNewlineColon 58 = False -- colon+notNewlineColon _  = True++isSpace 32 = True+isSpace _  = False++notNewline 10 = False+notNewline 13 = False+notNewline _  = True++newline :: Parser ()+newline =+    lf <|> (cr >> lf)+  where+    word8' x = word8 x >> return ()+    lf = word8' 10+    cr = word8' 13++parseHeaders :: Parser (Status, [Header])+parseHeaders = do+    s <- parseStatus <?> "HTTP status line"+    h <- manyTill parseHeader newline <?> "Response headers"+    return (s, h)++sinkHeaders :: ResourceIO m => Sink S.ByteString m (Status, [Header])+sinkHeaders = sinkParser parseHeaders+++parserHeadersFromByteString :: Monad m => S.ByteString -> m (Either String (Status, [Header]))+parserHeadersFromByteString s = return $ parseOnly parseHeaders s+++type Status = (S.ByteString, Int, S.ByteString)++parseStatus :: Parser Status+parseStatus = do+    end <- atEnd+    when end $ fail "EOF reached"+    _ <- manyTill (take 1 >> return ()) (try $ string "HTTP/") <?> "HTTP/"+    ver <- takeWhile1 $ not . isSpace+    _ <- word8 32 -- space+    statCode <- takeWhile1 $ not . isSpace+    statCode' <-+        case reads $ S8.unpack statCode of+            [] -> fail $ "Invalid status code: " ++ S8.unpack statCode+            (x, _):_ -> return x+    _ <- word8 32+    statMsg <- takeWhile1 $ notNewline+    newline+    if (statCode == "100")+        then newline >> parseStatus+        else return (ver, statCode', statMsg)++parseChunkHeader :: Parser Int+parseChunkHeader = do+    len <- hexs+    skipWhile isSpace+    newline <|> attribs+    return len++attribs :: Parser ()+attribs = do+    _ <- word8 59 -- colon+    skipWhile notNewline+    newline++hexs :: Parser Int+hexs = do+    ws <- many1 hex+    return $ foldl1 (\a b -> a * 16 + b) $ map fromIntegral ws++hex :: Parser Word8+hex =+    (digit <|> upper <|> lower) <?> "Hexadecimal digit"+  where+    digit = do+        d <- satisfy $ \w -> (w >= 48 && w <= 57)+        return $ d - 48+    upper = do+        d <- satisfy $ \w -> (w >= 65 && w <= 70)+        return $ d - 55+    lower = do+        d <- satisfy $ \w -> (w >= 97 && w <= 102)+        return $ d - 87
Network/HTTP/Conduit/Request.hs view
@@ -1,344 +1,344 @@-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-module Network.HTTP.Conduit.Request
-    ( Request (..)
-    , RequestBody (..)
-    , ContentType
-    , Proxy (..)
-    , parseUrl
-    , browserDecompress
-    , HttpException (..)
-    , defaultCheckCerts
-    , alwaysDecompress
-    , addProxy
-    , applyBasicAuth
-    , urlEncodedBody
-    , needsGunzip
-    , requestBuilder
-    ) where
-
-import Data.Int (Int64)
-import Data.Maybe (fromMaybe)
-import Data.Monoid (mempty, mappend)
-import Data.Typeable (Typeable)
-
-import Data.Default (Default (def))
-
-import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString)
-import Blaze.ByteString.Builder.Char8 (fromChar)
-import qualified Blaze.ByteString.Builder as Blaze
-
-import qualified Data.Conduit as C
-import qualified Data.Conduit.List as CL
-
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Char8 as S8
-import qualified Data.ByteString.Lazy as L
-
-import qualified Network.HTTP.Types as W
-import Data.Certificate.X509 (X509)
-
-import Network.TLS (TLSCertificateUsage (CertificateUsageAccept))
-import Network.TLS.Extra (certificateVerifyChain, certificateVerifyDomain)
-
-import Control.Exception (Exception, SomeException, toException)
-import Control.Failure (Failure (failure))
-import Codec.Binary.UTF8.String (encodeString)
-import qualified Data.CaseInsensitive as CI
-import qualified Data.ByteString.Base64 as B64
-
-import Network.HTTP.Conduit.Chunk (chunkIt)
-import Network.HTTP.Conduit.Util (readDec, (<>))
-
-type ContentType = S.ByteString
-
--- | All information on how to connect to a host and what should be sent in the
--- HTTP request.
---
--- If you simply wish to download from a URL, see 'parseUrl'.
---
--- The constructor for this data type is not exposed. Instead, you should use
--- either the 'def' method to retrieve a default instance, or 'parseUrl' to
--- construct from a URL, and then use the records below to make modifications.
--- This approach allows http-conduit to add configuration options without
--- breaking backwards compatibility.
-data Request m = Request
-    { method :: W.Method
-    -- ^ HTTP request method, eg GET, POST.
-    , secure :: Bool
-    -- ^ Whether to use HTTPS (ie, SSL).
-    , checkCerts :: W.Ascii -> [X509] -> IO TLSCertificateUsage
-    -- ^ Check if the server certificate is valid. Only relevant for HTTPS.
-    , host :: W.Ascii
-    , port :: Int
-    , path :: W.Ascii
-    -- ^ Everything from the host to the query string.
-    , queryString :: W.Ascii
-    , requestHeaders :: W.RequestHeaders
-    , requestBody :: RequestBody m
-    , proxy :: Maybe Proxy
-    -- ^ Optional HTTP proxy.
-    , rawBody :: Bool
-    -- ^ If @True@, a chunked and\/or gzipped body will not be
-    -- decoded. Use with caution.
-    , decompress :: ContentType -> Bool
-    -- ^ Predicate to specify whether gzipped data should be
-    -- decompressed on the fly (see 'alwaysDecompress' and
-    -- 'browserDecompress'). Default: browserDecompress.
-    , redirectCount :: Int
-    -- ^ How many redirects to follow when getting a resource. 0 means follow
-    -- no redirects. Default value: 10.
-    , checkStatus :: W.Status -> W.ResponseHeaders -> Maybe SomeException
-    -- ^ Check the status code. Note that this will run after all redirects are
-    -- performed. Default: return a @StatusCodeException@ on non-2XX responses.
-    }
-
--- | When using one of the
--- 'RequestBodySource'\/'RequestBodySourceChunked' constructors
--- and any function which calls 'redirectIter', you must ensure
--- that the 'Source' can be called multiple times.  Usually this
--- is not a problem.
---
--- The 'RequestBodySourceChunked' will send a chunked request
--- body, note that not all servers support this. Only use
--- 'RequestBodySourceChunked' if you know the server you're
--- sending to supports chunked request bodies.
-data RequestBody m
-    = RequestBodyLBS L.ByteString
-    | RequestBodyBS S.ByteString
-    | RequestBodyBuilder Int64 Blaze.Builder
-    | RequestBodySource Int64 (C.Source m Blaze.Builder)
-    | RequestBodySourceChunked (C.Source m Blaze.Builder)
-
--- | Define a HTTP proxy, consisting of a hostname and port number.
-
-data Proxy = Proxy
-    { proxyHost :: W.Ascii -- ^ The host name of the HTTP proxy.
-    , proxyPort :: Int -- ^ The port number of the HTTP proxy.
-    }
-
-encodeUrlCharPI :: Char -> String
-encodeUrlCharPI '/' = "/"
-encodeUrlCharPI c = encodeUrlChar c
-
-encodeUrlChar :: Char -> String
-encodeUrlChar c
-    -- List of unreserved characters per RFC 3986
-    -- Gleaned from http://en.wikipedia.org/wiki/Percent-encoding
-    | 'A' <= c && c <= 'Z' = [c]
-    | 'a' <= c && c <= 'z' = [c]
-    | '0' <= c && c <= '9' = [c]
-encodeUrlChar c@'-' = [c]
-encodeUrlChar c@'_' = [c]
-encodeUrlChar c@'.' = [c]
-encodeUrlChar c@'~' = [c]
-encodeUrlChar y =
-    let (a, c) = fromEnum y `divMod` 16
-        b = a `mod` 16
-        showHex' x
-            | x < 10 = toEnum $ x + (fromEnum '0')
-            | x < 16 = toEnum $ x - 10 + (fromEnum 'A')
-            | otherwise = error $ "Invalid argument to showHex: " ++ show x
-     in ['%', showHex' b, showHex' c]
-
--- | Convert a URL into a 'Request'.
---
--- This defaults some of the values in 'Request', such as setting 'method' to
--- GET and 'requestHeaders' to @[]@.
---
--- Since this function uses 'Failure', the return monad can be anything that is
--- an instance of 'Failure', such as 'IO' or 'Maybe'.
-parseUrl :: Failure HttpException m => String -> m (Request m')
-parseUrl s@('h':'t':'t':'p':':':'/':'/':rest) = parseUrl1 s False rest
-parseUrl s@('h':'t':'t':'p':'s':':':'/':'/':rest) = parseUrl1 s True rest
-parseUrl x = failure $ InvalidUrlException x "Invalid scheme"
-
-parseUrl1 :: Failure HttpException m
-          => String -> Bool -> String -> m (Request m')
-parseUrl1 full sec s =
-    parseUrl2 full sec s'
-  where
-    s' = encodeString s
-
-defaultCheckCerts :: W.Ascii -> [X509] -> IO TLSCertificateUsage
-defaultCheckCerts host' certs =
-    case certificateVerifyDomain (S8.unpack host') certs of
-        CertificateUsageAccept -> certificateVerifyChain certs
-        _                          -> return CertificateUsageAccept
-
-instance Default (Request m) where
-    def = Request
-        { host = "localhost"
-        , port = 80
-        , secure = False
-        , checkCerts = defaultCheckCerts
-        , requestHeaders = []
-        , path = "/"
-        , queryString = S8.empty
-        , requestBody = RequestBodyLBS L.empty
-        , method = "GET"
-        , proxy = Nothing
-        , rawBody = False
-        , decompress = browserDecompress
-        , redirectCount = 10
-        , checkStatus = \s@(W.Status sci _) hs ->
-            if 200 <= sci && sci < 300
-                then Nothing
-                else Just $ toException $ StatusCodeException s hs
-        }
-
-parseUrl2 :: Failure HttpException m
-          => String -> Bool -> String -> m (Request m')
-parseUrl2 full sec s = do
-    port' <- mport
-    return def
-        { host = S8.pack hostname
-        , port = port'
-        , secure = sec
-        , path = S8.pack
-                    $ if null path''
-                            then "/"
-                            else concatMap encodeUrlCharPI path''
-        , queryString = S8.pack qstring
-        }
-  where
-    (beforeSlash, afterSlash) = break (== '/') s
-    (hostname, portStr) = break (== ':') beforeSlash
-    (path', qstring') = break (== '?') afterSlash
-    path'' = path'
-    qstring'' = case qstring' of
-                '?':x -> x
-                _ -> qstring'
-    qstring = takeWhile (/= '#') qstring''
-    mport =
-        case (portStr, sec) of
-            ("", False) -> return 80
-            ("", True) -> return 443
-            (':':rest, _) -> maybe
-                (failure $ InvalidUrlException full "Invalid port")
-                return
-                (readDec rest)
-            x -> error $ "parseUrl1: this should never happen: " ++ show x
-
-data HttpException = StatusCodeException W.Status W.ResponseHeaders
-                   | InvalidUrlException String String
-                   | TooManyRedirects
-                   | HttpParserException String
-    deriving (Show, Typeable)
-instance Exception HttpException
-
--- | Always decompress a compressed stream.
-alwaysDecompress :: ContentType -> Bool
-alwaysDecompress = const True
-
--- | Decompress a compressed stream unless the content-type is 'application/x-tar'.
-browserDecompress :: ContentType -> Bool
-browserDecompress = (/= "application/x-tar")
-
--- | Add a Basic Auth header (with the specified user name and password) to the
--- given Request. Ignore error handling:
---
---    applyBasicAuth "user" "pass" $ fromJust $ parseUrl url
-
-applyBasicAuth :: S.ByteString -> S.ByteString -> Request m -> Request m
-applyBasicAuth user passwd req =
-    req { requestHeaders = authHeader : requestHeaders req }
-  where
-    authHeader = (CI.mk "Authorization", basic)
-    basic = S8.append "Basic " (B64.encode $ S8.concat [ user, ":", passwd ])
-
-
--- | Add a proxy to the the Request so that the Request when executed will use
--- the provided proxy.
-addProxy :: S.ByteString -> Int -> Request m -> Request m
-addProxy hst prt req =
-    req { proxy = Just $ Proxy hst prt }
-
--- FIXME add a helper for generating POST bodies
-
--- | Add url-encoded paramters to the 'Request'.
---
--- This sets a new 'requestBody', adds a content-type request header and
--- changes the 'method' to POST.
-urlEncodedBody :: Monad m => [(S.ByteString, S.ByteString)] -> Request m' -> Request m
-urlEncodedBody headers req = req
-    { requestBody = RequestBodyLBS body
-    , method = "POST"
-    , requestHeaders =
-        (ct, "application/x-www-form-urlencoded")
-      : filter (\(x, _) -> x /= ct) (requestHeaders req)
-    }
-  where
-    ct = "Content-Type"
-    body = L.fromChunks . return $ W.renderSimpleQuery False headers
-
-needsGunzip :: Request m
-            -> [W.Header] -- ^ response headers
-            -> Bool
-needsGunzip req hs' =
-        not (rawBody req)
-     && ("content-encoding", "gzip") `elem` hs'
-     && decompress req (fromMaybe "" $ lookup "content-type" hs')
-
-requestBuilder
-    :: C.Resource m
-    => Request m
-    -> C.Source m Builder
-requestBuilder req =
-    CL.sourceList [builder] `mappend` bodySource
-  where
-    sourceSingle = CL.sourceList . return
-
-    (contentLength, bodySource) =
-        case requestBody req of
-            RequestBodyLBS lbs -> (Just $ L.length lbs, sourceSingle $ fromLazyByteString lbs)
-            RequestBodyBS bs -> (Just $ fromIntegral $ S.length bs, sourceSingle $ fromByteString bs)
-            RequestBodyBuilder i b -> (Just $ i, sourceSingle b)
-            RequestBodySource i source -> (Just i, source)
-            RequestBodySourceChunked source -> (Nothing, source C.$= chunkIt)
-
-    hh
-        | port req == 80 && not (secure req) = host req
-        | port req == 443 && secure req = host req
-        | otherwise = host req <> S8.pack (':' : show (port req))
-
-    contentLengthHeader (Just contentLength') =
-            if method req `elem` ["GET", "HEAD"] && contentLength' == 0
-                then id
-                else (:) ("Content-Length", S8.pack $ show contentLength')
-    contentLengthHeader Nothing = (:) ("Transfer-Encoding", "chunked")
-
-    headerPairs :: W.RequestHeaders
-    headerPairs
-        = ("Host", hh)
-        : ("Accept-Encoding", "gzip")
-        : (contentLengthHeader contentLength)
-          (requestHeaders req)
-
-    builder :: Builder
-    builder =
-            fromByteString (method req)
-            <> fromByteString " "
-            <> (case proxy req of
-                    Just{} ->
-                        fromByteString (if secure req then "https://" else "http://")
-                        <> fromByteString hh
-                    Nothing -> mempty)
-            <> (case S8.uncons $ path req of
-                    Just ('/', _) -> fromByteString $ path req
-                    _ -> fromByteString "/" <> fromByteString (path req))
-            <> (if S8.null (queryString req)
-                        then mempty
-                        else fromChar '?' <> fromByteString (queryString req))
-            <> fromByteString " HTTP/1.1\r\n"
-            <> foldr
-                (\a b -> headerPairToBuilder a <> b)
-                (fromByteString "\r\n")
-                headerPairs
-
-    headerPairToBuilder (k, v) =
-           fromByteString (CI.original k)
-        <> fromByteString ": "
-        <> fromByteString v
-        <> fromByteString "\r\n"
+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+module Network.HTTP.Conduit.Request+    ( Request (..)+    , RequestBody (..)+    , ContentType+    , Proxy (..)+    , parseUrl+    , browserDecompress+    , HttpException (..)+    , defaultCheckCerts+    , alwaysDecompress+    , addProxy+    , applyBasicAuth+    , urlEncodedBody+    , needsGunzip+    , requestBuilder+    ) where++import Data.Int (Int64)+import Data.Maybe (fromMaybe)+import Data.Monoid (mempty, mappend)+import Data.Typeable (Typeable)++import Data.Default (Default (def))++import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString)+import Blaze.ByteString.Builder.Char8 (fromChar)+import qualified Blaze.ByteString.Builder as Blaze++import qualified Data.Conduit as C+import qualified Data.Conduit.List as CL++import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+import qualified Data.ByteString.Lazy as L++import qualified Network.HTTP.Types as W+import Data.Certificate.X509 (X509)++import Network.TLS (TLSCertificateUsage (CertificateUsageAccept))+import Network.TLS.Extra (certificateVerifyChain, certificateVerifyDomain)++import Control.Exception (Exception, SomeException, toException)+import Control.Failure (Failure (failure))+import Codec.Binary.UTF8.String (encodeString)+import qualified Data.CaseInsensitive as CI+import qualified Data.ByteString.Base64 as B64++import Network.HTTP.Conduit.Chunk (chunkIt)+import Network.HTTP.Conduit.Util (readDec, (<>))++type ContentType = S.ByteString++-- | All information on how to connect to a host and what should be sent in the+-- HTTP request.+--+-- If you simply wish to download from a URL, see 'parseUrl'.+--+-- The constructor for this data type is not exposed. Instead, you should use+-- either the 'def' method to retrieve a default instance, or 'parseUrl' to+-- construct from a URL, and then use the records below to make modifications.+-- This approach allows http-conduit to add configuration options without+-- breaking backwards compatibility.+data Request m = Request+    { method :: W.Method+    -- ^ HTTP request method, eg GET, POST.+    , secure :: Bool+    -- ^ Whether to use HTTPS (ie, SSL).+    , checkCerts :: W.Ascii -> [X509] -> IO TLSCertificateUsage+    -- ^ Check if the server certificate is valid. Only relevant for HTTPS.+    , host :: W.Ascii+    , port :: Int+    , path :: W.Ascii+    -- ^ Everything from the host to the query string.+    , queryString :: W.Ascii+    , requestHeaders :: W.RequestHeaders+    , requestBody :: RequestBody m+    , proxy :: Maybe Proxy+    -- ^ Optional HTTP proxy.+    , rawBody :: Bool+    -- ^ If @True@, a chunked and\/or gzipped body will not be+    -- decoded. Use with caution.+    , decompress :: ContentType -> Bool+    -- ^ Predicate to specify whether gzipped data should be+    -- decompressed on the fly (see 'alwaysDecompress' and+    -- 'browserDecompress'). Default: browserDecompress.+    , redirectCount :: Int+    -- ^ How many redirects to follow when getting a resource. 0 means follow+    -- no redirects. Default value: 10.+    , checkStatus :: W.Status -> W.ResponseHeaders -> Maybe SomeException+    -- ^ Check the status code. Note that this will run after all redirects are+    -- performed. Default: return a @StatusCodeException@ on non-2XX responses.+    }++-- | When using one of the+-- 'RequestBodySource' \/ 'RequestBodySourceChunked' constructors,+-- you must ensure+-- that the 'Source' can be called multiple times.  Usually this+-- is not a problem.+--+-- The 'RequestBodySourceChunked' will send a chunked request+-- body, note that not all servers support this. Only use+-- 'RequestBodySourceChunked' if you know the server you're+-- sending to supports chunked request bodies.+data RequestBody m+    = RequestBodyLBS L.ByteString+    | RequestBodyBS S.ByteString+    | RequestBodyBuilder Int64 Blaze.Builder+    | RequestBodySource Int64 (C.Source m Blaze.Builder)+    | RequestBodySourceChunked (C.Source m Blaze.Builder)++-- | Define a HTTP proxy, consisting of a hostname and port number.++data Proxy = Proxy+    { proxyHost :: W.Ascii -- ^ The host name of the HTTP proxy.+    , proxyPort :: Int -- ^ The port number of the HTTP proxy.+    }++encodeUrlCharPI :: Char -> String+encodeUrlCharPI '/' = "/"+encodeUrlCharPI c = encodeUrlChar c++encodeUrlChar :: Char -> String+encodeUrlChar c+    -- List of unreserved characters per RFC 3986+    -- Gleaned from http://en.wikipedia.org/wiki/Percent-encoding+    | 'A' <= c && c <= 'Z' = [c]+    | 'a' <= c && c <= 'z' = [c]+    | '0' <= c && c <= '9' = [c]+encodeUrlChar c@'-' = [c]+encodeUrlChar c@'_' = [c]+encodeUrlChar c@'.' = [c]+encodeUrlChar c@'~' = [c]+encodeUrlChar y =+    let (a, c) = fromEnum y `divMod` 16+        b = a `mod` 16+        showHex' x+            | x < 10 = toEnum $ x + (fromEnum '0')+            | x < 16 = toEnum $ x - 10 + (fromEnum 'A')+            | otherwise = error $ "Invalid argument to showHex: " ++ show x+     in ['%', showHex' b, showHex' c]++-- | Convert a URL into a 'Request'.+--+-- This defaults some of the values in 'Request', such as setting 'method' to+-- GET and 'requestHeaders' to @[]@.+--+-- Since this function uses 'Failure', the return monad can be anything that is+-- an instance of 'Failure', such as 'IO' or 'Maybe'.+parseUrl :: Failure HttpException m => String -> m (Request m')+parseUrl s@('h':'t':'t':'p':':':'/':'/':rest) = parseUrl1 s False rest+parseUrl s@('h':'t':'t':'p':'s':':':'/':'/':rest) = parseUrl1 s True rest+parseUrl x = failure $ InvalidUrlException x "Invalid scheme"++parseUrl1 :: Failure HttpException m+          => String -> Bool -> String -> m (Request m')+parseUrl1 full sec s =+    parseUrl2 full sec s'+  where+    s' = encodeString s++defaultCheckCerts :: W.Ascii -> [X509] -> IO TLSCertificateUsage+defaultCheckCerts host' certs =+    case certificateVerifyDomain (S8.unpack host') certs of+        CertificateUsageAccept -> certificateVerifyChain certs+        rejected               -> return rejected++instance Default (Request m) where+    def = Request+        { host = "localhost"+        , port = 80+        , secure = False+        , checkCerts = defaultCheckCerts+        , requestHeaders = []+        , path = "/"+        , queryString = S8.empty+        , requestBody = RequestBodyLBS L.empty+        , method = "GET"+        , proxy = Nothing+        , rawBody = False+        , decompress = browserDecompress+        , redirectCount = 10+        , checkStatus = \s@(W.Status sci _) hs ->+            if 200 <= sci && sci < 300+                then Nothing+                else Just $ toException $ StatusCodeException s hs+        }++parseUrl2 :: Failure HttpException m+          => String -> Bool -> String -> m (Request m')+parseUrl2 full sec s = do+    port' <- mport+    return def+        { host = S8.pack hostname+        , port = port'+        , secure = sec+        , path = S8.pack+                    $ if null path''+                            then "/"+                            else concatMap encodeUrlCharPI path''+        , queryString = S8.pack qstring+        }+  where+    (beforeSlash, afterSlash) = break (== '/') s+    (hostname, portStr) = break (== ':') beforeSlash+    (path', qstring') = break (== '?') afterSlash+    path'' = path'+    qstring'' = case qstring' of+                '?':x -> x+                _ -> qstring'+    qstring = takeWhile (/= '#') qstring''+    mport =+        case (portStr, sec) of+            ("", False) -> return 80+            ("", True) -> return 443+            (':':rest, _) -> maybe+                (failure $ InvalidUrlException full "Invalid port")+                return+                (readDec rest)+            x -> error $ "parseUrl1: this should never happen: " ++ show x++data HttpException = StatusCodeException W.Status W.ResponseHeaders+                   | InvalidUrlException String String+                   | TooManyRedirects+                   | HttpParserException String+    deriving (Show, Typeable)+instance Exception HttpException++-- | Always decompress a compressed stream.+alwaysDecompress :: ContentType -> Bool+alwaysDecompress = const True++-- | Decompress a compressed stream unless the content-type is 'application/x-tar'.+browserDecompress :: ContentType -> Bool+browserDecompress = (/= "application/x-tar")++-- | Add a Basic Auth header (with the specified user name and password) to the+-- given Request. Ignore error handling:+--+--    applyBasicAuth "user" "pass" $ fromJust $ parseUrl url++applyBasicAuth :: S.ByteString -> S.ByteString -> Request m -> Request m+applyBasicAuth user passwd req =+    req { requestHeaders = authHeader : requestHeaders req }+  where+    authHeader = (CI.mk "Authorization", basic)+    basic = S8.append "Basic " (B64.encode $ S8.concat [ user, ":", passwd ])+++-- | Add a proxy to the the Request so that the Request when executed will use+-- the provided proxy.+addProxy :: S.ByteString -> Int -> Request m -> Request m+addProxy hst prt req =+    req { proxy = Just $ Proxy hst prt }++-- FIXME add a helper for generating POST bodies++-- | Add url-encoded paramters to the 'Request'.+--+-- This sets a new 'requestBody', adds a content-type request header and+-- changes the 'method' to POST.+urlEncodedBody :: Monad m => [(S.ByteString, S.ByteString)] -> Request m' -> Request m+urlEncodedBody headers req = req+    { requestBody = RequestBodyLBS body+    , method = "POST"+    , requestHeaders =+        (ct, "application/x-www-form-urlencoded")+      : filter (\(x, _) -> x /= ct) (requestHeaders req)+    }+  where+    ct = "Content-Type"+    body = L.fromChunks . return $ W.renderSimpleQuery False headers++needsGunzip :: Request m+            -> [W.Header] -- ^ response headers+            -> Bool+needsGunzip req hs' =+        not (rawBody req)+     && ("content-encoding", "gzip") `elem` hs'+     && decompress req (fromMaybe "" $ lookup "content-type" hs')++requestBuilder+    :: C.Resource m+    => Request m+    -> C.Source m Builder+requestBuilder req =+    CL.sourceList [builder] `mappend` bodySource+  where+    sourceSingle = CL.sourceList . return++    (contentLength, bodySource) =+        case requestBody req of+            RequestBodyLBS lbs -> (Just $ L.length lbs, sourceSingle $ fromLazyByteString lbs)+            RequestBodyBS bs -> (Just $ fromIntegral $ S.length bs, sourceSingle $ fromByteString bs)+            RequestBodyBuilder i b -> (Just $ i, sourceSingle b)+            RequestBodySource i source -> (Just i, source)+            RequestBodySourceChunked source -> (Nothing, source C.$= chunkIt)++    hh+        | port req == 80 && not (secure req) = host req+        | port req == 443 && secure req = host req+        | otherwise = host req <> S8.pack (':' : show (port req))++    contentLengthHeader (Just contentLength') =+            if method req `elem` ["GET", "HEAD"] && contentLength' == 0+                then id+                else (:) ("Content-Length", S8.pack $ show contentLength')+    contentLengthHeader Nothing = (:) ("Transfer-Encoding", "chunked")++    headerPairs :: W.RequestHeaders+    headerPairs+        = ("Host", hh)+        : ("Accept-Encoding", "gzip")+        : (contentLengthHeader contentLength)+          (requestHeaders req)++    builder :: Builder+    builder =+            fromByteString (method req)+            <> fromByteString " "+            <> (case proxy req of+                    Just{} ->+                        fromByteString (if secure req then "https://" else "http://")+                        <> fromByteString hh+                    Nothing -> mempty)+            <> (case S8.uncons $ path req of+                    Just ('/', _) -> fromByteString $ path req+                    _ -> fromByteString "/" <> fromByteString (path req))+            <> (if S8.null (queryString req)+                        then mempty+                        else fromChar '?' <> fromByteString (queryString req))+            <> fromByteString " HTTP/1.1\r\n"+            <> foldr+                (\a b -> headerPairToBuilder a <> b)+                (fromByteString "\r\n")+                headerPairs++    headerPairToBuilder (k, v) =+           fromByteString (CI.original k)+        <> fromByteString ": "+        <> fromByteString v+        <> fromByteString "\r\n"
Network/HTTP/Conduit/Response.hs view
@@ -1,104 +1,108 @@-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-module Network.HTTP.Conduit.Response
-    ( Response (..)
-    , getResponse
-    , lbsResponse
-    ) where
-
-import Control.Arrow (first)
-import Data.Typeable (Typeable)
-
-import qualified Data.ByteString.Char8 as S8
-import qualified Data.ByteString.Lazy as L
-
-import qualified Data.CaseInsensitive as CI
-
-import Control.Monad.Trans.Resource (ResourceT, ResourceIO)
-import qualified Data.Conduit as C
-import qualified Data.Conduit.Zlib as CZ
-import qualified Data.Conduit.Binary as CB
-import qualified Data.Conduit.List as CL
-
-import qualified Network.HTTP.Types as W
-
-import Network.HTTP.Conduit.Manager
-import Network.HTTP.Conduit.Request
-import Network.HTTP.Conduit.Util
-import Network.HTTP.Conduit.Parser
-import Network.HTTP.Conduit.Chunk
-
--- | A simple representation of the HTTP response created by 'lbsConsumer'.
-data Response body = Response
-    { statusCode :: W.Status
-    , responseHeaders :: W.ResponseHeaders
-    , responseBody :: body
-    }
-    deriving (Show, Eq, Typeable)
-
--- | Convert a 'Response' that has a 'C.BufferedSource' body to one with a lazy
--- 'L.ByteString' body.
-lbsResponse :: C.Resource m
-            => ResourceT m (Response (C.BufferedSource m S8.ByteString))
-            -> ResourceT m (Response L.ByteString)
-lbsResponse mres = do
-    res <- mres
-    bss <- responseBody res C.$$ CL.consume
-    return res
-        { responseBody = L.fromChunks bss
-        }
-
-getResponse :: ResourceIO m
-            => ConnRelease m
-            -> Request m
-            -> C.BufferedSource m S8.ByteString
-            -> ResourceT m (Response (C.BufferedSource m S8.ByteString))
-getResponse connRelease req@(Request {..}) bsrc = do
-    ((_, sc, sm), hs) <- bsrc C.$$ sinkHeaders
-    let s = W.Status sc sm
-    let hs' = map (first CI.mk) hs
-    let mcl = lookup "content-length" hs' >>= readDec . S8.unpack
-
-    -- RFC 2616 section 4.4_1 defines responses that must not include a body
-    body <- if hasNoBody method sc || mcl == Just 0
-        then do
-            -- FIXME clean up socket
-            C.bufferSource $ CL.sourceList []
-        else do
-            bsrc' <-
-                if ("transfer-encoding", "chunked") `elem` hs'
-                    then C.bufferSource $ bsrc C.$= chunkedConduit rawBody
-                    else
-                        case mcl of
-                            Just len -> C.bufferSource $ bsrc C.$= CB.isolate len
-                            Nothing  -> return bsrc
-            if needsGunzip req hs'
-                then C.bufferSource $ bsrc' C.$= CZ.ungzip
-                else return bsrc'
-
-    -- should we put this connection back into the connection manager?
-    let toPut = Just "close" /= lookup "connection" hs'
-    let cleanup = connRelease $ if toPut then Reuse else DontReuse
-
-    return $ Response s hs' $ addCleanup cleanup body
-
--- | Add some cleanup code to the given 'C.BufferedSource'. General purpose
--- function, could be included in conduit itself.
-addCleanup :: C.ResourceIO m
-           => ResourceT m ()
-           -> C.BufferedSource m a
-           -> C.BufferedSource m a
-addCleanup cleanup bsrc = C.BufferedSource
-    { C.bsourcePull = do
-        res <- C.bsourcePull bsrc
-        case res of
-            C.Closed -> cleanup
-            C.Open _ -> return ()
-        return res
-    , C.bsourceUnpull = C.bsourceUnpull bsrc
-    , C.bsourceClose = do
-        C.bsourceClose bsrc
-        cleanup
-    }
+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+module Network.HTTP.Conduit.Response+    ( Response (..)+    , getResponse+    , lbsResponse+    ) where++import Control.Arrow (first)+import Data.Typeable (Typeable)++import qualified Data.ByteString.Char8 as S8+import qualified Data.ByteString.Lazy as L++import qualified Data.CaseInsensitive as CI++import Control.Monad.Trans.Resource (ResourceT, ResourceIO)+import qualified Data.Conduit as C+import qualified Data.Conduit.Zlib as CZ+import qualified Data.Conduit.Binary as CB+import qualified Data.Conduit.List as CL++import qualified Network.HTTP.Types as W++import Network.HTTP.Conduit.Manager+import Network.HTTP.Conduit.Request+import Network.HTTP.Conduit.Util+import Network.HTTP.Conduit.Parser+import Network.HTTP.Conduit.Chunk++-- | A simple representation of the HTTP response created by 'lbsConsumer'.+data Response body = Response+    { statusCode :: W.Status+    , responseHeaders :: W.ResponseHeaders+    , responseBody :: body+    }+    deriving (Show, Eq, Typeable)++-- | Since 1.1.2.+instance Functor Response where+    fmap f (Response status headers body) = Response status headers (f body)++-- | Convert a 'Response' that has a 'C.BufferedSource' body to one with a lazy+-- 'L.ByteString' body.+lbsResponse :: C.Resource m+            => ResourceT m (Response (C.BufferedSource m S8.ByteString))+            -> ResourceT m (Response L.ByteString)+lbsResponse mres = do+    res <- mres+    bss <- responseBody res C.$$ CL.consume+    return res+        { responseBody = L.fromChunks bss+        }++getResponse :: ResourceIO m+            => ConnRelease m+            -> Request m+            -> C.BufferedSource m S8.ByteString+            -> ResourceT m (Response (C.BufferedSource m S8.ByteString))+getResponse connRelease req@(Request {..}) bsrc = do+    ((_, sc, sm), hs) <- bsrc C.$$ sinkHeaders+    let s = W.Status sc sm+    let hs' = map (first CI.mk) hs+    let mcl = lookup "content-length" hs' >>= readDec . S8.unpack++    -- RFC 2616 section 4.4_1 defines responses that must not include a body+    body <- if hasNoBody method sc || mcl == Just 0+        then do+            -- FIXME clean up socket+            C.bufferSource $ CL.sourceList []+        else do+            bsrc' <-+                if ("transfer-encoding", "chunked") `elem` hs'+                    then C.bufferSource $ bsrc C.$= chunkedConduit rawBody+                    else+                        case mcl of+                            Just len -> C.bufferSource $ bsrc C.$= CB.isolate len+                            Nothing  -> return bsrc+            if needsGunzip req hs'+                then C.bufferSource $ bsrc' C.$= CZ.ungzip+                else return bsrc'++    -- should we put this connection back into the connection manager?+    let toPut = Just "close" /= lookup "connection" hs'+    let cleanup = connRelease $ if toPut then Reuse else DontReuse++    return $ Response s hs' $ addCleanup cleanup body++-- | Add some cleanup code to the given 'C.BufferedSource'. General purpose+-- function, could be included in conduit itself.+addCleanup :: C.ResourceIO m+           => ResourceT m ()+           -> C.BufferedSource m a+           -> C.BufferedSource m a+addCleanup cleanup bsrc = C.BufferedSource+    { C.bsourcePull = do+        res <- C.bsourcePull bsrc+        case res of+            C.Closed -> cleanup+            C.Open _ -> return ()+        return res+    , C.bsourceUnpull = C.bsourceUnpull bsrc+    , C.bsourceClose = do+        C.bsourceClose bsrc+        cleanup+    }
Network/HTTP/Conduit/Util.hs view
@@ -1,71 +1,71 @@-{-# LANGUAGE CPP #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts #-}
-module Network.HTTP.Conduit.Util
-    ( hGetSome
-    , (<>)
-    , readDec
-    , hasNoBody
-    ) where
-
-import Data.Monoid (Monoid, mappend)
-
-import qualified Data.ByteString.Char8 as S8
-
-import qualified Data.Text as T
-import qualified Data.Text.Read
-
-#if MIN_VERSION_base(4,3,0)
-import Data.ByteString (hGetSome)
-#else
-import GHC.IO.Handle.Types
-import System.IO                (hWaitForInput, hIsEOF)
-import System.IO.Error          (mkIOError, illegalOperationErrorType)
-
--- | Like 'hGet', except that a shorter 'ByteString' may be returned
--- if there are not enough bytes immediately available to satisfy the
--- whole request.  'hGetSome' only blocks if there is no data
--- available, and EOF has not yet been reached.
-hGetSome :: Handle -> Int -> IO S.ByteString
-hGetSome hh i
-    | i >  0    = let
-                   loop = do
-                     s <- S.hGetNonBlocking hh i
-                     if not (S.null s)
-                        then return s
-                        else do eof <- hIsEOF hh
-                                if eof then return s
-                                       else hWaitForInput hh (-1) >> loop
-                                         -- for this to work correctly, the
-                                         -- Handle should be in binary mode
-                                         -- (see GHC ticket #3808)
-                  in loop
-    | i == 0    = return S.empty
-    | otherwise = illegalBufferSize hh "hGetSome" i
-
-illegalBufferSize :: Handle -> String -> Int -> IO a
-illegalBufferSize handle fn sz =
-    ioError (mkIOError illegalOperationErrorType msg (Just handle) Nothing)
-    --TODO: System.IO uses InvalidArgument here, but it's not exported :-(
-    where
-      msg = fn ++ ": illegal ByteString size " ++ showsPrec 9 sz []
-#endif
-
-infixr 5 <>
-(<>) :: Monoid m => m -> m -> m
-(<>) = mappend
-
-readDec :: Integral i => String -> Maybe i
-readDec s =
-    case Data.Text.Read.decimal $ T.pack s of
-        Right (i, t)
-            | T.null t -> Just i
-        _ -> Nothing
-
-hasNoBody :: S8.ByteString -- ^ request method
-          -> Int -- ^ status code
-          -> Bool
-hasNoBody "HEAD" _ = True
-hasNoBody _ 204 = True
-hasNoBody _ 304 = True
-hasNoBody _ i = 100 <= i && i < 200
+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+module Network.HTTP.Conduit.Util+    ( hGetSome+    , (<>)+    , readDec+    , hasNoBody+    ) where++import Data.Monoid (Monoid, mappend)++import qualified Data.ByteString.Char8 as S8++import qualified Data.Text as T+import qualified Data.Text.Read++#if MIN_VERSION_base(4,3,0)+import Data.ByteString (hGetSome)+#else+import GHC.IO.Handle.Types+import System.IO                (hWaitForInput, hIsEOF)+import System.IO.Error          (mkIOError, illegalOperationErrorType)++-- | Like 'hGet', except that a shorter 'ByteString' may be returned+-- if there are not enough bytes immediately available to satisfy the+-- whole request.  'hGetSome' only blocks if there is no data+-- available, and EOF has not yet been reached.+hGetSome :: Handle -> Int -> IO S.ByteString+hGetSome hh i+    | i >  0    = let+                   loop = do+                     s <- S.hGetNonBlocking hh i+                     if not (S.null s)+                        then return s+                        else do eof <- hIsEOF hh+                                if eof then return s+                                       else hWaitForInput hh (-1) >> loop+                                         -- for this to work correctly, the+                                         -- Handle should be in binary mode+                                         -- (see GHC ticket #3808)+                  in loop+    | i == 0    = return S.empty+    | otherwise = illegalBufferSize hh "hGetSome" i++illegalBufferSize :: Handle -> String -> Int -> IO a+illegalBufferSize handle fn sz =+    ioError (mkIOError illegalOperationErrorType msg (Just handle) Nothing)+    --TODO: System.IO uses InvalidArgument here, but it's not exported :-(+    where+      msg = fn ++ ": illegal ByteString size " ++ showsPrec 9 sz []+#endif++infixr 5 <>+(<>) :: Monoid m => m -> m -> m+(<>) = mappend++readDec :: Integral i => String -> Maybe i+readDec s =+    case Data.Text.Read.decimal $ T.pack s of+        Right (i, t)+            | T.null t -> Just i+        _ -> Nothing++hasNoBody :: S8.ByteString -- ^ request method+          -> Int -- ^ status code+          -> Bool+hasNoBody "HEAD" _ = True+hasNoBody _ 204 = True+hasNoBody _ 304 = True+hasNoBody _ i = 100 <= i && i < 200
Setup.lhs view
@@ -1,8 +1,8 @@-#!/usr/bin/env runhaskell
-
-> module Main where
-> import Distribution.Simple
-> import System.Cmd (system)
-
-> main :: IO ()
-> main = defaultMain
+#!/usr/bin/env runhaskell++> module Main where+> import Distribution.Simple+> import System.Cmd (system)++> main :: IO ()+> main = defaultMain
http-conduit.cabal view
@@ -1,103 +1,103 @@-name:            http-conduit
-version:         1.1.1
-license:         BSD3
-license-file:    LICENSE
-author:          Michael Snoyman <michael@snoyman.com>
-maintainer:      Michael Snoyman <michael@snoyman.com>
-synopsis:        HTTP client package with conduit interface and HTTPS support.
-description:
-    This package uses attoparsec for parsing the actual contents of the HTTP connection. It also provides higher-level functions which allow you to avoid direct usage of conduits. See <http://www.yesodweb.com/book/http-conduit> for more information.
-category:        Web, Conduit
-stability:       Stable
-cabal-version:   >= 1.8
-build-type:      Simple
-homepage:        http://www.yesodweb.com/book/http-conduit
-extra-source-files: test/main.hs
-
-flag network-bytestring
-  default: False
-
-library
-    build-depends: base                  >= 4       && < 5
-                 , bytestring            >= 0.9.1.4 && < 0.10
-                 , transformers          >= 0.2     && < 0.3
-                 , failure               >= 0.1     && < 0.2
-                 , conduit               >= 0.0     && < 0.1
-                 , zlib-conduit          >= 0.0     && < 0.1
-                 , blaze-builder-conduit >= 0.0     && < 0.1
-                 , attoparsec-conduit    >= 0.0     && < 0.1
-                 , attoparsec            >= 0.8.0.2 && < 0.11
-                 , utf8-string           >= 0.3.4   && < 0.4
-                 , blaze-builder         >= 0.2.1   && < 0.4
-                 , http-types            >= 0.6     && < 0.7
-                 , cprng-aes             >= 0.2     && < 0.3
-                 , tls                   >= 0.8.1   && < 0.9
-                 , tls-extra             >= 0.4     && < 0.5
-                 , monad-control         >= 0.3     && < 0.4
-                 , containers            >= 0.2
-                 , certificate           >= 0.7     && < 1.1
-                 , case-insensitive      >= 0.2
-                 , base64-bytestring     >= 0.1     && < 0.2
-                 , asn1-data             >= 0.5.1   && < 0.7
-                 , data-default          >= 0.3     && < 0.4
-                 , text
-                 , transformers-base     >= 0.4     && < 0.5
-                 , lifted-base           >= 0.1     && < 0.2
-                 , time
-    if flag(network-bytestring)
-        build-depends: network               >= 2.2.1   && < 2.2.3
-                     , network-bytestring    >= 0.1.3   && < 0.1.4
-    else
-        build-depends: network               >= 2.3     && < 2.4
-    exposed-modules: Network.HTTP.Conduit
-    other-modules:   Network.HTTP.Conduit.Parser
-                     Network.HTTP.Conduit.ConnInfo
-                     Network.HTTP.Conduit.Request
-                     Network.HTTP.Conduit.Util
-                     Network.HTTP.Conduit.Manager
-                     Network.HTTP.Conduit.Chunk
-                     Network.HTTP.Conduit.Response
-    ghc-options:     -Wall
-
-test-suite test
-    main-is: test/main.hs
-    type: exitcode-stdio-1.0
-
-    ghc-options:   -Wall
-    cpp-options:   -DDEBUG
-    build-depends: base >= 4 && < 5
-                 , HUnit
-                 , hspec
-                 , bytestring
-                 , transformers
-                 , failure
-                 , conduit
-                 , zlib-conduit
-                 , blaze-builder-conduit
-                 , attoparsec-conduit
-                 , attoparsec
-                 , utf8-string
-                 , blaze-builder
-                 , http-types
-                 , cprng-aes
-                 , tls
-                 , tls-extra
-                 , monad-control
-                 , containers
-                 , certificate
-                 , case-insensitive
-                 , base64-bytestring
-                 , asn1-data
-                 , data-default
-                 , text
-                 , transformers-base
-                 , lifted-base
-                 , time
-                 , network
-                 , wai
-                 , warp
-                 , http-types
-
-source-repository head
-  type:     git
-  location: git://github.com/snoyberg/http-conduit.git
+name:            http-conduit+version:         1.1.2.2+license:         BSD3+license-file:    LICENSE+author:          Michael Snoyman <michael@snoyman.com>+maintainer:      Michael Snoyman <michael@snoyman.com>+synopsis:        HTTP client package with conduit interface and HTTPS support.+description:+    This package uses attoparsec for parsing the actual contents of the HTTP connection. It also provides higher-level functions which allow you to avoid direct usage of conduits. See <http://www.yesodweb.com/book/http-conduit> for more information.+category:        Web, Conduit+stability:       Stable+cabal-version:   >= 1.8+build-type:      Simple+homepage:        http://www.yesodweb.com/book/http-conduit+extra-source-files: test/main.hs++flag network-bytestring+  default: False++library+    build-depends: base                  >= 4       && < 5+                 , bytestring            >= 0.9.1.4 && < 0.10+                 , transformers          >= 0.2     && < 0.3+                 , failure               >= 0.1+                 , conduit               >= 0.0     && < 0.1+                 , zlib-conduit          >= 0.0     && < 0.1+                 , blaze-builder-conduit >= 0.0     && < 0.1+                 , attoparsec-conduit    >= 0.0     && < 0.1+                 , attoparsec            >= 0.8.0.2 && < 0.11+                 , utf8-string           >= 0.3.4   && < 0.4+                 , blaze-builder         >= 0.2.1   && < 0.4+                 , http-types            >= 0.6     && < 0.7+                 , cprng-aes             >= 0.2     && < 0.3+                 , tls                   >= 0.8.1   && < 0.9+                 , tls-extra             >= 0.4     && < 0.5+                 , monad-control         >= 0.3     && < 0.4+                 , containers            >= 0.2+                 , certificate           >= 0.7     && < 1.1+                 , case-insensitive      >= 0.2+                 , base64-bytestring     >= 0.1     && < 0.2+                 , asn1-data             >= 0.5.1   && < 0.7+                 , data-default          >= 0.3     && < 0.4+                 , text+                 , transformers-base     >= 0.4     && < 0.5+                 , lifted-base           >= 0.1     && < 0.2+                 , time+    if flag(network-bytestring)+        build-depends: network               >= 2.2.1   && < 2.2.3+                     , network-bytestring    >= 0.1.3   && < 0.1.4+    else+        build-depends: network               >= 2.3     && < 2.4+    exposed-modules: Network.HTTP.Conduit+    other-modules:   Network.HTTP.Conduit.Parser+                     Network.HTTP.Conduit.ConnInfo+                     Network.HTTP.Conduit.Request+                     Network.HTTP.Conduit.Util+                     Network.HTTP.Conduit.Manager+                     Network.HTTP.Conduit.Chunk+                     Network.HTTP.Conduit.Response+    ghc-options:     -Wall++test-suite test+    main-is: test/main.hs+    type: exitcode-stdio-1.0++    ghc-options:   -Wall+    cpp-options:   -DDEBUG+    build-depends: base >= 4 && < 5+                 , HUnit+                 , hspec+                 , bytestring+                 , transformers+                 , failure+                 , conduit+                 , zlib-conduit+                 , blaze-builder-conduit+                 , attoparsec-conduit+                 , attoparsec+                 , utf8-string+                 , blaze-builder+                 , http-types+                 , cprng-aes+                 , tls+                 , tls-extra+                 , monad-control+                 , containers+                 , certificate+                 , case-insensitive+                 , base64-bytestring+                 , asn1-data+                 , data-default+                 , text+                 , transformers-base+                 , lifted-base+                 , time+                 , network+                 , wai+                 , warp+                 , http-types++source-repository head+  type:     git+  location: git://github.com/snoyberg/http-conduit.git
test/main.hs view
@@ -1,49 +1,49 @@-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-import Test.Hspec.Monadic
-import Test.Hspec.HUnit ()
-import Test.HUnit
-import Network.Wai
-import Network.Wai.Handler.Warp (run)
-import Network.HTTP.Conduit
-import Control.Concurrent (forkIO, killThread)
-import Network.HTTP.Types
-import Control.Exception (try, SomeException)
-import Network.HTTP.Conduit.ConnInfo
-
-app :: Application
-app req =
-    case pathInfo req of
-        [] -> return $ responseLBS status200 [] "homepage"
-        _ -> return $ responseLBS status404 [] "not found"
-
-main :: IO ()
-main = hspecX $ do
-    describe "simpleHttp" $ do
-        it "gets homepage" $ do
-            tid <- forkIO $ run 3000 app
-            lbs <- simpleHttp "http://localhost:3000/"
-            killThread tid
-            lbs @?= "homepage"
-        it "throws exception on 404" $ do
-            tid <- forkIO $ run 3001 app
-            elbs <- try $ simpleHttp "http://localhost:3001/404"
-            killThread tid
-            case elbs of
-                Left (_ :: SomeException) -> return ()
-                Right _ -> error "Expected an exception"
-    describe "manager" $ do
-        it "closes all connections" $ do
-            clearSocketsList
-            tid1 <- forkIO $ run 3002 app
-            tid2 <- forkIO $ run 3003 app
-            withManager $ \manager -> do
-                let Just req1 = parseUrl "http://localhost:3002/"
-                let Just req2 = parseUrl "http://localhost:3003/"
-                _res1a <- http req1 manager
-                _res1b <- http req1 manager
-                _res2 <- http req2 manager
-                return ()
-            requireAllSocketsClosed
-            killThread tid2
-            killThread tid1
+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+import Test.Hspec.Monadic+import Test.Hspec.HUnit ()+import Test.HUnit+import Network.Wai+import Network.Wai.Handler.Warp (run)+import Network.HTTP.Conduit+import Control.Concurrent (forkIO, killThread)+import Network.HTTP.Types+import Control.Exception (try, SomeException)+import Network.HTTP.Conduit.ConnInfo++app :: Application+app req =+    case pathInfo req of+        [] -> return $ responseLBS status200 [] "homepage"+        _ -> return $ responseLBS status404 [] "not found"++main :: IO ()+main = hspecX $ do+    describe "simpleHttp" $ do+        it "gets homepage" $ do+            tid <- forkIO $ run 3000 app+            lbs <- simpleHttp "http://localhost:3000/"+            killThread tid+            lbs @?= "homepage"+        it "throws exception on 404" $ do+            tid <- forkIO $ run 3001 app+            elbs <- try $ simpleHttp "http://localhost:3001/404"+            killThread tid+            case elbs of+                Left (_ :: SomeException) -> return ()+                Right _ -> error "Expected an exception"+    describe "manager" $ do+        it "closes all connections" $ do+            clearSocketsList+            tid1 <- forkIO $ run 3002 app+            tid2 <- forkIO $ run 3003 app+            withManager $ \manager -> do+                let Just req1 = parseUrl "http://localhost:3002/"+                let Just req2 = parseUrl "http://localhost:3003/"+                _res1a <- http req1 manager+                _res1b <- http req1 manager+                _res2 <- http req2 manager+                return ()+            requireAllSocketsClosed+            killThread tid2+            killThread tid1