http-conduit 1.6.0.1 → 1.6.0.2
raw patch · 4 files changed
+180/−6 lines, 4 filesdep ~bytestringdep ~resourcetdep ~transformersPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: bytestring, resourcet, transformers, utf8-string
API changes (from Hackage documentation)
Files
- Network/HTTP/Conduit/Browser.hs +57/−0
- Network/HTTP/Conduit/Manager.hs +2/−1
- http-conduit.cabal +5/−5
- test/main.hs +116/−0
Network/HTTP/Conduit/Browser.hs view
@@ -1,4 +1,61 @@ {-# LANGUAGE CPP #-}+-- | This module is designed to work similarly to the Network.Browser module in the HTTP package.+-- The idea is that there are two new types defined: 'BrowserState' and 'BrowserAction'. The+-- purpose of this module is to make it easy to describe a browsing session, including navigating+-- to multiple pages, and have things like cookie jar updates work as expected as you browse+-- around.+--+-- BrowserAction is a monad that handles all your browser-related activities. This monad is+-- actually implemented as a specialization of the State monad, over the BrowserState type. The+-- BrowserState type has various bits of information that a web browser keeps, such as a current+-- cookie jar, the number of times to retry a request on failure, HTTP proxy information, etc. In+-- the BrowserAction monad, there is one BrowserState at any given time, and you can modify it by+-- using the convenience functions in this module.+--+-- A special kind of modification of the current browser state is the action of making a HTTP+-- request. This will do the request according to the params in the current BrowserState, as well+-- as modifying the current state with, for example, an updated cookie jar.+--+-- To use this module, you would bind together a series of BrowserActions (This simulates the user+-- clicking on links or using a settings dialogue etc.) to describe your browsing session. When+-- you've described your session, you call 'browse' on your top-level BrowserAction to actually+-- convert your actions into the ResourceT IO monad.+--+-- Here is an example program:+--+-- > import qualified Data.ByteString as B+-- > import qualified Data.ByteString.Lazy as LB+-- > import qualified Data.ByteString.UTF8 as UB+-- > import Data.Conduit+-- > import Network.HTTP.Conduit+-- > import Network.HTTP.Conduit.Browser+-- > +-- > -- The web request to log in to a service+-- > req1 :: IO (Request (ResourceT IO))+-- > req1 = do+-- > req <- parseUrl "http://www.myurl.com/login.php"+-- > return $ urlEncodedBody [ (UB.fromString "name", UB.fromString "litherum")+-- > , (UB.fromString "pass", UB.fromString "S33kRe7")+-- > ] req+-- > +-- > -- Once authenticated, run this request+-- > req2 :: IO (Request m')+-- > req2 = parseUrl "http://www.myurl.com/main.php"+-- > +-- > -- Bind two BrowserActions together+-- > action :: Request (ResourceT IO) -> Request (ResourceT IO) -> BrowserAction (Response LB.ByteString)+-- > action r1 r2 = do+-- > _ <- makeRequestLbs r1+-- > makeRequestLbs r2+-- > +-- > main :: IO ()+-- > main = do+-- > man <- newManager def+-- > r1 <- req1+-- > r2 <- req2+-- > out <- runResourceT $ browse man $ action r1 r2+-- > putStrLn $ UB.toString $ B.concat $ LB.toChunks $ responseBody out+ module Network.HTTP.Conduit.Browser ( BrowserState , BrowserAction
Network/HTTP/Conduit/Manager.hs view
@@ -37,8 +37,9 @@ import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Exception (mask_, SomeException, catch) import Control.Monad.Trans.Resource- ( ResourceT, runResourceT, MonadResource (..)+ ( ResourceT, runResourceT, MonadResource , MonadThrow, MonadUnsafeIO+ , allocate, resourceMask, register, release ) import Control.Concurrent (forkIO, threadDelay) import Data.Time (UTCTime (..), Day (..), DiffTime, getCurrentTime, addUTCTime)
http-conduit.cabal view
@@ -1,5 +1,5 @@ name: http-conduit-version: 1.6.0.1+version: 1.6.0.2 license: BSD3 license-file: LICENSE author: Michael Snoyman <michael@snoyman.com>@@ -19,16 +19,16 @@ library build-depends: base >= 4 && < 5- , bytestring >= 0.9.1.4 && < 0.11- , transformers >= 0.2 && < 0.4+ , bytestring >= 0.9.1.4+ , transformers >= 0.2 , failure >= 0.1- , resourcet >= 0.3 && < 0.4+ , resourcet >= 0.3 && < 0.5 , conduit >= 0.5 && < 0.6 , zlib-conduit >= 0.5 && < 0.6 , blaze-builder-conduit >= 0.5 && < 0.6 , attoparsec-conduit >= 0.5 && < 0.6 , attoparsec >= 0.8.0.2 && < 0.11- , utf8-string >= 0.3.4 && < 0.4+ , utf8-string >= 0.3.4 , blaze-builder >= 0.2.1 && < 0.4 , http-types >= 0.6 && < 0.8 , cprng-aes >= 0.2 && < 0.3
test/main.hs view
@@ -9,6 +9,8 @@ import qualified Network.Wai import Network.Wai.Handler.Warp (run) import Network.HTTP.Conduit+import Network.HTTP.Conduit.Browser+import Data.ByteString.Base64 (encode) import Control.Concurrent (forkIO, killThread, threadDelay) import Network.HTTP.Types import Control.Exception.Lifted (try, SomeException)@@ -27,14 +29,40 @@ import qualified Data.ByteString.Lazy as L import Blaze.ByteString.Builder (fromByteString) +strictToLazy :: S.ByteString -> L.ByteString+strictToLazy = L.fromChunks . replicate 1++lazyToStrict :: L.ByteString -> S.ByteString+lazyToStrict = S.concat . L.toChunks++dummy :: S.ByteString+dummy = "dummy"++user :: S.ByteString+user = "user"++pass :: S.ByteString+pass = "pass"+ app :: Application app req = case pathInfo req of [] -> return $ responseLBS status200 [] "homepage" ["cookies"] -> return $ responseLBS status200 [tastyCookie] "cookies"+ ["print-cookies"] -> return $ responseLBS status200 [] $ getHeader "Cookie"+ ["useragent"] -> return $ responseLBS status200 [] $ getHeader "User-Agent"+ ["authorities"] -> return $ responseLBS status200 [] $ getHeader "Authorization"+ ["redir1"] -> return $ responseLBS temporaryRedirect307 [redir2] L.empty+ ["redir2"] -> return $ responseLBS temporaryRedirect307 [redir3] L.empty+ ["redir3"] -> return $ responseLBS status200 [] $ strictToLazy dummy _ -> return $ responseLBS status404 [] "not found" where tastyCookie = (mk (fromString "Set-Cookie"), fromString "flavor=chocolate-chip;")+ getHeader s = strictToLazy $ case lookup s $ Network.Wai.requestHeaders req of+ Just a -> a+ Nothing -> S.empty+ redir2 = (mk (fromString "Location"), fromString "/redir2")+ redir3 = (mk (fromString "Location"), fromString "/redir3") main :: IO () main = hspecX $ do@@ -52,6 +80,94 @@ case elbs of Left (_ :: SomeException) -> return () Right _ -> error "Expected an exception"+ describe "browser" $ do+ it "cookie jar works" $ do+ tid <- forkIO $ run 3011 app+ request1 <- parseUrl "http://127.0.0.1:3011/cookies"+ request2 <- parseUrl "http://127.0.0.1:3011/print-cookies"+ elbs <- withManager $ \manager -> do+ browse manager $ do+ _ <- makeRequestLbs request1+ makeRequestLbs request2+ killThread tid+ if (lazyToStrict $ responseBody elbs) /= fromString "flavor=chocolate-chip"+ then error "Should have gotten the cookie back!"+ else return ()+ it "cookie filter can deny cookies" $ do+ tid <- forkIO $ run 3011 app+ request1 <- parseUrl "http://127.0.0.1:3011/cookies"+ request2 <- parseUrl "http://127.0.0.1:3011/print-cookies"+ elbs <- withManager $ \manager -> do+ browse manager $ do+ setCookieFilter $ const $ const $ return False+ _ <- makeRequestLbs request1+ makeRequestLbs request2+ killThread tid+ if (lazyToStrict $ responseBody elbs) /= S.empty+ then error "Shouldn't have gotten the cookie back!"+ else return ()+ it "can save and load cookie jar" $ do+ tid <- forkIO $ run 3011 app+ request1 <- parseUrl "http://127.0.0.1:3011/cookies"+ request2 <- parseUrl "http://127.0.0.1:3011/print-cookies"+ (elbs1, elbs2) <- withManager $ \manager -> do+ browse manager $ do+ _ <- makeRequestLbs request1+ cookie_jar <- getCookieJar+ setCookieJar def+ elbs1 <- makeRequestLbs request2+ setCookieJar cookie_jar+ elbs2 <- makeRequestLbs request2+ return (elbs1, elbs2)+ killThread tid+ if (((lazyToStrict $ responseBody elbs1) /= S.empty) ||+ ((lazyToStrict $ responseBody elbs2) /= fromString "flavor=chocolate-chip"))+ then error "Cookie jar got garbled up!"+ else return ()+ it "user agent sets correctly" $ do+ tid <- forkIO $ run 3012 app+ request <- parseUrl "http://127.0.0.1:3012/useragent"+ elbs <- withManager $ \manager -> do+ browse manager $ do+ setUserAgent $ fromString "abcd"+ makeRequestLbs request+ killThread tid+ if (lazyToStrict $ responseBody elbs) /= fromString "abcd"+ then error "Should have gotten the user agent back!"+ else return ()+ it "authorities get set correctly" $ do+ tid <- forkIO $ run 3013 app+ request <- parseUrl "http://127.0.0.1:3013/authorities"+ elbs <- withManager $ \manager -> do+ browse manager $ do+ setAuthorities $ const $ Just (user, pass)+ makeRequestLbs request+ killThread tid+ if (lazyToStrict $ responseBody elbs) /= (fromString "Basic " `S.append` (encode $ user `S.append` ":" `S.append` pass))+ then error "Authorities didn't get set correctly!"+ else return ()+ it "can follow redirects" $ do+ tid <- forkIO $ run 3014 app+ request <- parseUrl "http://127.0.0.1:3014/redir1"+ elbs <- withManager $ \manager -> do+ browse manager $ do+ setMaxRedirects 2+ makeRequestLbs request+ killThread tid+ if (lazyToStrict $ responseBody elbs) /= dummy+ then error "Should be able to follow 2 redirects"+ else return ()+ it "max redirects fails correctly" $ do+ tid <- forkIO $ run 3015 app+ request <- parseUrl "http://127.0.0.1:3015/redir1"+ elbs <- try $ withManager $ \manager -> do+ browse manager $ do+ setMaxRedirects 1+ makeRequestLbs request+ killThread tid+ case elbs of+ Left (TooManyRedirects _) -> return ()+ _ -> error "Shouldn't have followed all those redirects!" describe "httpLbs" $ do it "preserves 'set-cookie' headers" $ do tid <- forkIO $ run 3010 app