@@ -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
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
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
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
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
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).
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)
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
409+ >>= either handleHttpException return
411+-- | Mostly like 'req' with respect to its arguments, but accepts a callback
412+-- that allows to perform a request in arbitrary fashion.
414+-- This function /does not/ perform handling\/wrapping exceptions, checking
415+-- response, and retrying. It only prepares 'L.Request' and allows you to
421+ :: forall m method body scheme a.
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
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
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
471class MonadIO m => MonadHttp m where
498class MonadIO m => MonadHttp m where
473− {-# MINIMAL handleHttpException #-}
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'.
544+ , httpConfigRetryPolicy :: RetryPolicyM IO
545+ -- ^ The retry policy to use for request retrying. By default 'def' is
546+ -- used (see 'RetryPolicyM').
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.
517 } deriving Typeable
554 } deriving Typeable
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
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 }
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>"
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).
1258−newtype JsonResponse a = JsonResponse (L.Response a)
1306+data JsonResponse a = JsonResponse (L.Response a) ByteString
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)
1318+ . BL.take bodyPreviewLength
1267− Right x -> return $ JsonResponse response { L.responseBody = x }
1321+ return $ JsonResponse response { L.responseBody = x } preview
1322+ makeResponseBodyPreview (JsonResponse _ preview) = return preview
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
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
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
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.
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).
1317−newtype ReturnRequest = ReturnRequest L.Request
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)
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.
1328−returnRequest :: Proxy ReturnRequest
1329−returnRequest = Proxy
1331----------------------------------------------------------------------------
1367----------------------------------------------------------------------------
1332-- Inspecting a response
1368-- Inspecting a response
@@ -1375,11 +1411,6 @@ responseCookieJar
1375 -> L.CookieJar
1411 -> L.CookieJar
1376responseCookieJar = L.responseCookieJar . toVanillaResponse
1412responseCookieJar = L.responseCookieJar . toVanillaResponse
1378−-- | Get the original request from 'ReturnRequest' response interpretation.
1380−responseRequest :: ReturnRequest -> L.Request
1381−responseRequest (ReturnRequest request) = request
1383----------------------------------------------------------------------------
1414----------------------------------------------------------------------------
1384-- Response—Defining your own interpretation
1415-- Response—Defining your own interpretation
@@ -1407,6 +1438,14 @@ class HttpResponse response where
1408 getHttpResponse :: L.Request -> L.Manager -> IO response
1439 getHttpResponse :: L.Request -> L.Manager -> IO response
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.
1447+ makeResponseBodyPreview :: response -> IO ByteString
1410----------------------------------------------------------------------------
1449----------------------------------------------------------------------------
@@ -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)
1507+----------------------------------------------------------------------------
1510+-- | Max length of preview fragment of response body.
1512+bodyPreviewLength :: Num a => a
1513+bodyPreviewLength = 1024