2
2
import json
3
3
import platform
4
4
import sys
5
-
6
5
import requests
7
6
8
7
from ..exceptions import Auth0Error , RateLimitError
8
+ from time import sleep
9
+ from random import randint
9
10
10
11
UNKNOWN_ERROR = 'a0.sdk.internal.unknown'
11
12
12
-
13
13
class RestClient (object ):
14
14
"""Provides simple methods for handling all RESTful api endpoints.
15
15
@@ -22,14 +22,18 @@ class RestClient(object):
22
22
(defaults to 5.0 for both)
23
23
"""
24
24
25
- def __init__ (self , jwt , telemetry = True , timeout = 5.0 ):
25
+ def __init__ (self , jwt , telemetry = True , timeout = 5.0 , retries = 3 ):
26
26
self .jwt = jwt
27
27
self .timeout = timeout
28
+ self .retries = retries
29
+ self ._metrics = {'retries' : 0 , 'backoff' : []}
30
+ self ._skip_sleep = False
28
31
29
32
self .base_headers = {
30
33
'Authorization' : 'Bearer {}' .format (self .jwt ),
31
34
'Content-Type' : 'application/json' ,
32
35
}
36
+
33
37
if telemetry :
34
38
py_version = platform .python_version ()
35
39
version = sys .modules ['auth0' ].__version__
@@ -47,10 +51,69 @@ def __init__(self, jwt, telemetry=True, timeout=5.0):
47
51
'Auth0-Client' : base64 .b64encode (auth0_client ),
48
52
})
49
53
54
+ # Returns a hard cap for the maximum number of retries allowed (10)
55
+ def MAX_REQUEST_RETRIES (self ):
56
+ return 10
57
+
58
+ # Returns the maximum amount of jitter to introduce in milliseconds (100ms)
59
+ def MAX_REQUEST_RETRY_JITTER (self ):
60
+ return 100
61
+
62
+ # Returns the maximum delay window allowed (1000ms)
63
+ def MAX_REQUEST_RETRY_DELAY (self ):
64
+ return 1000
65
+
66
+ # Returns the minimum delay window allowed (100ms)
67
+ def MIN_REQUEST_RETRY_DELAY (self ):
68
+ return 100
69
+
50
70
def get (self , url , params = None ):
51
71
headers = self .base_headers .copy ()
52
72
53
- response = requests .get (url , params = params , headers = headers , timeout = self .timeout )
73
+ # Track the API request attempt number
74
+ attempt = 0
75
+
76
+ # Reset the metrics tracker
77
+ self ._metrics = {'retries' : 0 , 'backoff' : []}
78
+
79
+ # Cap the maximum number of retries to 10 or fewer. Floor the retries at 0.
80
+ retries = min (self .MAX_REQUEST_RETRIES (), max (0 , self .retries ))
81
+
82
+ while True :
83
+ # Increment attempt number
84
+ attempt += 1
85
+
86
+ # Issue the request
87
+ response = requests .get (url , params = params , headers = headers , timeout = self .timeout );
88
+
89
+ # If the response did not have a 429 header, or the retries were configured at 0, or the attempt number is equal to or greater than the configured retries, break
90
+ if response .status_code != 429 or retries <= 0 or attempt > retries :
91
+ break
92
+
93
+ # Retry the request. Apply a exponential backoff for subsequent attempts, using this formula:
94
+ # max(MIN_REQUEST_RETRY_DELAY, min(MAX_REQUEST_RETRY_DELAY, (100ms * (2 ** attempt - 1)) + random_between(1, MAX_REQUEST_RETRY_JITTER)))
95
+
96
+ # ✔ Increases base delay by (100ms * (2 ** attempt - 1))
97
+ wait = 100 * 2 ** (attempt - 1 )
98
+
99
+ # ✔ Introduces jitter to the base delay; increases delay between 1ms to MAX_REQUEST_RETRY_JITTER (100ms)
100
+ wait += randint (1 , self .MAX_REQUEST_RETRY_JITTER ())
101
+
102
+ # ✔ Is never more than MAX_REQUEST_RETRY_DELAY (1s)
103
+ wait = min (self .MAX_REQUEST_RETRY_DELAY (), wait )
104
+
105
+ # ✔ Is never less than MIN_REQUEST_RETRY_DELAY (100ms)
106
+ wait = max (self .MIN_REQUEST_RETRY_DELAY (), wait )
107
+
108
+ self ._metrics ['retries' ] = attempt
109
+ self ._metrics ['backoff' ].append (wait )
110
+
111
+ # Skip calling sleep() when running unit tests
112
+ if self ._skip_sleep is False :
113
+ # sleep() functions in seconds, so convert the milliseconds formula above accordingly
114
+ sleep (wait / 1000 )
115
+
116
+ # Return the final Response
54
117
return self ._process_response (response )
55
118
56
119
def post (self , url , data = None ):
0 commit comments