packages feed

wreq 0.3.0.0 → 0.3.0.1

raw patch · 14 files changed

+266/−36 lines, 14 filesdep ~http-client

Dependency ranges changed: http-client

Files

Network/Wreq.hs view
@@ -37,6 +37,10 @@ module Network.Wreq     (     -- * HTTP verbs++    -- ** Sessions+    -- $session+     -- ** GET       get     , getWith@@ -525,6 +529,20 @@            -> String         -- ^ The body for this 'Form.Part'.            -> Form.Part partString name value = Form.partBS name (encodeUtf8 (T.pack value))++-- $session+--+-- The basic HTTP functions ('get', 'post', and so on) in this module+-- have a few key drawbacks:+--+-- * If several requests go to the same server, there is no reuse of+--   TCP connections.+--+-- * There is no management of cookies across multiple requests.+--+-- This makes these functions inefficient and verbose for many common+-- uses.  For greater efficiency, use the "Network.Wreq.Session"+-- module.  -- $cookielenses --
Network/Wreq/Internal/Lens.hs view
@@ -30,6 +30,7 @@     , assoc     , assoc2     , setHeader+    , maybeSetHeader     , deleteKey     ) where @@ -55,8 +56,17 @@ assoc2 k f = fmap (uncurry ((++) . fmap ((,) k))) .              _1 (f . fmap snd) . partition ((==k) . fst) +-- | Set a header to the given value, replacing any prior value. setHeader :: HeaderName -> S.ByteString -> Request -> Request setHeader name value = requestHeaders %~ ((name,value) :) . deleteKey name++-- | Set a header to the given value, but only if the header was not+-- already set.+maybeSetHeader :: HeaderName -> S.ByteString -> Request -> Request+maybeSetHeader name value = requestHeaders %~+  \hdrs -> case lookup name hdrs of+             Just _  -> hdrs+             Nothing -> (name,value) : hdrs  deleteKey :: (Eq a) => a -> [(a,b)] -> [(a,b)] deleteKey key = filter ((/= key) . fst)
Network/Wreq/Internal/Types.hs view
@@ -149,13 +149,13 @@   -- etc.), this field will be used only for the /first/ HTTP request   -- to be issued during a 'Network.Wreq.Session.Session'. Any changes   -- changes made for subsequent requests will be ignored.-  , checkStatus :: +  , checkStatus ::     Maybe (Status -> ResponseHeaders -> CookieJar -> Maybe SomeException)   -- ^ Function that checks the status code and potentially returns an exception.   --   -- This defaults to 'Nothing', which will just use the default of   -- 'Network.HTTP.Client.Request' which throws a 'StatusException' if the status-  -- is not 2XX. +  -- is not 2XX.   } deriving (Typeable)  -- | Supported authentication types.@@ -255,6 +255,10 @@ -- | A request that is ready to be submitted. data Req = Req Mgr Request +-- | Return the URL associated with the given 'Req'.+--+-- This includes the port number if not standard, and the query string+-- if one exists. reqURL :: Req -> S.ByteString reqURL (Req _ req) = mconcat [     if https then "https" else "http"@@ -275,7 +279,8 @@ -- response. type Run body = Req -> IO (Response body) --- | A session that spans multiple requests.+-- | A session that spans multiple requests.  This is responsible for+-- cookie management and TCP connection reuse. data Session = Session {       seshCookies :: MVar CookieJar     , seshManager :: Manager
Network/Wreq/Lens.hs view
@@ -167,7 +167,7 @@ -- Example (note the use of TLS): -- -- @---let opts = 'Network.Wreq.defaults' 'Control.Lens.&' 'Lens.auth' 'Control.Lens..~' 'Network.Wreq.basicAuth' \"user\" \"pass\"+--let opts = 'Network.Wreq.defaults' 'Control.Lens.&' 'Lens.auth' 'Control.Lens.?~' 'Network.Wreq.basicAuth' \"user\" \"pass\" --'Network.Wreq.getWith' opts \"https:\/\/httpbin.org\/basic-auth\/user\/pass\" -- @ auth :: Lens' Options (Maybe Auth)@@ -232,6 +232,10 @@ checkStatus = TH.checkStatus  -- | A traversal onto the cookie with the given name, if one exists.+--+-- N.B. This is an \"illegal\" 'Traversal'': we can change the+-- 'cookieName' of the associated 'Cookie' so that it differs from the+-- name provided to this function. cookie :: ByteString -> Traversal' Options Cookie cookie = TH.cookie 
Network/Wreq/Session.hs view
@@ -1,5 +1,42 @@ {-# LANGUAGE RankNTypes, RecordWildCards #-} +-- |+-- Module      : Network.Wreq+-- Copyright   : (c) 2014 Bryan O'Sullivan+--+-- License     : BSD-style+-- Maintainer  : bos@serpentine.com+-- Stability   : experimental+-- Portability : GHC+--+-- The functions in this module use a 'Session' to handle the+-- following common needs:+--+-- * TCP connection reuse.  This is important for performance when+--   multiple requests go to a single server, particularly if TLS is+--   being used.+--+-- * Transparent cookie management.  Any cookies set by the server+--   persist from one request to the next.+--+--+-- This module is designed to be used alongside the "Network.Wreq"+-- module.  Typical usage will look like this:+--+-- @+-- import "Network.Wreq"+-- import qualified "Network.Wreq.Session" as Sess+--+-- main = Sess.'withSession' $ \\sess ->+--   Sess.'get' sess \"http:\/\/httpbin.org\/get\"+-- @+--+-- We create a 'Session' using 'withSession', then pass the session to+-- subsequent functions.+--+-- Note the use of a qualified import statement, so that we can refer+-- unambiguously to the 'Session'-specific implementation of HTTP GET.+ module Network.Wreq.Session     (       Session@@ -35,9 +72,12 @@ import qualified Network.Wreq as Wreq import qualified Network.Wreq.Internal.Lens as Lens +-- | Create a 'Session', passing it to the given function.  The+-- 'Session' will no longer be valid after that function returns. withSession :: (Session -> IO a) -> IO a withSession = withSessionWith defaultManagerSettings +-- | Create a session, using the given manager settings. withSessionWith :: HTTP.ManagerSettings -> (Session -> IO a) -> IO a withSessionWith settings act = do   mv <- newMVar $ HTTP.createCookieJar []@@ -47,42 +87,54 @@                 , seshRun = runWith                 } +-- | 'Session'-specific version of 'Network.Wreq.get'. get :: Session -> String -> IO (Response L.ByteString) get = getWith defaults +-- | 'Session'-specific version of 'Network.Wreq.post'. post :: Postable a => Session -> String -> a -> IO (Response L.ByteString) post = postWith defaults +-- | 'Session'-specific version of 'Network.Wreq.head_'. head_ :: Session -> String -> IO (Response ()) head_ = headWith defaults +-- | 'Session'-specific version of 'Network.Wreq.options'. options :: Session -> String -> IO (Response ()) options = optionsWith defaults +-- | 'Session'-specific version of 'Network.Wreq.put'. put :: Putable a => Session -> String -> a -> IO (Response L.ByteString) put = putWith defaults +-- | 'Session'-specific version of 'Network.Wreq.delete'. delete :: Session -> String -> IO (Response L.ByteString) delete = deleteWith defaults +-- | 'Session'-specific version of 'Network.Wreq.getWith'. getWith :: Options -> Session -> String -> IO (Response L.ByteString) getWith opts sesh url = run string sesh =<< prepareGet opts url +-- | 'Session'-specific version of 'Network.Wreq.postWith'. postWith :: Postable a => Options -> Session -> String -> a          -> IO (Response L.ByteString) postWith opts sesh url payload =   run string sesh =<< preparePost opts url payload +-- | 'Session'-specific version of 'Network.Wreq.headWith'. headWith :: Options -> Session -> String -> IO (Response ()) headWith opts sesh url = run ignore sesh =<< prepareHead opts url +-- | 'Session'-specific version of 'Network.Wreq.optionsWith'. optionsWith :: Options -> Session -> String -> IO (Response ()) optionsWith opts sesh url = run ignore sesh =<< prepareOptions opts url +-- | 'Session'-specific version of 'Network.Wreq.putWith'. putWith :: Putable a => Options -> Session -> String -> a         -> IO (Response L.ByteString) putWith opts sesh url payload = run string sesh =<< preparePut opts url payload +-- | 'Session'-specific version of 'Network.Wreq.deleteWith'. deleteWith :: Options -> Session -> String -> IO (Response L.ByteString) deleteWith opts sesh url = run string sesh =<< prepareDelete opts url 
Network/Wreq/Types.hs view
@@ -142,5 +142,5 @@  payload :: S.ByteString -> HTTP.RequestBody -> Request -> IO Request payload ct body req = AWS.addTmpPayloadHashHeader $ req-                    & Lens.setHeader "Content-Type" ct+                    & Lens.maybeSetHeader "Content-Type" ct                     & Lens.requestBody .~ body
− changelog
@@ -1,19 +0,0 @@--*- markdown -*---2014-12-02 0.3.0.0--* Support for Amazon Web Services request signing--* New customMethod, customMethodWith functions allow use of arbitrary-  HTTP verbs--* httpProxy, basicAuth, oauth2Bearer, oauth2Token: removed Maybe from-  result types, changed documentation to suggest use of (?~)--2014-08-25 0.2.0.0--* Support for lens 4.4--2014-04-22 0.1.0.0--* Initial release.
+ changelog.md view
@@ -0,0 +1,23 @@+-*- markdown -*-++2014-12-11 0.3.0.1++* Bump lower bound on http-client to 0.3.0.1++2014-12-02 0.3.0.0++* Support for Amazon Web Services request signing++* New customMethod, customMethodWith functions allow use of arbitrary+  HTTP verbs++* httpProxy, basicAuth, oauth2Bearer, oauth2Token: removed Maybe from+  result types, changed documentation to suggest use of (?~)++2014-08-25 0.2.0.0++* Support for lens 4.4++2014-04-22 0.1.0.0++* Initial release.
+ examples/SimpleSession.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}++import Control.Lens+import Network.Wreq+import qualified Network.Wreq.Session as S++main :: IO ()+main = S.withSession $ \sess -> do+  -- Our first request causes the httpbin.org server to set a cookie+  -- in its response.++  S.get sess "http://httpbin.org/cookies/set?name=hi"++  -- The session value manages both cookies and HTTP connection reuse+  -- for us.  When we issue the second request, it should+  -- transparently reuse the same connection, and also send the+  -- cookies that we set during the first request.++  r2 <- S.post sess "http://httpbin.org/post" ["a" := (3 :: Int)]++  -- And here's where we verify that the cookie is still set on the+  -- second request.++  print $ r2 ^. responseCookie "name" . cookieValue
examples/wreq-examples.cabal view
@@ -26,6 +26,17 @@     text,     wreq ++executable wreq-example-simple-session+  main-is:        SimpleSession.hs+  ghc-options:    -Wall -fwarn-tabs -threaded+  default-language: Haskell98++  build-depends:+    base >= 4.5 && < 5,+    lens,+    wreq+ executable upload-paste   main-is:          UploadPaste.hs   ghc-options:      -Wall -fwarn-tabs -threaded
tests/UnitTests.hs view
@@ -11,7 +11,7 @@ import Control.Exception (Exception, toException) import Control.Lens ((^.), (^?), (.~), (?~), (&)) import Control.Monad (unless, void)-import Data.Aeson (Value(..), object)+import Data.Aeson import Data.Aeson.Lens (key) import Data.ByteString (ByteString) import Data.Char (toUpper)@@ -113,6 +113,41 @@   r <- put (site "/put") ("wibble" :: ByteString)   assertEqual "PUT succeeds" status200 (r ^. responseStatus) +data SolrAdd = SolrAdd+    { doc          :: String+    , boost        :: Float+    , overwrite    :: Bool+    , commitWithin :: Integer+    }++instance ToJSON SolrAdd where+    toJSON (SolrAdd doc boost overwrite commitWithin) =+        object+            [+                "add" .= object+                [ "doc" .= toJSON doc+                , "boost" .= boost+                , "overwrite" .= overwrite+                , "commitWithin" .= commitWithin+                ]+            ]++solrAdd :: SolrAdd+solrAdd = SolrAdd "wibble" 1.0 True 10000++jsonPut Verb{..} site = do+  r <- put (site "/put") $ toJSON solrAdd+  assertEqual "toJSON PUT request has correct Content-Type header"+    (Just "application/json")+    (r ^. responseBody ^? key "headers" . key "Content-Type")++byteStringPut Verb{..} site = do+  let opts = defaults & header "Content-Type" .~ ["application/json"]+  r <- putWith opts (site "/put") $ encode solrAdd+  assertEqual "ByteString PUT request has correct Content-Type header"+    (Just "application/json")+    (r ^. responseBody ^? key "headers" . key "Content-Type")+ basicDelete Verb{..} site = do   r <- delete (site "/delete")   assertEqual "DELETE succeeds" status200 (r ^. responseStatus)@@ -181,16 +216,16 @@   r <- getWith opts (site "/status/404")   assertThrows "Non 404 throws error" inspect $     getWith opts (site "/get")-  assertEqual "Status 404" +  assertEqual "Status 404"     404     (r ^. responseStatus . statusCode)-  where -    customCs (Status 404 _) _ _ = Nothing +  where+    customCs (Status 404 _) _ _ = Nothing     customCs s h cj             = Just . toException . StatusCodeException s h $ cj      inspect e = case e of-      (StatusCodeException (Status sc _) _ _) -> -        assertEqual "200 Status Error" sc 200 +      (StatusCodeException (Status sc _) _ _) ->+        assertEqual "200 Status Error" sc 200  getGzip Verb{..} site = do   r <- get (site "/gzip")@@ -266,6 +301,8 @@     , testCase "params" $ getParams verb site     , testCase "headers" $ getHeaders verb site     , testCase "gzip" $ getGzip verb site+    , testCase "json put" $ jsonPut verb site+    , testCase "bytestring put" $ byteStringPut verb site     , testCase "cookiesSet" $ cookiesSet verb site     , testCase "getWithManager" $ getWithManager site     , testCase "cookieSession" $ cookieSession site
wreq.cabal view
@@ -1,5 +1,5 @@ name:                wreq-version:             0.3.0.0+version:             0.3.0.1 synopsis:            An easy-to-use HTTP client library. description:   .@@ -40,7 +40,7 @@ extra-source-files:   README.md   TODO.md-  changelog+  changelog.md   examples/*.cabal   examples/*.hs   www/*.css@@ -100,7 +100,7 @@     exceptions >= 0.5,     ghc-prim,     hashable,-    http-client >= 0.4.3,+    http-client >= 0.4.6,     http-client-tls >= 0.2,     http-types >= 0.8,     lens >= 4.5,
www/index.md view
@@ -34,13 +34,24 @@  # Whirlwind tour +All of the examples that follow assume that you are using the+[`OverloadedStrings`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/type-class-extensions.html#overloaded-strings)+language extension, which you can enable in `ghci` as follows:+ ~~~~ {.haskell}+ghci> :set -XOverloadedStrings+~~~~++And now let's get started.++~~~~ {.haskell} ghci> import Network.Wreq ghci> r <- get "http://httpbin.org/get" ~~~~ -Its `lens`-based API is easy to learn (the tutorial walks you through-the [basics of lenses](tutorial.html#a-quick-lens-backgrounder) and+The `wreq` library's `lens`-based API is easy to learn (the tutorial+walks you through the+[basics of lenses](tutorial.html#a-quick-lens-backgrounder)) and powerful to work with.  ~~~~ {.haskell}@@ -55,7 +66,7 @@ ~~~~ {.haskell} ghci> let opts = defaults & param "q" .~ ["tetris"]                           & param "language" .~ ["haskell"]-ghci> r <- getWith opts "https://api.github.com/search/code"+ghci> r <- getWith opts "https://api.github.com/search/repositories" ~~~~  Haskell-to-JSON interoperation is seamless.
www/tutorial.md view
@@ -414,7 +414,10 @@ "bar" ~~~~ +To make cookies even easier to deal with, we'll want to+[use the `Session` API](#session), but we'll come back to that later. + # Authentication  The `wreq` library supports both basic authentication and OAuth2@@ -540,3 +543,54 @@ authentication during a session, to avoid the need for an unauthenticated failure followed by an authenticated success if we visit the same endpoint repeatedly.)+++# Handling multiple HTTP requests++<a id="session">For non-trivial applications</a>, we'll always want to+use a+[`Session`](http://hackage.haskell.org/package/wreq/docs/Network-Wreq-Session.html#t:Session)+to efficiently and correctly handle multiple requests.++The `Session` API provides two important features:++* When we issue multiple HTTP requests to the same server, a `Session`+  will reuse TCP and TLS connections for us.  (The simpler API we've+  discussed so far does not do this.)  This greatly improves+  efficiency.++* A `Session` transparently manages HTTP cookies.  (We can manage them+  by hand, but it's awkward and verbose, so we won't cover it in this+  tutorial.)++Here's a complete example.++~~~~ {.haskell}+{-# LANGUAGE OverloadedStrings #-}++import Control.Lens+import Network.Wreq+import qualified Network.Wreq.Session as S++main :: IO ()+main = S.withSession $ \sess -> do+  -- First request: tell the server to set a cookie+  S.get sess "http://httpbin.org/cookies/set?name=hi"++  -- Second request: the cookie should still be set afterwards.+  r <- S.post sess "http://httpbin.org/post" ["a" := (3 :: Int)]+  print $ r ^. responseCookie "name" . cookieValue+~~~~++The key differences from the basic API are as follows.++* We import the+  [`Network.Wreq.Session`](http://hackage.haskell.org/package/wreq/docs/Network-Wreq-Session.html)+  module qualified, and we'll identify its functions by prefixing them+  with "`S.`".++* To create a `Session`, we use `S.withSession`. It calls our code+  with `sess`, the `Session` value we'll use.++* Instead of `get` and `post`, we call the `Session`-specific+  versions, [`S.get`](http://hackage.haskell.org/package/wreq/docs/Network-Wreq-Session.html#v:get) and [`S.post`](http://hackage.haskell.org/package/wreq/docs/Network-Wreq-Session.html#v:post), and pass `sess` to each of them.