429 限流加 Retry-After 头:值为限流窗口秒数,不改阈值与计数

- _send_json 加 extra_headers 参数,429 返回处传 Retry-After =
  limiter.window_seconds(默认 60),客户端据此退避一个完整窗口,
  不再以原速率 hammer 把自己钉在限流里(Postgres DbRateLimiter
  先 INSERT 再 COUNT,被拒请求本身也计数)。
- 单测:429 响应含 Retry-After 且值等于窗口秒数(默认 60 与注入
  窗口 7 各验一次)。
- 相关单测:test_prod_webhook_exec + test_rate_limit_db 21 个、
  test_server + test_server_endpoints 28 个,全过;cloud 全量未跑。
This commit is contained in:
host committed 2026-10-28 10:12:57 +00:00
1 parent eae9884b13
commit c43eaac4d3
2 files changed
+41 -2

No files matched your search

+29
View File
@@ -255,6 +255,35 @@ class WebhookExecTestCase(unittest.TestCase):
self.assertEqual(status, 429)
self.assertEqual(body, {"error": "rate_limited"})
def test_rate_limit_429_has_retry_after_window_seconds(self):
# 429 响应必须含 Retry-After 头,且值为限流窗口秒数
# (limiter.window_seconds),不是硬编码 60:先用默认
# 窗口 60 验一次,再用注入窗口 7 验一次。
for window, per_minute in ((60, 1), (7, 1)):
self.server.rate_limiter = RateLimiter(
per_minute=per_minute, window_seconds=window)
self.server.rate_limit_per_minute = per_minute
self.server.rate_limit_window_seconds = window
conn = http.client.HTTPConnection(
"127.0.0.1", self.port, timeout=10)
try:
conn.request("GET", "/projects",
headers={"X-API-Key": self.owner_key})
resp = conn.getresponse()
resp.read()
self.assertEqual(resp.status, 200)
conn.request("GET", "/projects",
headers={"X-API-Key": self.owner_key})
resp = conn.getresponse()
body = resp.read()
self.assertEqual(resp.status, 429)
self.assertEqual(json.loads(body.decode("utf-8")),
{"error": "rate_limited"})
self.assertEqual(resp.getheader("Retry-After"),
str(window))
finally:
conn.close()
def test_health_backend_and_db_ok_and_metrics_by_status(self):
status, body = self._req("GET", "/health")
self.assertEqual(status, 200)