packages feed

http-client 0.4.8.1 → 0.4.9

raw patch · 7 files changed

+117/−2 lines, 7 filesdep +directory

Dependencies added: directory

Files

ChangeLog.md view
@@ -1,3 +1,7 @@+## 0.4.9++* Add RequestBody smart constructors `streamFile` and `streamFileObserved`, the latter with accompanying type `StreamFileStatus`.+ ## 0.4.8.1  * Automatically call withSocketsDo everywhere [#107](https://github.com/snoyberg/http-client/issues/107)
Network/HTTP/Client.hs view
@@ -146,6 +146,9 @@     , Popper     , NeedsPopper     , GivesPopper+    , streamFile+    , observedStreamFile+    , StreamFileStatus (..)       -- * Response     , Response     , responseStatus
Network/HTTP/Client/MultipartFormData.hs view
@@ -50,7 +50,7 @@     ,renderPart     ) where -import Network.HTTP.Client+import Network.HTTP.Client hiding (streamFile) import Network.Mime import Network.HTTP.Types (hContentType, methodPost, Header()) import Data.Monoid ((<>))
Network/HTTP/Client/Request.hs view
@@ -21,12 +21,16 @@     , requestBuilder     , useDefaultTimeout     , setQueryString+    , streamFile+    , observedStreamFile     ) where +import Data.Int (Int64) import Data.Maybe (fromMaybe, isJust) import Data.Monoid (mempty, mappend) import Data.String (IsString(..)) import Data.Char (toLower)+import Control.Applicative ((<$>)) import Control.Monad (when, unless) import Numeric (showHex) @@ -38,6 +42,7 @@ import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L+import Data.ByteString.Lazy.Internal (defaultChunkSize)  import qualified Network.HTTP.Types as W import Network.URI (URI (..), URIAuth (..), parseURI, relativeTo, escapeURIString, isAllowedInURI)@@ -57,6 +62,9 @@ import Control.Monad.Catch (MonadThrow, throwM) import Data.IORef +import System.IO (withBinaryFile, hTell, hFileSize, Handle, IOMode (ReadMode))++ -- | Convert a URL into a 'Request'. -- -- This defaults some of the values in 'Request', such as setting 'method' to@@ -406,3 +414,40 @@ -- Since 0.3.6 setQueryString :: [(S.ByteString, Maybe S.ByteString)] -> Request -> Request setQueryString qs req = req { queryString = W.renderQuery True qs }++-- | Send a file as the request body.+--+-- It is expected that the file size does not change between calling+-- `streamFile` and making any requests using this request body.+--+-- Since 0.4.9+streamFile :: FilePath -> IO RequestBody+streamFile = observedStreamFile (\_ -> return ())++-- | Send a file as the request body, while observing streaming progress via+-- a `PopObserver`. Observations are made between reading and sending a chunk.+--+-- It is expected that the file size does not change between calling+-- `observedStreamFile` and making any requests using this request body.+--+-- Since 0.4.9+observedStreamFile :: (StreamFileStatus -> IO ()) -> FilePath -> IO RequestBody+observedStreamFile obs path = do+    size <- fromIntegral <$> withBinaryFile path ReadMode hFileSize++    let filePopper :: Handle -> Popper+        filePopper h = do+            bs <- S.hGetSome h defaultChunkSize+            currentPosition <- fromIntegral <$> hTell h+            obs $ StreamFileStatus+                { fileSize = size+                , readSoFar = currentPosition+                , thisChunkSize = S.length bs+                }+            return bs++        givesFilePopper :: GivesPopper ()+        givesFilePopper k = withBinaryFile path ReadMode $ \h -> do+            k (filePopper h)++    return $ RequestBodyStream size givesFilePopper
Network/HTTP/Client/Types.hs view
@@ -28,6 +28,7 @@     , ConnHost (..)     , ConnKey (..)     , ProxyOverride (..)+    , StreamFileStatus (..)     ) where  import qualified Data.Typeable as T (Typeable)@@ -597,4 +598,14 @@ -- | @ConnKey@ consists of a hostname, a port and a @Bool@ -- specifying whether to use SSL. data ConnKey = ConnKey ConnHost Int S.ByteString Int Bool+    deriving (Eq, Show, Ord, T.Typeable)++-- | Status of streaming a request body from a file.+--+-- Since 0.4.9+data StreamFileStatus = StreamFileStatus+    { fileSize :: Int64+    , readSoFar :: Int64+    , thisChunkSize :: Int+    }     deriving (Eq, Show, Ord, T.Typeable)
http-client.cabal view
@@ -1,5 +1,5 @@ name:                http-client-version:             0.4.8.1+version:             0.4.9 synopsis:            An HTTP client engine, intended as a base layer for more user-friendly packages. description:         Hackage documentation generation is not reliable. For up to date documentation, please see: <http://www.stackage.org/package/http-client>. homepage:            https://github.com/snoyberg/http-client@@ -95,6 +95,7 @@                        Network.HTTP.Client.BodySpec                        Network.HTTP.Client.HeadersSpec                        Network.HTTP.Client.RequestSpec+                       Network.HTTP.Client.RequestBodySpec   build-depends:       base                      , http-client                      , hspec@@ -113,3 +114,4 @@                      , zlib                      , async                      , streaming-commons >= 0.1.1+                     , directory
+ test-nonet/Network/HTTP/Client/RequestBodySpec.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}+module Network.HTTP.Client.RequestBodySpec where++import Control.Monad+import Test.Hspec+import Control.Exception+import System.IO+import Data.IORef+import qualified Data.ByteString as BS+import Network.HTTP.Client (streamFile, parseUrl, requestBody)+import Network.HTTP.Client.Internal (dummyConnection, Connection, connectionWrite, requestBuilder)+import System.Directory (getTemporaryDirectory)++spec :: Spec+spec = describe "streamFile" $ it "works" $ withTmpFile $ \(path, h) -> do+    replicateM_ 5000 $ BS.hPut h "Hello, world!\r\n"+    hClose h++    withBinaryFile path ReadMode $ \h' -> do+        conn <- verifyFileConnection h'++        req0 <- parseUrl "http://example.com"+        body <- streamFile path+        let req = req0 { requestBody = body }++        requestBuilder req conn+        hIsEOF h' `shouldReturn` True+  where+    withTmpFile = bracket getTmpFile closeTmpFile+    getTmpFile = do+        tmp <- getTemporaryDirectory+        openBinaryTempFile tmp "request-body-stream-file"+    closeTmpFile (_, h) = hClose h++    firstReadBS = "GET / HTTP/1.1\r\nHost: example.com\r\nAccept-Encoding: gzip\r\nContent-Length: 75000\r\n\r\n"++    verifyFileConnection h = do+        (conn, _, _) <- dummyConnection []+        isFirstReadRef <- newIORef True+        return conn+            { connectionWrite = \bs -> do+                isFirstRead <- readIORef isFirstReadRef+                if isFirstRead+                then do+                    bs `shouldBe` firstReadBS+                    writeIORef isFirstReadRef False+                else do+                    bs' <- BS.hGet h (BS.length bs)+                    bs `shouldBe` bs'+            }