throttle-kit 0.4.1

Rate limiting for Rust — GCRA algorithm with in-memory and Redis backends, Tower layer integration
Documentation

ratelimit

Rate limiting for Rust — GCRA (Generic Cell Rate Algorithm) with pluggable backends (in-memory, Redis) and optional Tower layer integration.

CI crates.io license

Features

  • GCRA algorithm — smooth, memory-efficient rate limiting
  • In-memory backend via DashMap (default)
  • Optional Redis backend for distributed deployments
  • Tower Layer with X-RateLimit-* response headers
  • Configurable burst / token-bucket capacity

What is GCRA?

The Generic Cell Rate Algorithm treats each key as a leaky bucket. A request is allowed if the bucket has capacity; otherwise it is rejected. Tokens are replenished at a constant rate derived from the desired RPS/RPM.

allowed if:  ema ≤ limit
where ema(t) = max(0, ema(t-1) - (t - t_last)) + 1

This yields the same behaviour as a token bucket but only stores the last emission time — one Instant per key.

Quick Start

use ratelimit::{RateLimiter, Quota, InMemoryBackend};

#[tokio::main]
async fn main() {
    let backend = InMemoryBackend::new();
    let limiter = RateLimiter::new(Quota::per_second(100), backend);

    let result = limiter.check("api-key-abc").await;
    assert!(result.allowed);
    println!("remaining: {}", result.remaining);
}

Presets

Constructor Rate Burst
Quota::per_second n / s n
Quota::per_minute n / 60 s n
Quota::per_hour n / 3600 s n

Override burst with .allow_burst(n).

Tower Integration

By default the layer keys requests by the client's socket address and ignores X-Forwarded-For entirely (secure by default — a client-set XFF header cannot mint fresh budgets).

use ratelimit::{RateLimiter, Quota, InMemoryBackend};
use ratelimit::RateLimitLayer;
use axum::{Router, extract::ConnectInfo, routing::get};
use std::net::SocketAddr;

let backend = InMemoryBackend::new();
let layer = RateLimitLayer::new(Quota::per_second(50), backend);

let app = Router::new()
    .route("/", get(handler))
    .layer(layer);

// Serve with connect info so the layer can see the peer address.
// Without it, requests are rejected with 503 (fail closed) unless a
// MissingClientPolicy::FallbackKey is configured.
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(
    listener,
    app.into_make_service_with_connect_info::<SocketAddr>(),
).await?;

Behind a proxy

If the service sits behind proxies you control, tell the layer which peers it may believe, and how many header entries your infrastructure appends in front of the direct peer (num_trusted_hops):

use ratelimit::client_ip::{ClientIpConfig, IpNet};

// internet → ALB (trusted) → nginx (trusted, direct peer) → app
// nginx appends the ALB's address, so XFF is "client, alb-ip";
// the default num_trusted_hops = 1 skips "alb-ip" and yields "client".
let layer = RateLimitLayer::new(Quota::per_second(50), backend)
    .with_client_ip(ClientIpConfig {
        trusted_proxies: vec![IpNet::parse("10.0.0.0/8").unwrap()],
        num_trusted_hops: 1,                       // default
        trusted_header: None,                      // default: X-Forwarded-For
    });

Resolution walks the header RIGHT-TO-LEFT and never trusts entries left of the resolved one, so client-spoofed XFF values are ignored. For a single internet → nginx → app proxy, set num_trusted_hops: 0. Platforms with a dedicated header (CF-Connecting-IP) can set trusted_header; same trust rules apply.

Migrating from 0.3.0: if you were behind a proxy, you must configure trusted_proxies (the old trust-XFF-unconditionally behavior is gone); if your service is directly exposed, you need nothing — the default is stricter and safe. Callers keying by API keys instead of IP can use .with_key_extractor(...).

Comparison with governor

ratelimit governor
Algorithm GCRA (leaky bucket) GCRA (leaky bucket)
Backends In-memory, Redis In-memory only
Tower layer Optional Built-in
Dependencies Minimal Jitter + parking_lot
Burst support .allow_burst() Fixed per-quota

License

Licensed under either of MIT or Apache-2.0 at your option.

Security

Threat model: THREAT-MODEL.md.

Performance

Measured hot-path SLOs and allocation profile: PERF-SLO.md. Benchmarks run in CI (non-gating regression visibility against the saved ci baseline).