Files

3.9 KiB

HTTP headers and 429 response

The extension injects standard rate-limit headers on every request — both allowed and rejected. Clients can use these headers to implement back-off logic without waiting for a 429.

Response headers

Header Type Description
X-RateLimit-Limit integer Maximum requests allowed in the current window
X-RateLimit-Remaining integer Requests remaining in the current window (≥ 0)
X-RateLimit-Reset Unix timestamp (s) When the quota resets or the next token arrives
Retry-After seconds Only on 429 — how long to wait before retrying (≥ 1)

Example — allowed request

HTTP/1.1 200 OK
X-RateLimit-Limit:     100
X-RateLimit-Remaining: 73
X-RateLimit-Reset:     1711750860
Content-Type: application/json

Example — rejected request (429)

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit:     100
X-RateLimit-Remaining: 0
X-RateLimit-Reset:     1711750860
Retry-After:           1
Content-Type: text/plain

Too Many Requests

Header semantics by strategy

FIXED_WINDOW

Header Value
X-RateLimit-Reset Unix timestamp of the next window start (aligned to clock)
Retry-After Seconds until X-RateLimit-Reset (minimum 1)

At a 1-second window boundary Retry-After will typically be 1.

TOKEN_BUCKET

Header Value
X-RateLimit-Remaining Current token count (may increase between requests due to refill)
X-RateLimit-Reset Estimated Unix timestamp when the next token arrives
Retry-After Milliseconds-precise estimate converted to seconds (minimum 1)

Because the token bucket refills continuously, X-RateLimit-Reset is a near-future timestamp rather than an aligned window boundary.

Retry-After precision

Retry-After is computed as:

retryAfter = max(1, X-RateLimit-Reset - currentTimeSeconds)

The minimum value is always 1 second — RFC 7231 discourages Retry-After: 0 as it encourages instant retry loops.

Client-side back-off example (Java)

HttpResponse<String> res = client.send(request, BodyHandlers.ofString());

if (res.statusCode() == 429) {
    String retryAfter = res.headers().firstValue("Retry-After").orElse("1");
    long waitMs = Long.parseLong(retryAfter) * 1000L;
    Thread.sleep(waitMs);
    // retry...
}

Client-side back-off example (JavaScript fetch)

const res = await fetch('/api/search?q=flash');

if (res.status === 429) {
    const retryAfter = parseInt(res.headers.get('Retry-After') ?? '1', 10);
    await new Promise(r => setTimeout(r, retryAfter * 1000));
    // retry...
}

Monitoring / alerting

X-RateLimit-Remaining can be scraped by a metrics agent to track approaching limits before they hit 429:

  • remaining / limit < 0.1 → warning (less than 10% quota left)
  • status == 429 → rate-limit violation counter increment

If flash-ext-limiter is used together with a future metrics extension, the 429 rate per resolver key is a natural signal for abuse detection or auto-scaling.

Header injection timing

Headers are injected before calling next.handle(req, res) on allowed requests, and instead of calling it on rejected requests. This means:

  • Handlers cannot accidentally overwrite X-RateLimit-* headers (they are set first, but handlers that call res.header(...) with the same name will add a second value — avoid this by not setting these headers manually).
  • On 429, the handler body is never executed — no side effects occur.

Integration with Swagger UI (flash-ext-openapi)

When flash-ext-openapi is installed, handlers annotated with @Limit automatically contribute rate-limit response headers to generated OpenAPI responses:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset
  • Retry-After on 429

If 429 is not manually declared, OpenAPI auto-adds 429 Too Many Requests.