Open rob-buskens-sp opened 5 months ago
I found my code and it used https://urllib3.readthedocs.io/en/2.2.1/reference/urllib3.util.html which is what the python sdk appears to use for retries.
I had configured with a back off factor to wait longer on retries.
retries = 3
backoff_factor = 0.3
retry_http_codes = (500, 502, 504, 429)
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist = retry_http_codes
)
I didn't do any testing regarding it handling 429 responses and the retry-after header.
I did some testing and it appears that urllib3.Retry handles 429 and the Retry-After header with seconds. I didn't try dates.
GET /api/users returns a 429 every 2nd invocation with varying waits
const app = express();
const port = 3000;
const routes = require('./routes');
app.use('/api', routes);
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
const express = require('express');
const router = express.Router();
// Define routes
retry = false
// min and max included
function randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
router.get('/users', (req, res) => {
if (retry) {
retryAfter = randomIntFromInterval(10, 60)
console.log(`retry-after ${retryAfter}`)
res.appendHeader('Retry-After', retryAfter).sendStatus(429);
} else {
console.log('200 all good')
res.status(200).send({ status: 200, message: "all good"})
}
retry = !retry
});
router.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`Details of user ${userId}`);
});
router.post('/users', (req, res) => {
res.send('Create a new user');
});
router.put('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`Update user ${userId}`);
});
router.delete('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`Delete user ${userId}`);
});
module.exports = router;
import urllib3
import logging
import time
from urllib3 import Retry
if __name__ == '__main__':
logging.basicConfig(filename='retry.log',
filemode='a',
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%H:%M:%S',
level=getattr(logging, "DEBUG"))
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel('DEBUG')
requests_log.propagate = True
timeout = urllib3.util.Timeout(connect=2.0, read=7.0)
retries = Retry(
total=5, status_forcelist = [ 502, 503, 504 ],
respect_retry_after_header=True
)
http = urllib3.PoolManager(timeout=timeout, retries=retries)
for x in range(100):
resp = http.request("GET", "http://localhost:3000/api/users")
print(x, resp.status)
python client makes 100 calls, urllib3 logging shows every second call is a 429 and the retry occurring while the program output shows only the successful processing.
0 200
1 200
2 200
3 200
4 200
5 200
6 200
7 200
18:18:36,124 urllib3.util.retry DEBUG Incremented Retry for (url='/api/users'): Retry(total=4, connect=None, read=None, redirect=None, status=None)
18:19:30,134 urllib3.connectionpool DEBUG Retry: /api/users
18:19:30,136 urllib3.connectionpool DEBUG Resetting dropped connection: localhost
18:19:30,142 urllib3.connectionpool DEBUG http://localhost:3000 "GET /api/users HTTP/1.1" 200 0
18:19:30,145 urllib3.connectionpool DEBUG http://localhost:3000 "GET /api/users HTTP/1.1" 429 0
18:19:30,145 urllib3.util.retry DEBUG Incremented Retry for (url='/api/users'): Retry(total=4, connect=None, read=None, redirect=None, status=None)
18:19:40,155 urllib3.connectionpool DEBUG Retry: /api/users
18:19:40,156 urllib3.connectionpool DEBUG Resetting dropped connection: localhost
18:19:40,162 urllib3.connectionpool DEBUG http://localhost:3000 "GET /api/users HTTP/1.1" 200 0
18:19:40,164 urllib3.connectionpool DEBUG http://localhost:3000 "GET /api/users HTTP/1.1" 429 0
18:19:40,165 urllib3.util.retry DEBUG Incremented Retry for (url='/api/users'): Retry(total=4, connect=None, read=None, redirect=None, status=None)
18:20:38,175 urllib3.connectionpool DEBUG Retry: /api/users
18:20:38,177 urllib3.connectionpool DEBUG Resetting dropped connection: localhost
18:20:38,189 urllib3.connectionpool DEBUG http://localhost:3000 "GET /api/users HTTP/1.1" 200 0
18:20:38,191 urllib3.connectionpool DEBUG http://localhost:3000 "GET /api/users HTTP/1.1" 429 0
18:20:38,192 urllib3.util.retry DEBUG Incremented Retry for (url='/api/users'): Retry(total=4, connect=None, read=None, redirect=None, status=None)
18:21:15,202 urllib3.connectionpool DEBUG Retry: /api/users
18:21:15,204 urllib3.connectionpool DEBUG Resetting dropped connection: localhost
18:21:15,213 urllib3.connectionpool DEBUG http://localhost:3000 "GET /api/users HTTP/1.1" 200 0
I suggest you'll want to do your own testing. On confirmation the feature request is to update the python-sdk doc: https://developer.sailpoint.com/docs/tools/sdk/python
Feature request
Incorporate support for Identity Security Cloud (ISC) API rate limits.
If ISC APIs return a 429 then try again after the number of seconds in the Retry-After header.
Rate Limits
There is a rate limit of 100 requests per access_token per 10 seconds for V3 API calls through the API gateway. If you exceed the rate limit, expect the following response from the API:
HTTP Status Code: 429 Too Many Requests
Headers:
Retry-After: {seconds to wait before rate limit resets}
Sample code coming?
I believe I've implemented this previously and will look for the code as an example. But don't wait for me! lol.