Expand description
§Overview
A Redis-backed rate-limiting middleware for Tower using redis-rs.
Rate limiting is based on the GCRA,
which allows one request every emission_interval, with unused allowance accumulating up to capacity to allow bursts.
§Required permissions
Besides the commands redis-rs internally calls for all connections, this crate additionally uses the following commands:
§Limitations
All of Redis, Valkey, and Redict currently embed Lua 5.1.
Until they upgrade it to 5.3 or later, integers are stored as doubles, which start losing precision beyond 2^53.
This occurs if the rate limit resets after May 2057, which is possible (but strongly discouraged) with improperly high capacity and emission_interval, combined with heavy traffic.
§Features
Exactly one of the tokio and smol features must be enabled.
| Name | Default | Description |
|---|---|---|
| tokio | x | Use the Tokio runtime |
| smol | Use the smol runtime. Warning: not yet tested with the smol runtime (tests use the Tokio runtime, though they did pass with this feature enabled 😅) | |
| tracing | Enable tracing. Keys are recorded as &strs if valid UTF-8, or as raw &[u8] otherwise |
§Example
use axum::{Router, body::Body, response::IntoResponse, routing::get};
use http::{Request, Response, StatusCode, header::AUTHORIZATION};
use redis::Client;
use std::{io, time::Duration};
use tokio::net::TcpListener;
use tower_rate_limit_redis::{KeyResolver, RateLimitLayer};
// Rate limit based on the bearer token, returning 401 if the token is missing.
#[derive(Clone)]
struct BearerTokenResolver;
impl KeyResolver for BearerTokenResolver {
type Key = String;
type ErrorBody = Body;
fn key<T>(&self, req: &Request<T>) -> Result<Self::Key, Response<Self::ErrorBody>> {
req.headers()
.get(AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.and_then(|s| s.strip_prefix("Bearer "))
.map(ToString::to_string)
.ok_or_else(|| (StatusCode::UNAUTHORIZED, "Missing Bearer token").into_response())
}
}
#[tokio::main]
async fn main() -> io::Result<()> {
let listener = TcpListener::bind("localhost:8080").await?;
let conn = Client::open("valkey://localhost")
.expect("Failed to create Valkey client")
.get_multiplexed_async_connection()
.await
.expect("Failed to connect to Valkey");
let layer = RateLimitLayer::builder()
.conn(conn)
.key_resolver(BearerTokenResolver)
.capacity(5)
.emission_interval(Duration::from_secs(2))
.timeout(Duration::from_millis(300))
.build();
let router = Router::new()
.route("/", get(|| async { StatusCode::NO_CONTENT }))
.route_layer(layer);
axum::serve(listener, router)
.with_graceful_shutdown(async {}) // shut down the server so the test terminates lol
.await
}Structs§
- Rate
Limit Layer - Enforces rate limit on the underlying service.
- Rate
Limit Layer Builder - Builder for
RateLimitLayer.
Traits§
- KeyResolver
- Resolves a Redis key for each incoming request.