packages feed

http-conduit 1.0.0.1 → 1.1.0

raw patch · 9 files changed

+315/−304 lines, 9 filesdep −zlib-enumPVP ok

version bump matches the API change (PVP)

Dependencies removed: zlib-enum

API changes (from Hackage documentation)

- Network.HTTP.Conduit: httpLbsRedirect :: ResourceIO m => Request m -> Manager -> ResourceT m Response
- Network.HTTP.Conduit: httpRedirect :: ResourceIO m => Request m -> (Status -> ResponseHeaders -> BufferedSource m ByteString -> ResourceT m a) -> Manager -> ResourceT m a
- Network.HTTP.Conduit: lbsConsumer :: ResourceIO m => ResponseConsumer m Response
- Network.HTTP.Conduit: redirectConsumer :: ResourceIO m => Int -> Request m -> ResponseConsumer m a -> Manager -> ResponseConsumer m a
- Network.HTTP.Conduit: type ResponseConsumer m a = Status -> ResponseHeaders -> BufferedSource m ByteString -> ResourceT m a
+ Network.HTTP.Conduit: checkStatus :: Request m -> Status -> ResponseHeaders -> Maybe SomeException
+ Network.HTTP.Conduit: redirectCount :: Request m -> Int
- Network.HTTP.Conduit: Response :: Int -> ResponseHeaders -> ByteString -> Response
+ Network.HTTP.Conduit: Response :: Status -> ResponseHeaders -> body -> Response body
- Network.HTTP.Conduit: StatusCodeException :: Int -> ByteString -> HttpException
+ Network.HTTP.Conduit: StatusCodeException :: Status -> ResponseHeaders -> HttpException
- Network.HTTP.Conduit: data Response
+ Network.HTTP.Conduit: data Response body
- Network.HTTP.Conduit: http :: ResourceIO m => Request m -> ResponseConsumer m a -> Manager -> ResourceT m a
+ Network.HTTP.Conduit: http :: ResourceIO m => Request m -> Manager -> ResourceT m (Response (BufferedSource m ByteString))
- Network.HTTP.Conduit: httpLbs :: ResourceIO m => Request m -> Manager -> ResourceT m Response
+ Network.HTTP.Conduit: httpLbs :: ResourceIO m => Request m -> Manager -> ResourceT m (Response ByteString)
- Network.HTTP.Conduit: responseBody :: Response -> ByteString
+ Network.HTTP.Conduit: responseBody :: Response body -> body
- Network.HTTP.Conduit: responseHeaders :: Response -> ResponseHeaders
+ Network.HTTP.Conduit: responseHeaders :: Response body -> ResponseHeaders
- Network.HTTP.Conduit: simpleHttp :: ResourceIO m => String -> m ByteString
+ Network.HTTP.Conduit: simpleHttp :: MonadIO m => String -> m ByteString
- Network.HTTP.Conduit: statusCode :: Response -> Int
+ Network.HTTP.Conduit: statusCode :: Response body -> Status

Files

Network/HTTP/Conduit.hs view
@@ -1,5 +1,6 @@ {-# 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@@ -12,19 +13,19 @@ -- > 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. By using 'httpRedirect', it will automatically--- follow 3xx redirects.+-- 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--- >         let handler _ _ bsrc = bsrc C.$$ sinkFile "google.html"--- >         run_ $ httpRedirect request handler manager+-- >         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':@@ -51,15 +52,11 @@     ( -- * Perform a request       simpleHttp     , httpLbs-    , httpLbsRedirect     , http-    , httpRedirect-    , redirectConsumer       -- * Datatypes     , Proxy (..)     , RequestBody (..)     , Response (..)-    , ResponseConsumer       -- ** Request     , Request     , def@@ -75,6 +72,8 @@     , proxy     , rawBody     , decompress+    , redirectCount+    , checkStatus       -- *** Defaults     , defaultCheckCerts       -- * Manager@@ -85,7 +84,6 @@     , parseUrl     , applyBasicAuth     , addProxy-    , lbsConsumer       -- * Decompression predicates     , alwaysDecompress     , browserDecompress@@ -93,6 +91,10 @@     , urlEncodedBody       -- * Exceptions     , HttpException (..)+#if DEBUG+      -- * Debug+    , printOpenSockets+#endif     ) where  import qualified Data.ByteString as S@@ -104,10 +106,12 @@  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@@ -116,114 +120,44 @@  -- | 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 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. ----- The second argument specifies how the response should be handled. It's a--- function that takes two arguments: the first is the HTTP status code of the--- response, and the second is a list of all response headers. This module--- exports 'lbsConsumer', which generates a 'Response' value.+-- 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 that this allows you to have fully interleaved IO actions during your--- HTTP download, making it possible to download very large responses in--- constant memory.+-- Note: Unlike previous versions, this function will perform redirects, as+-- specified by the 'redirectCount' setting. http-     :: ResourceIO m-     => Request m-     -> ResponseConsumer m a-     -> Manager-     -> ResourceT m a-http req consumer m = withConn req m $ \ci -> do-    bsrc <- C.bufferSource $ connSource ci-    requestBuilder req C.$$ builderToByteString C.=$ connSink ci-    getResponse req consumer 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 = http lbsConsumer@------ Please see 'lbsConsumer' for more information on how the 'Response' value is--- created.------ Even though a '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 write your own--- iteratee and use 'http' or 'httpRedirect' directly.-httpLbs :: ResourceIO m => Request m -> Manager -> ResourceT m Response-httpLbs req = http req lbsConsumer---- | 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. 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 write your own--- iteratee and use 'http' or 'httpRedirect' directly.-simpleHttp :: ResourceIO m => String -> m L.ByteString-simpleHttp url = runResourceT $ do-    url' <- liftBase $ parseUrl url-    man <- newManager-    Response sc _ b <- httpLbsRedirect url'-        { decompress = browserDecompress-        } man-    if 200 <= sc && sc < 300-        then return b-        else liftBase $ throwIO $ StatusCodeException sc b---- | Same as 'http', but follows all 3xx redirect status codes that contain a--- location header.-httpRedirect     :: ResourceIO m     => Request m-    -> (W.Status -> W.ResponseHeaders -> C.BufferedSource m S.ByteString -> ResourceT m a)     -> Manager-    -> ResourceT m a-httpRedirect req bodyStep manager =-    http req (redirectConsumer 10 req bodyStep manager) manager---- | Download the specified 'Request', returning the results as a 'Response'--- and automatically handling redirects.------ This is a simplified version of 'httpRedirect' 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 'httpRedirect' directly. This function is defined as:------ @httpLbsRedirect = httpRedirect lbsConsumer@------ Please see 'lbsConsumer' for more information on how the 'Response' value is--- created.------ Even though a '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 write your own--- iteratee and use 'http' or 'httpRedirect' directly.-httpLbsRedirect :: ResourceIO m => Request m -> Manager -> ResourceT m Response-httpLbsRedirect req m = httpRedirect req lbsConsumer m---- | Make a request automatically follow 3xx redirects.------ Used internally by 'httpRedirect' and family.-redirectConsumer :: ResourceIO m-             => Int -- ^ number of redirects to attempt-             -> Request m -- ^ Original request-             -> ResponseConsumer m a-             -> Manager-             -> ResponseConsumer m a-redirectConsumer redirects req bodyStep manager s@(W.Status code _) hs bsrc-    | 300 <= code && code < 400 =-        case lookup "location" hs of-            Just l'' -> do+    -> 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@@ -245,12 +179,73 @@                         , path = path l                         , queryString = queryString l                         , method =-                            if code == 303+                            -- 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                         }-                if redirects == 0-                    then liftBase $ throwIO TooManyRedirects-                    else (http req') (redirectConsumer (redirects - 1) req' bodyStep manager) manager-            Nothing -> bodyStep s hs bsrc-    | otherwise = bodyStep s hs bsrc+                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/ConnInfo.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE CPP #-} module Network.HTTP.Conduit.ConnInfo     ( ConnInfo     , connClose@@ -11,6 +12,9 @@     , TLSCertificateRejectReason(..)     , TLSCertificateUsage(..)     , getSocket+#if DEBUG+    , printOpenSockets+#endif     ) where  import Control.Exception (SomeException, throwIO, try)@@ -35,6 +39,11 @@  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@@ -58,15 +67,47 @@     , C.sourceClose = return ()     } -socketConn :: Socket -> ConnInfo-socketConn sock = ConnInfo-    { connRead  = recv sock 4096-    , connWrite = sendAll sock-    , connClose = sClose sock-    }+#if DEBUG+allOpenSockets :: I.IORef (Int, IntMap.IntMap String)+allOpenSockets = unsafePerformIO $ I.newIORef (0, IntMap.empty) -sslClientConn :: ([X509] -> IO TLSCertificateUsage) -> Handle -> IO ConnInfo-sslClientConn onCerts h = do+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+#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 ]@@ -79,7 +120,12 @@     return ConnInfo         { connRead = recvD istate         , connWrite = sendData istate . L.fromChunks . (:[])-        , connClose = bye istate >> hClose h+        , connClose = do+#if DEBUG+            removeSocket i+#endif+            bye istate+            hClose h         }   where     recvD istate = do
Network/HTTP/Conduit/Manager.hs view
@@ -4,11 +4,11 @@     ( Manager     , ConnKey (..)     , newManager-    , withConn-    , WithConnResponse (..)+    , getConn     , ConnReuse (..)-    , UseConn     , withManager+    , ConnRelease+    , ManagedConn (..)     ) where  import Control.Applicative ((<$>))@@ -26,8 +26,13 @@ import qualified Data.Text as T  import Control.Monad.Base (liftBase)-import Control.Exception.Lifted (mask, try, throwIO, SomeException)-import Control.Monad.Trans.Resource (ResourceT, runResourceT, ResourceIO, withIO)+import Control.Exception.Lifted (mask)+import Control.Monad.Trans.Resource+    ( ResourceT, runResourceT, ResourceIO, withIO+    , register, release+    , newRef, readRef', writeRef+    , safeFromIOBase+    )  import Network (connectTo, PortID (PortNumber)) import Data.Certificate.X509 (X509)@@ -78,31 +83,34 @@     m <- I.atomicModifyIORef i $ \x -> (Map.empty, x)     mapM_ connClose $ Map.elems m -type UseConn m a = ConnInfo -> ResourceT m (WithConnResponse a)--withSocketConn+getSocketConn     :: ResourceIO m     => Manager     -> String     -> Int-    -> UseConn m a-    -> ResourceT m a-withSocketConn man host' port' =-    withManagedConn man (ConnKey (T.pack host') port' False) $-        fmap socketConn $ getSocket host' port'+    -> 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" -withSslConn :: ResourceIO m+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-            -> UseConn m a-            -> ResourceT m a-withSslConn checkCert man host' port' =-    withManagedConn man (ConnKey (T.pack host') port' True) $-        (connectTo host' (PortNumber $ fromIntegral port') >>= sslClientConn checkCert)+            -> 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" -withSslProxyConn+getSslProxyConn             :: ResourceIO m             => ([X509] -> IO TLSCertificateUsage)             -> S8.ByteString -- ^ Target host@@ -110,12 +118,12 @@             -> Manager             -> String -- ^ Proxy host             -> Int -- ^ Proxy port-            -> UseConn m a-            -> ResourceT m a-withSslProxyConn checkCert thost tport man phost pport =-    withManagedConn man (ConnKey (T.pack phost) pport True) $-        doConnect >>= sslClientConn checkCert+            -> 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@@ -136,44 +144,62 @@         error $ "Proxy failed to CONNECT to '"                 ++ S8.unpack thost ++ ":" ++ show tport ++ "' : " ++ s -withManagedConn+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-    -> UseConn m a-    -> ResourceT m a-withManagedConn man key open f = mask $ \restore -> do+    -> 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, False)-            Just ci -> return (ci, True)-    ea <- try $ restore $ f ci-    case ea of-        Left e -> do-            liftBase $ connClose ci-            if isManaged-                then restore $ withManagedConn man key open f-                else throwIO (e :: SomeException)-        Right (WithConnResponse cr a) -> do-            case cr of-                Reuse -> liftBase $ putSocket man key ci-                DontReuse -> liftBase $ connClose ci-            return a+                return (ci, Fresh)+            -- Return the existing one+            Just ci -> return (ci, Reused) -data WithConnResponse a = WithConnResponse !ConnReuse !a+    -- 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 -withConn :: ResourceIO m-         => Request m-         -> Manager-         -> UseConn m a-         -> ResourceT m a-withConn req m =+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@@ -183,6 +209,6 @@             Nothing -> (False, S8.unpack h, port req)     go =         case (secure req, useProxy) of-            (False, _) -> withSocketConn-            (True, False) -> withSslConn $ checkCerts req h-            (True, True) -> withSslProxyConn (checkCerts req h) h (port req)+            (False, _) -> getSocketConn+            (True, False) -> getSslConn $ checkCerts req h+            (True, True) -> getSslProxyConn (checkCerts req h) h (port req)
Network/HTTP/Conduit/Parser.hs view
@@ -120,30 +120,3 @@     lower = do         d <- satisfy $ \w -> (w >= 97 && w <= 102)         return $ d - 87--{--sinkParserTill :: Monad m-               => Parser a-               -> Parser end-               -> E.Enumeratee a S.ByteString m b-sinkParserTill p pend =-    E.continue $ step $ parse p-  where-    step parse (E.Chunks xs) = parseLoop parse xs-    step parse E.EOF = case parse S.empty of-        Done extra a -> E.yield a $ if S.null extra-            then E.Chunks []-            else E.Chunks [extra]-        Partial _ -> err [] "sinkParser: divergent parser"-        Fail _ ctx msg -> err ctx msg--    parseLoop parse [] = E.continue (step parse)-    parseLoop parse (x:xs) = case parse x of-        Done extra a -> E.yield a $ if S.null extra-            then E.Chunks xs-            else E.Chunks (extra:xs)-        Partial parse' -> parseLoop parse' xs-        Fail _ ctx msg -> err ctx msg--    err ctx msg = E.throwError (ParseError ctx msg)--}
Network/HTTP/Conduit/Request.hs view
@@ -42,7 +42,7 @@ import Network.TLS (TLSCertificateUsage (CertificateUsageAccept)) import Network.TLS.Extra (certificateVerifyChain, certificateVerifyDomain) -import Control.Exception (Exception)+import Control.Exception (Exception, SomeException, toException) import Control.Failure (Failure (failure)) import Codec.Binary.UTF8.String (encodeString) import qualified Data.CaseInsensitive as CI@@ -51,7 +51,6 @@ 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@@ -86,7 +85,13 @@     , decompress :: ContentType -> Bool     -- ^ Predicate to specify whether gzipped data should be     -- decompressed on the fly (see 'alwaysDecompress' and-    -- 'browserDecompress').+    -- '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@@ -175,7 +180,12 @@         , method = "GET"         , proxy = Nothing         , rawBody = False-        , decompress = alwaysDecompress+        , 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@@ -211,7 +221,7 @@                 (readDec rest)             x -> error $ "parseUrl1: this should never happen: " ++ show x -data HttpException = StatusCodeException Int L.ByteString+data HttpException = StatusCodeException W.Status W.ResponseHeaders                    | InvalidUrlException String String                    | TooManyRedirects                    | HttpParserException String
Network/HTTP/Conduit/Response.hs view
@@ -3,16 +3,14 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} module Network.HTTP.Conduit.Response-    ( lbsConsumer-    , Response (..)-    , ResponseConsumer+    ( Response (..)     , getResponse+    , lbsResponse     ) where  import Control.Arrow (first) import Data.Typeable (Typeable) -import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L @@ -32,47 +30,42 @@ import Network.HTTP.Conduit.Parser import Network.HTTP.Conduit.Chunk ---- | Convert the HTTP response into a 'Response' value.------ Even though a '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 write your own--- iteratee and use 'http' or 'httpRedirect' directly.-lbsConsumer :: ResourceIO m => ResponseConsumer m Response-lbsConsumer (W.Status sc _) hs bsrc = do-    lbs <- fmap L.fromChunks $ bsrc C.$$ CL.consume-    return $ Response sc hs lbs- -- | A simple representation of the HTTP response created by 'lbsConsumer'.-data Response = Response-    { statusCode :: Int+data Response body = Response+    { statusCode :: W.Status     , responseHeaders :: W.ResponseHeaders-    , responseBody :: L.ByteString+    , responseBody :: body     }-    deriving (Show, Read, Eq, Typeable)+    deriving (Show, Eq, Typeable) -type ResponseConsumer m a-    = W.Status-   -> W.ResponseHeaders-   -> C.BufferedSource m S.ByteString-   -> ResourceT m a+-- | 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-            => Request m-            -> ResponseConsumer m a+            => ConnRelease m+            -> Request m             -> C.BufferedSource m S8.ByteString-            -> ResourceT m (WithConnResponse a)-getResponse req@(Request {..}) bodyStep bsrc = do+            -> 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-    res <- if hasNoBody method sc || mcl == Just 0+    body <- if hasNoBody method sc || mcl == Just 0         then do-            bsrcNull <- C.bufferSource $ CL.sourceList []-            bodyStep s hs' bsrcNull+            -- FIXME clean up socket+            C.bufferSource $ CL.sourceList []         else do             bsrc' <-                 if ("transfer-encoding", "chunked") `elem` hs'@@ -81,14 +74,31 @@                         case mcl of                             Just len -> C.bufferSource $ bsrc C.$= CB.isolate len                             Nothing  -> return bsrc-            bsrc'' <--                if needsGunzip req hs'-                    then C.bufferSource $ bsrc' C.$= CZ.ungzip-                    else return bsrc'-            bodyStep s hs' bsrc''-            -- FIXME this is causing hangs, need to look into it bsrc C.$$ CL.sinkNull-            -- Most likely just need to flush the actual buffer+            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'-    return $ WithConnResponse (if toPut then Reuse else DontReuse) res+    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
@@ -15,8 +15,7 @@ import qualified Data.Text as T import qualified Data.Text.Read -#if 1--- FIXME MIN_VERSION_base(4,3,0)+#if MIN_VERSION_base(4,3,0) import Data.ByteString (hGetSome) #else import GHC.IO.Handle.Types
http-conduit.cabal view
@@ -1,5 +1,5 @@ name:            http-conduit-version:         1.0.0.1+version:         1.1.0 license:         BSD3 license-file:    LICENSE author:          Michael Snoyman <michael@snoyman.com>@@ -11,11 +11,8 @@ stability:       Stable cabal-version:   >= 1.6 build-type:      Simple-homepage:        http://github.com/snoyberg/http-enumerator+homepage:        http://github.com/snoyberg/http-conduit -flag test-  description: Build the test executable.-  default: False flag network-bytestring   default: False @@ -31,7 +28,6 @@                  , attoparsec            >= 0.8.0.2 && < 0.11                  , utf8-string           >= 0.3.4   && < 0.4                  , blaze-builder         >= 0.2.1   && < 0.4-                 , zlib-enum             >= 0.2     && < 0.3                  , http-types            >= 0.6     && < 0.7                  , cprng-aes             >= 0.2     && < 0.3                  , tls                   >= 0.8.1   && < 0.9@@ -61,13 +57,6 @@                      Network.HTTP.Conduit.Response     ghc-options:     -Wall -executable http-conduit-    main-is: test.hs-    if flag(test)-        Buildable: True-    else-        Buildable: False- source-repository head   type:     git-  location: git://github.com/snoyberg/http-enumerator.git+  location: git://github.com/snoyberg/http-conduit.git
− test.hs
@@ -1,37 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}-import Network.HTTP.Conduit-import Network-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import System.Environment.UTF8 (getArgs)-import Data.CaseInsensitive (original)-import Data.Conduit-import Control.Monad.IO.Class--main :: IO ()-main = withSocketsDo $ do-    [url] <- getArgs-    _req2 <- parseUrl url-    let req = {-urlEncodedBody-                [ ("foo", "bar")-                , ("baz%%38**.8fn", "bin")-                ]-} _req2-                    { method = "OPTIONS"-                    }-    runResourceT $ do-        man <- newManager-        Response sc hs b <- httpLbsRedirect req man-#if DEBUG-        return ()-#else-        liftIO $ do-            print sc-            mapM_ (\(x, y) -> do-                S.putStr $ original x-                putStr ": "-                S.putStr y-                putStrLn "") hs-            putStrLn ""-            L.putStr b-#endif