on master help sign in
Integration with retry (#22)
0ddc000Mark Karpov
CHANGELOG.md+18
@@ -3,6 +3,24 @@
3* Made URL parsing functions `parseUrlHttp` and `parseUrlHttps` recognize
3* Made URL parsing functions `parseUrlHttp` and `parseUrlHttps` recognize
4 port numbers.
4 port numbers.
5
5
6+* Added `req'` function that allows to perform requests via a callback that
7+ receives pre-constructed request and manager.
8+
9+* Removed the `ReturnRequest` HTTP response implementation as it was not
10+ quite safe and was not going to work with retrying. Use `req'` instead for
11+ “pure” testing.
12+
13+* Changed the type of `httpConfigCheckResponse`, so the second argument can
14+ be any instance of `HttpResponse`.
15+
16+* Added built-in automatic retrying. See `httpConfigRetryPolicy` and
17+ `httpConfigRetryJudge` in `HttpConfig`. The default configuration retries
18+ 5 times on request timeouts.
19+
20+* Added the `makeResponseBodyPreview` method to the `HttpResponse` type
21+ class that allows to specify how to build a “preview” of response body for
22+ inclusion into exceptions.
23+
6* Improved wording in the documentation and `README.md`.
24* Improved wording in the documentation and `README.md`.
7
25
8## Req 0.2.0
26## Req 0.2.0
Network/HTTP/Req.hs+98−51
@@ -96,6 +96,7 @@
96{-# LANGUAGE GeneralizedNewtypeDeriving #-}
96{-# LANGUAGE GeneralizedNewtypeDeriving #-}
97{-# LANGUAGE KindSignatures #-}
97{-# LANGUAGE KindSignatures #-}
98{-# LANGUAGE OverloadedStrings #-}
98{-# LANGUAGE OverloadedStrings #-}
99+{-# LANGUAGE RankNTypes #-}
99{-# LANGUAGE RecordWildCards #-}
100{-# LANGUAGE RecordWildCards #-}
100{-# LANGUAGE ScopedTypeVariables #-}
101{-# LANGUAGE ScopedTypeVariables #-}
101{-# LANGUAGE TypeFamilies #-}
102{-# LANGUAGE TypeFamilies #-}
@@ -116,6 +117,7 @@ module Network.HTTP.Req
116 ( -- * Making a request
117 ( -- * Making a request
117 -- $making-a-request
118 -- $making-a-request
118 req
119 req
120+ , req'
119 , withReqManager
121 , withReqManager
120 -- * Embedding requests into your monad
122 -- * Embedding requests into your monad
121 -- $embedding-requests
123 -- $embedding-requests
@@ -191,15 +193,12 @@ module Network.HTTP.Req
191 , bsResponse
193 , bsResponse
192 , LbsResponse
194 , LbsResponse
193 , lbsResponse
195 , lbsResponse
194− , ReturnRequest
195− , returnRequest
196 -- ** Inspecting a response
196 -- ** Inspecting a response
197 , responseBody
197 , responseBody
198 , responseStatusCode
198 , responseStatusCode
199 , responseStatusMessage
199 , responseStatusMessage
200 , responseHeader
200 , responseHeader
201 , responseCookieJar
201 , responseCookieJar
202− , responseRequest
203 -- ** Defining your own interpretation
202 -- ** Defining your own interpretation
204 -- $new-response-interpretation
203 -- $new-response-interpretation
205 , HttpResponse (..)
204 , HttpResponse (..)
@@ -211,9 +210,10 @@ where
211
210
212import Control.Applicative
211import Control.Applicative
213import Control.Arrow (first, second)
212import Control.Arrow (first, second)
214−import Control.Exception (Exception, try, catch, throwIO)
213+import Control.Exception (Exception, try, handle, throwIO)
215import Control.Monad
214import Control.Monad
216import Control.Monad.IO.Class
215import Control.Monad.IO.Class
216+import Control.Retry
217import Data.Aeson (ToJSON (..), FromJSON (..))
217import Data.Aeson (ToJSON (..), FromJSON (..))
218import Data.ByteString (ByteString)
218import Data.ByteString (ByteString)
219import Data.Data (Data)
219import Data.Data (Data)
@@ -285,10 +285,8 @@ import GHC.Exts (Constraint)
285-- * 'jsonResponse'
285-- * 'jsonResponse'
286-- * 'bsResponse' (to get a strict 'ByteString')
286-- * 'bsResponse' (to get a strict 'ByteString')
287-- * 'lbsResponse' (to get a lazy 'BL.ByteString')
287-- * 'lbsResponse' (to get a lazy 'BL.ByteString')
288−-- * 'returnRequest' (makes no request, but returns the constructed
289−-- request itself, used for testing).
290--
288--
291−-- Finally @options@ is a 'Monoid' that holds a composite 'Option' for all
289+-- Finally, @options@ is a 'Monoid' that holds a composite 'Option' for all
292-- other optional settings like query parameters, headers, non-standard port
290-- other optional settings like query parameters, headers, non-standard port
293-- number, etc. There are quite a few things you can put there, see the
291-- number, etc. There are quite a few things you can put there, see the
294-- corresponding section in the documentation. If you don't need anything at
292-- corresponding section in the documentation. If you don't need anything at
@@ -388,8 +386,7 @@ import GHC.Exts (Constraint)
388-- > print (responseBody response :: Value)
386-- > print (responseBody response :: Value)
389
387
390req
388req
391− :: forall m method body response scheme.
392− ( MonadHttp m
389+ :: ( MonadHttp m
393 , HttpMethod method
390 , HttpMethod method
394 , HttpBody body
391 , HttpBody body
395 , HttpResponse response
392 , HttpResponse response
@@ -400,8 +397,40 @@ req
400 -> Proxy response -- ^ A hint how to interpret response
397 -> Proxy response -- ^ A hint how to interpret response
401 -> Option scheme -- ^ Collection of optional parameters
398 -> Option scheme -- ^ Collection of optional parameters
402 -> m response -- ^ Response
399 -> m response -- ^ Response
403−req method url body Proxy options = do
400+req method url body Proxy options = req' method url body options $ \request manager -> do
401+ HttpConfig {..} <- getHttpConfig
402+ let wrappingVanilla = handle (throwIO . VanillaHttpException)
403+ wrapExc = handle (throwIO . LI.toHttpException request)
404+ (liftIO . try . wrappingVanilla . wrapExc) (do
405+ response <- retrying httpConfigRetryPolicy httpConfigRetryJudge
406+ (const $ getHttpResponse request manager)
407+ httpConfigCheckResponse request response
408+ return response)
409+ >>= either handleHttpException return
410+
411+-- | Mostly like 'req' with respect to its arguments, but accepts a callback
412+-- that allows to perform a request in arbitrary fashion.
413+--
414+-- This function /does not/ perform handling\/wrapping exceptions, checking
415+-- response, and retrying. It only prepares 'L.Request' and allows you to
416+-- use it.
417+--
418+-- @since 0.3.0
419+
420+req'
421+ :: forall m method body scheme a.
422+ ( MonadHttp m
423+ , HttpMethod method
424+ , HttpBody body
425+ , HttpBodyAllowed (AllowsBody method) (ProvidesBody body) )
426+ => method -- ^ HTTP method
427+ -> Url scheme -- ^ 'Url'—location of resource
428+ -> body -- ^ Body of the request
429+ -> Option scheme -- ^ Collection of optional parameters
430+ -> (L.Request -> L.Manager -> m a) -- ^ How to perform request
431+ -> m a
432+req' method url body options m = do
404− config <- getHttpConfig
433+ config@HttpConfig {..} <- getHttpConfig
405 manager <- liftIO (readIORef globalManager)
434 manager <- liftIO (readIORef globalManager)
406 let -- NOTE First appearance of any given header wins. This allows to
435 let -- NOTE First appearance of any given header wins. This allows to
407 -- “overwrite” headers when we construct a request by cons-ing.
436 -- “overwrite” headers when we construct a request by cons-ing.
@@ -419,10 +448,8 @@ req method url body Proxy options = do
419 getRequestMod (Womb body :: Womb "body" body) <>
448 getRequestMod (Womb body :: Womb "body" body) <>
420 getRequestMod url <>
449 getRequestMod url <>
421 getRequestMod (Womb method :: Womb "method" method)
450 getRequestMod (Womb method :: Womb "method" method)
422− wrappingVanilla m = catch m (throwIO . VanillaHttpException)
423 request <- finalizeRequest options request'
451 request <- finalizeRequest options request'
424− (liftIO . try . wrappingVanilla) (getHttpResponse request manager)
452+ m request manager
425− >>= either handleHttpException return
426
453
427-- | Global 'L.Manager' that 'req' uses. Here we just go with the default
454-- | Global 'L.Manager' that 'req' uses. Here we just go with the default
428-- settings, so users don't need to deal with this manager stuff at all, but
455-- settings, so users don't need to deal with this manager stuff at all, but
@@ -470,8 +497,6 @@ withReqManager m = liftIO (readIORef globalManager) >>= m
470
497
471class MonadIO m => MonadHttp m where
498class MonadIO m => MonadHttp m where
472
499
473− {-# MINIMAL handleHttpException #-}
474−
475 -- | This method describes how to deal with 'HttpException' that was
500 -- | This method describes how to deal with 'HttpException' that was
476 -- caught by the library. One option is to re-throw it if you are OK with
501 -- caught by the library. One option is to re-throw it if you are OK with
477 -- exceptions, but if you prefer working with something like
502 -- exceptions, but if you prefer working with something like
@@ -505,7 +530,7 @@ data HttpConfig = HttpConfig
505 -- ^ Alternative 'L.Manager' to use. 'Nothing' (default value) means
530 -- ^ Alternative 'L.Manager' to use. 'Nothing' (default value) means
506 -- that default implicit manager will be used (that's what you want in
531 -- that default implicit manager will be used (that's what you want in
507 -- 99% of cases).
532 -- 99% of cases).
508− , httpConfigCheckResponse :: L.Request -> L.Response L.BodyReader -> IO ()
533+ , httpConfigCheckResponse :: forall r. HttpResponse r => L.Request -> r -> IO ()
509 -- ^ Function to check the response immediately after receiving the
534 -- ^ Function to check the response immediately after receiving the
510 -- status and headers. This is used for throwing exceptions on
535 -- status and headers. This is used for throwing exceptions on
511 -- non-success status codes by default (set to @\\_ _ -> return ()@ if
536 -- non-success status codes by default (set to @\\_ _ -> return ()@ if
@@ -514,6 +539,18 @@ data HttpConfig = HttpConfig
514 -- something is wrong and we need a way to short-cut execution. The
539 -- something is wrong and we need a way to short-cut execution. The
515 -- thrown exception is caught by the library though and is available in
540 -- thrown exception is caught by the library though and is available in
516 -- 'handleHttpException'.
541 -- 'handleHttpException'.
542+ --
543+ -- @since 0.3.0
544+ , httpConfigRetryPolicy :: RetryPolicyM IO
545+ -- ^ The retry policy to use for request retrying. By default 'def' is
546+ -- used (see 'RetryPolicyM').
547+ --
548+ -- @since 0.3.0
549+ , httpConfigRetryJudge :: forall r. HttpResponse r => RetryStatus -> r -> IO Bool
550+ -- ^ The function is used to decide whether to retry a request. 'True'
551+ -- means that the request should be retried.
552+ --
553+ -- @since 0.3.0
517 } deriving Typeable
554 } deriving Typeable
518
555
519instance Default HttpConfig where
556instance Default HttpConfig where
@@ -522,17 +559,27 @@ instance Default HttpConfig where
522 , httpConfigRedirectCount = 10
559 , httpConfigRedirectCount = 10
523 , httpConfigAltManager = Nothing
560 , httpConfigAltManager = Nothing
524 , httpConfigCheckResponse = \_ response ->
561 , httpConfigCheckResponse = \_ response ->
525− let Y.Status statusCode _ = L.responseStatus response in
562+ let statusCode = responseStatusCode response in
526 unless (200 <= statusCode && statusCode < 300) $ do
563 unless (200 <= statusCode && statusCode < 300) $ do
527− chunk <- BL.toStrict <$> L.brReadSome (L.responseBody response) 1024
564+ chunk <- makeResponseBodyPreview response
565+ let vresponse = toVanillaResponse response
528− LI.throwHttp (L.StatusCodeException (void response) chunk) }
566+ LI.throwHttp (L.StatusCodeException (void vresponse) chunk)
567+ , httpConfigRetryPolicy = def
568+ , httpConfigRetryJudge = \_ r -> return $
569+ responseStatusCode r `elem`
570+ [ 408 -- Request timeout
571+ , 504 -- Gateway timeout
572+ , 524 -- A timeout occurred
573+ , 598 -- (Informal convention) Network read timeout error
574+ , 599 -- (Informal convention) Network connect timeout error
575+ ]
576+ }
529
577
530instance RequestComponent HttpConfig where
578instance RequestComponent HttpConfig where
531 getRequestMod HttpConfig {..} = Endo $ \x ->
579 getRequestMod HttpConfig {..} = Endo $ \x ->
532 x { L.proxy = httpConfigProxy
580 x { L.proxy = httpConfigProxy
533 , L.redirectCount = httpConfigRedirectCount
581 , L.redirectCount = httpConfigRedirectCount
534− , LI.requestManagerOverride = httpConfigAltManager
582+ , LI.requestManagerOverride = httpConfigAltManager }
535− , LI.checkResponse = httpConfigCheckResponse }
536
583
537----------------------------------------------------------------------------
584----------------------------------------------------------------------------
538-- Request—Method
585-- Request—Method
@@ -1243,6 +1290,7 @@ instance HttpResponse IgnoreResponse where
1243 toVanillaResponse (IgnoreResponse response) = response
1290 toVanillaResponse (IgnoreResponse response) = response
1244 getHttpResponse request manager =
1291 getHttpResponse request manager =
1245 IgnoreResponse <$> liftIO (L.httpNoBody request manager)
1292 IgnoreResponse <$> liftIO (L.httpNoBody request manager)
1293+ makeResponseBodyPreview _ = return "<ignored response>"
1246
1294
1247-- | Use this as the fourth argument of 'req' to specify that you want it to
1295-- | Use this as the fourth argument of 'req' to specify that you want it to
1248-- return the 'IgnoreResponse' interpretation.
1296-- return the 'IgnoreResponse' interpretation.
@@ -1255,16 +1303,23 @@ ignoreResponse = Proxy
1255-- monad in which you use 'req' will determine what to do in the case when
1303-- monad in which you use 'req' will determine what to do in the case when
1256-- parsing fails (the 'JsonHttpException' constructor will be used).
1304-- parsing fails (the 'JsonHttpException' constructor will be used).
1257
1305
1258−newtype JsonResponse a = JsonResponse (L.Response a)
1306+data JsonResponse a = JsonResponse (L.Response a) ByteString
1259
1307
1260instance FromJSON a => HttpResponse (JsonResponse a) where
1308instance FromJSON a => HttpResponse (JsonResponse a) where
1261 type HttpResponseBody (JsonResponse a) = a
1309 type HttpResponseBody (JsonResponse a) = a
1262− toVanillaResponse (JsonResponse response) = response
1310+ toVanillaResponse (JsonResponse response _) = response
1263 getHttpResponse request manager = do
1311 getHttpResponse request manager = do
1264 response <- L.httpLbs request manager
1312 response <- L.httpLbs request manager
1265 case A.eitherDecode (L.responseBody response) of
1313 case A.eitherDecode (L.responseBody response) of
1266 Left e -> throwIO (JsonHttpException e)
1314 Left e -> throwIO (JsonHttpException e)
1315+ Right x -> do
1316+ let preview
1317+ = BL.toStrict
1318+ . BL.take bodyPreviewLength
1319+ . L.responseBody
1320+ $ response
1267− Right x -> return $ JsonResponse response { L.responseBody = x }
1321+ return $ JsonResponse response { L.responseBody = x } preview
1322+ makeResponseBodyPreview (JsonResponse _ preview) = return preview
1268
1323
1269-- | Use this as the forth argument of 'req' to specify that you want it to
1324-- | Use this as the forth argument of 'req' to specify that you want it to
1270-- return the 'JsonResponse' interpretation.
1325-- return the 'JsonResponse' interpretation.
@@ -1283,6 +1338,7 @@ instance HttpResponse BsResponse where
1283 L.withResponse request manager $ \response -> do
1338 L.withResponse request manager $ \response -> do
1284 chunks <- L.brConsume (L.responseBody response)
1339 chunks <- L.brConsume (L.responseBody response)
1285 return $ BsResponse response { L.responseBody = B.concat chunks }
1340 return $ BsResponse response { L.responseBody = B.concat chunks }
1341+ makeResponseBodyPreview = return . B.take bodyPreviewLength . responseBody
1286
1342
1287-- | Use this as the forth argument of 'req' to specify that you want to
1343-- | Use this as the forth argument of 'req' to specify that you want to
1288-- interpret response body as a strict 'ByteString'.
1344-- interpret response body as a strict 'ByteString'.
@@ -1300,6 +1356,7 @@ instance HttpResponse LbsResponse where
1300 toVanillaResponse (LbsResponse response) = response
1356 toVanillaResponse (LbsResponse response) = response
1301 getHttpResponse request manager =
1357 getHttpResponse request manager =
1302 LbsResponse <$> L.httpLbs request manager
1358 LbsResponse <$> L.httpLbs request manager
1359+ makeResponseBodyPreview = return . BL.toStrict . BL.take 1027 . responseBody
1303
1360
1304-- | Use this as the forth argument of 'req' to specify that you want to
1361-- | Use this as the forth argument of 'req' to specify that you want to
1305-- interpret response body as a lazy 'BL.ByteString'.
1362-- interpret response body as a lazy 'BL.ByteString'.
@@ -1307,27 +1364,6 @@ instance HttpResponse LbsResponse where
1307lbsResponse :: Proxy LbsResponse
1364lbsResponse :: Proxy LbsResponse
1308lbsResponse = Proxy
1365lbsResponse = Proxy
1309
1366
1310−-- | This interpretation does not result in any request at all, but you can
1311−-- use the 'responseRequest' function to extract 'L.Request' that 'req' has
1312−-- prepared. This is useful primarily for testing.
1313−--
1314−-- Note that when you use this interpretation inspecting response will
1315−-- diverge (i.e. it'll blow up with an error, don't do that).
1316−
1317−newtype ReturnRequest = ReturnRequest L.Request
1318−
1319−instance HttpResponse ReturnRequest where
1320− type HttpResponseBody ReturnRequest = ()
1321− toVanillaResponse (ReturnRequest _) = error
1322− "Network.HTTP.Req.ReturnRequest interpretation does not make requests"
1323− getHttpResponse request _ = return (ReturnRequest request)
1324−
1325−-- | Use this as the forth argument of 'req' to specify that you want it to
1326−-- just return the request it consturcted without making any requests.
1327−
1328−returnRequest :: Proxy ReturnRequest
1329−returnRequest = Proxy
1330−
1331----------------------------------------------------------------------------
1367----------------------------------------------------------------------------
1332-- Inspecting a response
1368-- Inspecting a response
1333
1369
@@ -1375,11 +1411,6 @@ responseCookieJar
1375 -> L.CookieJar
1411 -> L.CookieJar
1376responseCookieJar = L.responseCookieJar . toVanillaResponse
1412responseCookieJar = L.responseCookieJar . toVanillaResponse
1377
1413
1378−-- | Get the original request from 'ReturnRequest' response interpretation.
1379−
1380−responseRequest :: ReturnRequest -> L.Request
1381−responseRequest (ReturnRequest request) = request
1382−
1383----------------------------------------------------------------------------
1414----------------------------------------------------------------------------
1384-- Response—Defining your own interpretation
1415-- Response—Defining your own interpretation
1385
1416
@@ -1407,6 +1438,14 @@ class HttpResponse response where
1407
1438
1408 getHttpResponse :: L.Request -> L.Manager -> IO response
1439 getHttpResponse :: L.Request -> L.Manager -> IO response
1409
1440
1441+ -- | Construct a “preview” of response body. It is recommend to limit the
1442+ -- length to 1024 bytes. This is mainly useful for inclusion of response
1443+ -- body fragments in exceptions.
1444+ --
1445+ -- @since 0.3.0
1446+
1447+ makeResponseBodyPreview :: response -> IO ByteString
1448+
1410----------------------------------------------------------------------------
1449----------------------------------------------------------------------------
1411-- Other
1450-- Other
1412
1451
@@ -1464,3 +1503,11 @@ data Scheme
1464 = Http -- ^ HTTP
1503 = Http -- ^ HTTP
1465 | Https -- ^ HTTPS
1504 | Https -- ^ HTTPS
1466 deriving (Eq, Ord, Show, Data, Typeable, Generic)
1505 deriving (Eq, Ord, Show, Data, Typeable, Generic)
1506+
1507+----------------------------------------------------------------------------
1508+-- Constants
1509+
1510+-- | Max length of preview fragment of response body.
1511+
1512+bodyPreviewLength :: Num a => a
1513+bodyPreviewLength = 1024
httpbin-tests/Network/HTTP/ReqSpec.hs+33−4
@@ -13,12 +13,14 @@ import Control.Exception (throwIO)
13import Control.Monad.Reader
13import Control.Monad.Reader
14import Data.Aeson (Value (..), ToJSON (..), object, (.=))
14import Data.Aeson (Value (..), ToJSON (..), object, (.=))
15import Data.Default.Class
15import Data.Default.Class
16+import Data.IORef
16import Data.Monoid ((<>))
17import Data.Monoid ((<>))
17import Data.Proxy
18import Data.Proxy
18import Data.Text (Text)
19import Data.Text (Text)
19import Network.HTTP.Req
20import Network.HTTP.Req
20import Test.Hspec
21import Test.Hspec
21import Test.QuickCheck
22import Test.QuickCheck
23+import qualified Control.Retry as R
22import qualified Data.Aeson as A
24import qualified Data.Aeson as A
23import qualified Data.ByteString as B
25import qualified Data.ByteString as B
24import qualified Data.ByteString.Lazy as BL
26import qualified Data.ByteString.Lazy as BL
@@ -31,6 +33,7 @@ import qualified Network.HTTP.Client.MultipartFormData as LM
31import qualified Network.HTTP.Types as Y
33import qualified Network.HTTP.Types as Y
32
34
33#if !MIN_VERSION_base(4,8,0)
35#if !MIN_VERSION_base(4,8,0)
36+import Control.Applicative
34import Data.Monoid (mempty)
37import Data.Monoid (mempty)
35import Data.Word (Word)
38import Data.Word (Word)
36#endif
39#endif
@@ -204,6 +207,15 @@ spec = do
204 -- TODO /gzip
207 -- TODO /gzip
205 -- TODO /deflate
208 -- TODO /deflate
206
209
210+ describe "retrying" $
211+ it "retries as many times as specified" $ do
212+ let status = 408 :: Int
213+ nref <- newIORef (0 :: Int)
214+ r <- countingRetries nref $ req GET (httpbin /: "status" /~ status)
215+ NoReqBody ignoreResponse mempty
216+ responseStatusCode r `shouldBe` status
217+ readIORef nref `shouldReturn` 6 -- number of retries plus 1
218+
207 forM_ [101..102] checkStatusCode
219 forM_ [101..102] checkStatusCode
208 forM_ [200..208] checkStatusCode
220 forM_ [200..208] checkStatusCode
209 -- forM_ [300..308] checkStatusCode
221 -- forM_ [300..308] checkStatusCode
@@ -320,18 +332,35 @@ instance MonadHttp (ReaderT HttpConfig IO) where
320-- response status codes.
332-- response status codes.
321
333
322prepareForShit
334prepareForShit
323− :: (forall m. MonadHttp m => m a)
335+ :: ReaderT HttpConfig IO a
324 -> IO a
336 -> IO a
325prepareForShit m = runReaderT m def { httpConfigCheckResponse = noNoise }
337prepareForShit m = runReaderT m def { httpConfigCheckResponse = noNoise }
338+ where
326− where noNoise _ _ = return ()
339+ noNoise _ _ = return ()
327
340
328-- | Run request with such settings that it throws on any response.
341-- | Run request with such settings that it throws on any response.
329
342
330blindlyThrowing
343blindlyThrowing
331− :: (forall m. MonadHttp m => m a)
344+ :: ReaderT HttpConfig IO a
332 -> IO a
345 -> IO a
333blindlyThrowing m = runReaderT m def { httpConfigCheckResponse = doit }
346blindlyThrowing m = runReaderT m def { httpConfigCheckResponse = doit }
347+ where
334− where doit _ _ = error "Oops!"
348+ doit _ _ = error "Oops!"
349+
350+-- | Run request with such settings that every retry increments the given
351+-- @'IORef' 'Int'@.
352+
353+countingRetries
354+ :: IORef Int
355+ -> ReaderT HttpConfig IO a
356+ -> IO a
357+countingRetries nref m = runReaderT m def
358+ { httpConfigCheckResponse = noNoise
359+ , httpConfigRetryPolicy = R.constantDelay 50000 <> R.limitRetries 5
360+ , httpConfigRetryJudge = judge }
361+ where
362+ noNoise _ _ = return ()
363+ judge _ _ = True <$ modifyIORef nref (+ 1)
335
364
336-- | 'Url' representing <https://httpbin.org>.
365-- | 'Url' representing <https://httpbin.org>.
337
366
pure-tests/Network/HTTP/ReqSpec.hs+5−2
@@ -20,6 +20,7 @@ import Control.Exception (throwIO)
20import Control.Monad.Reader
20import Control.Monad.Reader
21import Data.Aeson (ToJSON (..))
21import Data.Aeson (ToJSON (..))
22import Data.ByteString (ByteString)
22import Data.ByteString (ByteString)
23+import Data.Default.Class
23import Data.Maybe (isNothing, fromJust)
24import Data.Maybe (isNothing, fromJust)
24import Data.Monoid ((<>))
25import Data.Monoid ((<>))
25import Data.Proxy
26import Data.Proxy
@@ -305,6 +306,8 @@ instance Arbitrary HttpConfig where
305 httpConfigRedirectCount <- arbitrary
306 httpConfigRedirectCount <- arbitrary
306 let httpConfigAltManager = Nothing
307 let httpConfigAltManager = Nothing
307 httpConfigCheckResponse _ _ = return ()
308 httpConfigCheckResponse _ _ = return ()
309+ httpConfigRetryPolicy = def
310+ httpConfigRetryJudge _ _ = return False
308 return HttpConfig {..}
311 return HttpConfig {..}
309
312
310instance Show HttpConfig where
313instance Show HttpConfig where
@@ -403,8 +406,8 @@ req_
403 -> body -- ^ Body of the request
406 -> body -- ^ Body of the request
404 -> Option scheme -- ^ Collection of optional parameters
407 -> Option scheme -- ^ Collection of optional parameters
405 -> m L.Request -- ^ Vanilla request
408 -> m L.Request -- ^ Vanilla request
406−req_ method url' body options =
409+req_ method url' body options = req' method url' body options $
407− responseRequest `liftM` req method url' body returnRequest options
410+ \request _ -> return request
408
411
409-- | A dummy 'Url'.
412-- | A dummy 'Url'.
410
413
req.cabal+3
@@ -40,6 +40,7 @@ library
40 , http-client-tls >= 0.3.2 && < 0.4
40 , http-client-tls >= 0.3.2 && < 0.4
41 , http-types >= 0.8 && < 10.0
41 , http-types >= 0.8 && < 10.0
42 , mtl >= 2.0 && < 3.0
42 , mtl >= 2.0 && < 3.0
43+ , retry >= 0.7 && < 0.8
43 , text >= 0.2 && < 1.3
44 , text >= 0.2 && < 1.3
44 , time >= 1.2 && < 1.9
45 , time >= 1.2 && < 1.9
45 , transformers >= 0.4 && < 0.6
46 , transformers >= 0.4 && < 0.6
@@ -63,6 +64,7 @@ test-suite pure-tests
63 , blaze-builder >= 0.3 && < 0.5
64 , blaze-builder >= 0.3 && < 0.5
64 , bytestring >= 0.10.8 && < 0.11
65 , bytestring >= 0.10.8 && < 0.11
65 , case-insensitive >= 0.2 && < 1.3
66 , case-insensitive >= 0.2 && < 1.3
67+ , data-default-class
66 , hspec >= 2.0 && < 3.0
68 , hspec >= 2.0 && < 3.0
67 , hspec-core >= 2.0 && < 3.0
69 , hspec-core >= 2.0 && < 3.0
68 , http-client >= 0.5 && < 0.6
70 , http-client >= 0.5 && < 0.6
@@ -92,6 +94,7 @@ test-suite httpbin-tests
92 , http-types >= 0.8 && < 10.0
94 , http-types >= 0.8 && < 10.0
93 , mtl >= 2.0 && < 3.0
95 , mtl >= 2.0 && < 3.0
94 , req
96 , req
97+ , retry >= 0.7 && < 0.8
95 , text >= 0.2 && < 1.3
98 , text >= 0.2 && < 1.3
96 , unordered-containers >= 0.2.5 && < 0.2.9
99 , unordered-containers >= 0.2.5 && < 0.2.9
97 if flag(dev)
100 if flag(dev)