Expand description
§Overview
A Redis-backed rate-limiting middleware for Tower using Fred.
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 Fred 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
| Name | Default | Description |
|---|---|---|
| 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 fred::{
interfaces::ClientLike,
types::{Builder, Key},
};
use http::{Request, Response, StatusCode, header::AUTHORIZATION};
use std::{io, time::Duration};
use tokio::net::TcpListener;
use tower_rate_limit_fred::{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 ErrorBody = Body;
fn key<T>(&self, req: &Request<T>) -> Result<Key, Response<Self::ErrorBody>> {
req.headers()
.get(AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.and_then(|s| s.strip_prefix("Bearer "))
.map(Key::from)
.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 pool = Builder::default_centralized()
.with_performance_config(|config| {
// highly advised since Fred hangs forever if Redis is unavailable:
// https://github.com/aembke/fred.rs/issues/353
config.default_command_timeout = Duration::from_millis(300);
})
.build_pool(12)
.expect("Failed to create Redis pool");
pool.init().await.expect("Failed to connect to Redis pool");
let layer = RateLimitLayer::builder()
.conn(pool)
.key_resolver(BearerTokenResolver)
.capacity(5)
.emission_interval(Duration::from_secs(2))
.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.