Skip to main content

Module rate_limit

Module rate_limit 

Source
Expand description

Token-bucket rate limiter as a ServerInterceptor.

Provides RateLimitInterceptor, a ready-made interceptor that limits request throughput per caller. The caller key is derived from CallContext::caller_identity; for unauthenticated callers behind a trusted reverse proxy, the client IP can be taken from x-forwarded-for (see RateLimitConfig::trusted_proxy_hops).

§Example

use std::sync::Arc;
use a2a_protocol_server::rate_limit::{RateLimitInterceptor, RateLimitConfig};

let limiter = Arc::new(
    RateLimitInterceptor::new(RateLimitConfig {
        requests_per_window: 100,
        window_secs: 60,
        ..RateLimitConfig::default()
    })
    .expect("valid rate limit config"),
);

Then add it to the handler builder:

let handler = RequestHandlerBuilder::new(executor)
    .with_interceptor(limiter)
    .build()?;

§Caller identity

The per-caller key is derived in this order:

  1. CallContext::caller_identity — set by an authentication interceptor. This is the recommended source: it cannot be forged by the client.
  2. The client IP from x-forwarded-for, only when RateLimitConfig::trusted_proxy_hops is non-zero. The header is client-controlled, so by default (trusted_proxy_hops == 0) it is ignored entirely — otherwise a caller could evade the limit by forging a fresh address on every request.
  3. A shared "anonymous" key. All remaining callers share one budget, which keeps the limit enforceable (fail-closed) at the cost of granularity.

§Design

Uses a fixed-window counter per caller key. Windows are aligned to wall clock seconds. When a request exceeds the per-window limit, the before hook returns an error. A2A / JSON-RPC define no dedicated throttling code, so this surfaces as an internal error (-32603) whose message names the rate limit; the request is rejected. (If you need a distinct client-visible signal for backoff, wrap this in a transport adapter that maps the message to your preferred status — e.g. HTTP 429.)

The bucket map is bounded by RateLimitConfig::max_buckets. When the map is full and stale buckets cannot be evicted, requests from new callers are rejected until capacity frees up (fail-closed).

For production deployments requiring sliding windows, distributed counters, or more sophisticated algorithms, implement a custom ServerInterceptor or use a reverse proxy (nginx, Envoy).

Structs§

RateLimitConfig
Configuration for RateLimitInterceptor.
RateLimitInterceptor
A fixed-window rate limiting ServerInterceptor.

Constants§

DEFAULT_MAX_BUCKETS
Default cap on the number of tracked caller buckets.