Crate tower_rate_limit_redis

Crate tower_rate_limit_redis 

Source
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.

NameDefaultDescription
tokioxUse the Tokio runtime
smolUse the smol runtime. Warning: not yet tested with the smol runtime (tests use the Tokio runtime, though they did pass with this feature enabled 😅)
tracingEnable 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§

RateLimitLayer
Enforces rate limit on the underlying service.
RateLimitLayerBuilder
Builder for RateLimitLayer.

Traits§

KeyResolver
Resolves a Redis key for each incoming request.