Skip to content

governor: 429 responses have no Retry-After (computed then discarded), and x-ratelimit-remaining/reset are constants #163

Description

@rrrodzilla

Summary

When the governor rejects a request, it computes how long the client must wait and then discards it. The 429 response has no Retry-After header and no x-ratelimit-* headers, so clients cannot back off correctly. The x-ratelimit-* headers on successful responses are placeholders: remaining is always limit - 1 and reset is always now + 60s.

Version checked

acton-service 0.43.1 (crates.io source), observed through SchemaForge v0.45.0.

Source

  • src/middleware/governor.rs:393-405 (check_with_limiter): retry_after is computed from not_until.wait_time_from(...), logged, and then Err(Error::RateLimitExceeded) is returned without it. The RateLimitExceeded struct (:135-158) already models retry_after but is not used on this path.
  • src/error.rs:584-591: Error::RateLimitExceeded maps to a 429 JSON body with no headers.
  • src/middleware/governor.rs:386-390 and :424-447: remaining = requests_per_minute.saturating_sub(1) and reset_secs: 60 are constants, and the headers are only added to successful responses.
  • By contrast, src/lockout/middleware.rs:110-120 sets Retry-After on its 423.

Reproduction

$ for i in $(seq 1 40); do curl -s -o /dev/null http://127.0.0.1:3000/health; done
$ curl -s -D - -o /dev/null http://127.0.0.1:3000/health | grep -i 'HTTP/\|retry\|ratelimit'
HTTP/1.1 429 Too Many Requests

Expected

A 429 from the governor carries Retry-After: <ceil(seconds)> (RFC 9110 §10.2.3) and, ideally, x-ratelimit-limit / x-ratelimit-remaining: 0 / x-ratelimit-reset. Successful responses report the real remaining capacity, or omit the header rather than report a constant.

Suggested fix

Return a rate-limit error that carries the wait duration (for example Error::RateLimited { retry_after: Duration, limit: u32 }), and set the headers in its IntoResponse. Use the limiter state for remaining (governor 0.10's StateInformationMiddleware returns a StateSnapshot with remaining_burst_capacity()), or drop x-ratelimit-remaining until it is accurate. Add a test asserting Retry-After on a 429.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions