浏览代码

feat(api): officially support Retry-After: header for throttled requests

Peter Thomassen 4 年之前
父节点
当前提交
7519f13a62
共有 2 个文件被更改,包括 13 次插入9 次删除
  1. 8 7
      api/desecapi/tests/test_throttling.py
  2. 5 2
      docs/rate-limits.rst

+ 8 - 7
api/desecapi/tests/test_throttling.py

@@ -39,7 +39,7 @@ class ThrottlingTestCase(TestCase):
         def do_test():
             view = MockView.as_view()
             sum_delay = 0
-            for delay, count in counts:
+            for delay, count, max_wait in counts:
                 sum_delay += delay
                 with mock.patch('desecapi.throttling.ScopedRatesThrottle.timer', return_value=time.time() + sum_delay):
                     for _ in range(count):
@@ -48,6 +48,7 @@ class ThrottlingTestCase(TestCase):
 
                     response = view(request)
                     self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
+                    self.assertTrue(max_wait - 1 <= float(response['Retry-After']) <= max_wait)
 
         cache.clear()
         request = self.factory.get('/')
@@ -59,19 +60,19 @@ class ThrottlingTestCase(TestCase):
                     do_test()
 
     def test_requests_are_throttled_4sec(self):
-        self._test_requests_are_throttled(['4/sec'], [(0, 4), (1, 4)])
+        self._test_requests_are_throttled(['4/sec'], [(0, 4, 1), (1, 4, 1)])
 
     def test_requests_are_throttled_4min(self):
-        self._test_requests_are_throttled(['4/min'], [(0, 4)])
+        self._test_requests_are_throttled(['4/min'], [(0, 4, 60)])
 
     def test_requests_are_throttled_multiple(self):
-        self._test_requests_are_throttled(['5/s', '4/day'], [(0, 4)])
-        self._test_requests_are_throttled(['4/s', '5/day'], [(0, 4)])
+        self._test_requests_are_throttled(['5/s', '4/day'], [(0, 4, 86400)])
+        self._test_requests_are_throttled(['4/s', '5/day'], [(0, 4, 1)])
 
     def test_requests_are_throttled_multiple_cascade(self):
         # We test that we can do 4 requests in the first second and only 2 in the second second
-        self._test_requests_are_throttled(['4/s', '6/day'], [(0, 4), (1, 2)])
+        self._test_requests_are_throttled(['4/s', '6/day'], [(0, 4, 1), (1, 2, 86400)])
 
     def test_requests_are_throttled_multiple_cascade_with_buckets(self):
         # We test that we can do 4 requests in the first second and only 2 in the second second
-        self._test_requests_are_throttled(['4/s', '6/day'], [(0, 4), (1, 2)], buckets=['foo', 'bar'])
+        self._test_requests_are_throttled(['4/s', '6/day'], [(0, 4, 1), (1, 2, 86400)], buckets=['foo', 'bar'])

+ 5 - 2
docs/rate-limits.rst

@@ -8,8 +8,11 @@ ensure that the system load remains manageable, to avoid update rejections due
 to concurrent DNS updates on the same domain etc.
 
 Rate limits apply per account and are enforced in a sliding-window fashion.
-For throttled requests, the server will respond with ``429 Too Many Requests``.
-The response body contains information on how long to wait.
+For throttled requests, the server will return status ``429 Too Many
+Requests`` and give a human-readable explanation in the response body,
+including how long to wait before making another request.  The number of
+seconds after which the next request will be allowed is also given by the
+``Retry-After`` header.
 
 **Example:** If the rate is 10/min and you make a request every second, the
 11th request will be rejected.  You will then have to wait for 50 seconds,