http-client 0.3.2.1 → 0.3.2.2
raw patch · 7 files changed
+76/−111 lines, 7 files
Files
- Network/HTTP/Client.hs +1/−1
- Network/HTTP/Client/Body.hs +47/−59
- Network/HTTP/Client/Response.hs +2/−10
- Network/HTTP/Client/Types.hs +8/−12
- http-client.cabal +1/−1
- test/Network/HTTP/Client/BodySpec.hs +16/−28
- test/Network/HTTP/ClientSpec.hs +1/−0
Network/HTTP/Client.hs view
@@ -43,7 +43,7 @@ -- A note on exceptions: for the most part, all actions that perform I/O should -- be assumed to throw an @HttpException@ in the event of some problem, and all -- pure functions will be total. For example, @withResponse@, @httpLbs@, and--- @brRead@ can all throw exceptions. Functions like @responseStatus@ and+-- @BodyReader@ can all throw exceptions. Functions like @responseStatus@ and -- @applyBasicAuth@ are guaranteed to be total (or there\'s a bug in the -- library). --
Network/HTTP/Client/Body.hs view
@@ -9,6 +9,7 @@ , brEmpty , brAddCleanup , brReadSome+ , brRead ) where import Network.HTTP.Client.Connection@@ -21,8 +22,15 @@ import Control.Monad (unless, when) import qualified Data.Streaming.Zlib as Z +-- ^ Get a single chunk of data from the response body, or an empty+-- bytestring if no more data is available.+--+-- Since 0.1.0+brRead :: BodyReader -> IO S.ByteString+brRead = id+ brReadSome :: BodyReader -> Int -> IO L.ByteString-brReadSome BodyReader {..} =+brReadSome brRead = loop id where loop front rem@@ -34,35 +42,29 @@ else loop (front . (bs:)) (rem - S.length bs) brEmpty :: BodyReader-brEmpty = BodyReader- { brRead = return S.empty- , brComplete = return True- }+brEmpty = return S.empty brAddCleanup :: IO () -> BodyReader -> BodyReader-brAddCleanup cleanup br = BodyReader- { brRead = do- bs <- brRead br- when (S.null bs) cleanup- return bs- , brComplete = brComplete br- }+brAddCleanup cleanup brRead = do+ bs <- brRead+ when (S.null bs) cleanup+ return bs -- | Strictly consume all remaining chunks of data from the stream. -- -- Since 0.1.0 brConsume :: BodyReader -> IO [S.ByteString]-brConsume f =+brConsume brRead = go id where go front = do- x <- brRead f+ x <- brRead if S.null x then return $ front [] else go (front . (x:)) makeGzipReader :: BodyReader -> IO BodyReader-makeGzipReader br = do+makeGzipReader brRead = do inf <- Z.initInflate $ Z.WindowBits 31 istate <- newIORef Nothing let goPopper popper = do@@ -80,69 +82,55 @@ return bs Z.PRError e -> throwIO $ HttpZlibException e start = do- bs <- brRead br+ bs <- brRead if S.null bs then return S.empty else do popper <- Z.feedInflate inf bs goPopper popper- return BodyReader- { brRead = do- state <- readIORef istate- case state of- Nothing -> start- Just popper -> goPopper popper- , brComplete = brComplete br- }+ return $ do+ state <- readIORef istate+ case state of+ Nothing -> start+ Just popper -> goPopper popper makeUnlimitedReader :: Connection -> IO BodyReader makeUnlimitedReader Connection {..} = do icomplete <- newIORef False- return $! BodyReader- { brRead = do- bs <- connectionRead- when (S.null bs) $ writeIORef icomplete True- return bs- , brComplete = readIORef icomplete- }+ return $ do+ bs <- connectionRead+ when (S.null bs) $ writeIORef icomplete True+ return bs makeLengthReader :: Int -> Connection -> IO BodyReader makeLengthReader count0 Connection {..} = do icount <- newIORef count0- return $! BodyReader- { brRead = do- count <- readIORef icount- if count <= 0- then return empty- else do- bs <- connectionRead- when (S.null bs) $ throwIO $ ResponseBodyTooShort (fromIntegral count0) (fromIntegral $ count0 - count)- case compare count $ S.length bs of- LT -> do- let (x, y) = S.splitAt count bs- connectionUnread y- writeIORef icount (-1)- return x- EQ -> do- writeIORef icount (-1)- return bs- GT -> do- writeIORef icount (count - S.length bs)- return bs- , brComplete = fmap (== -1) $ readIORef icount- }+ return $ do+ count <- readIORef icount+ if count <= 0+ then return empty+ else do+ bs <- connectionRead+ when (S.null bs) $ throwIO $ ResponseBodyTooShort (fromIntegral count0) (fromIntegral $ count0 - count)+ case compare count $ S.length bs of+ LT -> do+ let (x, y) = S.splitAt count bs+ connectionUnread y+ writeIORef icount (-1)+ return x+ EQ -> do+ writeIORef icount (-1)+ return bs+ GT -> do+ writeIORef icount (count - S.length bs)+ return bs makeChunkedReader :: Bool -- ^ raw -> Connection -> IO BodyReader makeChunkedReader raw conn@Connection {..} = do icount <- newIORef 0- return $! BodyReader- { brRead = go icount- , brComplete = do- count <- readIORef icount- return $! count == -1- }+ return $ go icount where go icount = do count0 <- readIORef icount
Network/HTTP/Client/Response.hs view
@@ -8,17 +8,13 @@ , lbsResponse ) where -import Control.Arrow (first)-import Control.Monad (liftM, (>=>))+import Control.Monad ((>=>)) import Control.Exception (throwIO) -import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L -import qualified Data.CaseInsensitive as CI- import Data.Default.Class (def) import qualified Network.HTTP.Types as W@@ -26,12 +22,10 @@ import Network.HTTP.Client.Types -import Network.HTTP.Client.Manager import Network.HTTP.Client.Request import Network.HTTP.Client.Util import Network.HTTP.Client.Body import Network.HTTP.Client.Headers-import Network.HTTP.Client.Connection import System.Timeout (timeout) @@ -96,7 +90,7 @@ let timeout' = case timeout'' of Nothing -> id- Just t -> timeout t >=> maybe responseTimeout return+ Just t -> timeout t >=> maybe (throwIO ResponseTimeout) return StatusHeaders s version hs <- timeout' $ parseStatusHeaders conn let mcl = lookup "content-length" hs >>= readDec . S8.unpack @@ -132,5 +126,3 @@ , responseCookieJar = def , responseClose' = ResponseClose (cleanup False) }- where- responseTimeout = throwIO ResponseTimeout
Network/HTTP/Client/Types.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE RankNTypes #-} module Network.HTTP.Client.Types- ( BodyReader (..)+ ( BodyReader , Connection (..) , StatusHeaders (..) , HttpException (..)@@ -50,19 +50,15 @@ import Data.Text (Text) import Data.Streaming.Zlib (ZlibException) --- | An abstraction for representing an incoming response body coming from the--- server. Data provided by this abstraction has already been gunzipped and+-- | An @IO@ action that represents an incoming response body coming from the+-- server. Data provided by this action has already been gunzipped and -- de-chunked, and respects any content-length headers present. ----- Since 0.1.0-data BodyReader = BodyReader- { brRead :: !(IO S.ByteString)- -- ^ Get a single chunk of data from the response body, or an empty- -- bytestring if no more data is available.- --- -- Since 0.1.0- , brComplete :: !(IO Bool)- }+-- The action gets a single chunk of data from the response body, or an empty+-- bytestring if no more data is available.+--+-- Since 0.4.0+type BodyReader = IO S.ByteString data Connection = Connection { connectionRead :: !(IO S.ByteString)
http-client.cabal view
@@ -1,5 +1,5 @@ name: http-client-version: 0.3.2.1+version: 0.3.2.2 synopsis: An HTTP client engine, intended as a base layer for more user-friendly packages. description: This codebase has been refactored from http-conduit. homepage: https://github.com/snoyberg/http-client
test/Network/HTTP/Client/BodySpec.hs view
@@ -11,6 +11,11 @@ main :: IO () main = hspec spec +brComplete :: BodyReader -> IO Bool+brComplete brRead = do+ xs <- brRead+ return (xs == "")+ spec :: Spec spec = describe "BodySpec" $ do it "chunked, single" $ do@@ -18,89 +23,72 @@ [ "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed" ] reader <- makeChunkedReader False conn- complete1 <- brComplete reader- complete1 `shouldBe` False body <- brConsume reader S.concat body `shouldBe` "hello world" input' <- input S.concat input' `shouldBe` "not consumed"- complete2 <- brComplete reader- complete2 `shouldBe` True+ brComplete reader `shouldReturn` True+ it "chunked, pieces" $ do (conn, _, input) <- dummyConnection $ map S.singleton $ S.unpack "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed" reader <- makeChunkedReader False conn- complete1 <- brComplete reader- complete1 `shouldBe` False body <- brConsume reader S.concat body `shouldBe` "hello world" input' <- input S.concat input' `shouldBe` "not consumed"- complete2 <- brComplete reader- complete2 `shouldBe` True+ brComplete reader `shouldReturn` True+ it "chunked, raw" $ do (conn, _, input) <- dummyConnection [ "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed" ] reader <- makeChunkedReader True conn- complete1 <- brComplete reader- complete1 `shouldBe` False body <- brConsume reader S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\n" input' <- input S.concat input' `shouldBe` "not consumed"- complete2 <- brComplete reader- complete2 `shouldBe` True+ brComplete reader `shouldReturn` True it "chunked, pieces, raw" $ do (conn, _, input) <- dummyConnection $ map S.singleton $ S.unpack "5\r\nhello\r\n6\r\n world\r\n0\r\nnot consumed" reader <- makeChunkedReader True conn- complete1 <- brComplete reader- complete1 `shouldBe` False body <- brConsume reader S.concat body `shouldBe` "5\r\nhello\r\n6\r\n world\r\n0\r\n" input' <- input S.concat input' `shouldBe` "not consumed"- complete2 <- brComplete reader- complete2 `shouldBe` True+ brComplete reader `shouldReturn` True it "length, single" $ do (conn, _, input) <- dummyConnection [ "hello world done" ] reader <- makeLengthReader 11 conn- complete1 <- brComplete reader- complete1 `shouldBe` False body <- brConsume reader S.concat body `shouldBe` "hello world" input' <- input S.concat input' `shouldBe` " done"- complete2 <- brComplete reader- complete2 `shouldBe` True+ brComplete reader `shouldReturn` True+ it "length, pieces" $ do (conn, _, input) <- dummyConnection $ map S.singleton $ S.unpack "hello world done" reader <- makeLengthReader 11 conn- complete1 <- brComplete reader- complete1 `shouldBe` False body <- brConsume reader S.concat body `shouldBe` "hello world" input' <- input S.concat input' `shouldBe` " done"- complete2 <- brComplete reader- complete2 `shouldBe` True+ brComplete reader `shouldReturn` True+ it "gzip" $ do let orig = L.fromChunks $ replicate 5000 "Hello world!" origZ = compress orig (conn, _, input) <- dummyConnection $ L.toChunks origZ ++ ["ignored"] reader' <- makeLengthReader (fromIntegral $ L.length origZ) conn reader <- makeGzipReader reader'- complete1 <- brComplete reader- complete1 `shouldBe` False body <- brConsume reader L.fromChunks body `shouldBe` orig input' <- input S.concat input' `shouldBe` "ignored"- complete2 <- brComplete reader- complete2 `shouldBe` True+ brComplete reader `shouldReturn` True
test/Network/HTTP/ClientSpec.hs view
@@ -13,6 +13,7 @@ import Test.Hspec import qualified Data.Streaming.Network as N import qualified Data.ByteString as S+import Data.ByteString.Lazy.Char8 () -- orphan instance main :: IO () main = hspec spec