tower-governor

repository·main·Indexed 43 days ago

https://github.com/benwis/tower-governor

A rate-limiting middleware for Tower backed by the governor crate, providing configurable key-based and global limits. It is compatible with Tower-based frameworks such as Axum, Hyper, and Tonic. Version 0.8.0 features customizable KeyExtractors (PeerIp, SmartIp, and Global), configurable burst sizes and replenishment rates via GovernorConfigBuilder, and built-in support for rate-limit HTTP headers.

Tokens
4.4K
Snippets
10
Records
22
Agent score
56%

What's inside tower-governor

  1. Choose a rate limiting key extractor

    main

    You can control how requests are identified for rate limiting by providing a KeyExtractor. This allows you to limit by IP, by proxy-provided headers, or globally.

    Available extractors:

    • PeerIpKeyExtractor: (Default) Uses the peer IP address of the request.
    • SmartIpKeyExtractor: Looks for common IP identification headers (in order: x-forwarded-for, x-real-ip, forwarded) and falls back to the peer IP.
    • GlobalKeyExtractor: Uses the same key for all incoming requests, effectively applying a global rate limit.

    Custom extractors can be implemented by defining your own KeyExtractor (e.g., for API keys).

  2. Integrate tower-governor with Axum

    main

    To use tower-governor with the Axum web framework, add the axum feature to your Cargo.toml. You must use .into_make_service_with_connect_info::<SocketAddr>() when serving your application to ensure the PeerIpKeyExtractor (the default) can find the client's IP address in the request extensions.

    Steps:

    1. Configure a GovernorConfig using GovernorConfigBuilder.
    2. Apply the GovernorLayer to your Axum Router.
    3. Serve the app using into_make_service_with_connect_info::<SocketAddr>() to provide the necessary connection information.
    use axum::{error_handling::HandleErrorLayer, routing::get, BoxError, Router};
    use std::net::SocketAddr;
    use std::sync::Arc;
    use std::time::Duration;
    use tokio::net::TcpListener;
    use tower::ServiceBuilder;
    use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer};
    
    async fn hello() -> &'static str {
        "Hello world"
    }
    
    #[tokio::main]
    async fn main() {
        // Allow bursts with up to five requests per IP address
        // and replenishes one element every two seconds
        let governor_conf = GovernorConfigBuilder::default()
            .per_second(2)
            .burst_size(5)
            .finish()
            .unwrap();
    
        let governor_limiter = governor_conf.limiter().clone();
        let interval = Duration::from_secs(60);
        
        // Background task to clean up storage
        std::thread::spawn(move || {
            loop {
                std::thread::sleep(interval);
                governor_limiter.retain_recent();
            }
        });
    
        let app = Router::new()
            .route("/", get(hello))
            .layer(GovernorLayer::new(governor_conf));
    
        let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
        let listener = TcpListener::bind(addr).await.unwrap();
        axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
            .await
            .unwrap();
    }
  3. Configure tower-governor with no-default-features

    main

    If you disable the default feature in Cargo.toml, the PeerIpKeyExtractor and SmartIpKeyExtractor will no longer automatically find the IP. Instead, they will expect a std::net::SocketAddr to be present in the request's Extensions.

    If a valid SocketAddr is not provided in the extensions, the middleware will return a GovernorError::UnableToExtractKey.

    Cargo.toml configuration:

    [dependencies]
    tower-governor = { version = "0.3", default-features = false }

    Example of manually inserting the address into extensions:

    let service = ServiceBuilder::new()
        .map_request(|(mut req, addr): (Request<()>, SocketAddr)| {
            req.extensions_mut().insert(addr);
            req
        })
        .layer(GovernorLayer::new(config))
        .service(service);
  4. Understand rate limit headers provided by Governor

    main

    Depending on the middleware used, tower-governor can automatically inject rate-limiting information into the HTTP response headers.

    When using StateInformationMiddleware, the following headers are added to successful responses:

    • x-ratelimit-limit: The total burst size (quota).
    • x-ratelimit-remaining: The remaining burst capacity in the current window.

    When a rate limit is exceeded (TooManyRequests), the following headers are included in the error response:

    • x-ratelimit-after: Seconds to wait until the quota resets.
    • retry-after: Seconds to wait until the quota resets.
    • x-ratelimit-limit: The total burst size.
    • x-ratelimit-remaining: Set to 0.
  5. Avoid common pitfalls with tower-governor

    main
    1. Configuration Reuse: Do not construct the same configuration multiple times unless you want independent rate limiters. Instead, create one configuration and pass a reference to multiple Governor instances.
    2. Axum Connection Info: When using the default PeerIpKeyExtractor with Axum, you must use .into_make_service_with_connect_info::<SocketAddr>(). Failing to do so means the middleware cannot find the peer IP in the request extensions.
    3. Tonic IP Extraction: When using tower-governor with tonic, you must extract the IP address within a Tonic interceptor.
  6. Configure rate limiting with GovernorConfigBuilder

    main

    Use GovernorConfigBuilder to define custom rate limiting behavior. The quota determines how many requests are allowed before blocking occurs and how quickly the quota replenishes.

    Key methods:

    • .per_second(u32): Sets the replenishment rate (how many elements are added back to the quota per second).
    • .burst_size(u32): Sets the maximum number of requests allowed in a burst.
    • .use_headers(): Enables additional rate limit headers in responses.
    • .finish(): Finalizes the configuration.

    To enable extra headers like x-ratelimit-limit, x-ratelimit-whitelisted, and x-ratelimit-remaining, call .use_headers() on the builder. By default, only x-ratelimit-after and retry-after are sent.

    let governor_conf = GovernorConfigBuilder::default()
        .per_second(2)
        .burst_size(5)
        .use_headers()
        .finish()
        .unwrap();
  7. Handle Governor errors and custom responses

    main

    tower-governor surfaces a GovernorError.

    • Automatic Conversion: If the axum or tonic features are enabled, GovernorError is automatically converted into a response with the appropriate body type via the From trait.
    • Custom Error Handling: To return a custom response when rate limiting is triggered, use the .error_handler method on either GovernorLayer or governor::Governor to transform errors into a Response.
  8. Use configuration presets

    main

    If you don't need custom values, use the following GovernorConfig presets:

    • GovernorConfig::default(): Suitable for most services. Allows bursts of up to 8 requests and replenishes 1 element every 500ms, based on peer IP.
    • GovernorConfig::secure(): For security-sensitive services. Allows bursts of up to 2 requests and replenishes 1 element every 4 seconds, based on peer IP.
  9. Use the secure configuration preset

    main

    For security-sensitive services (like login endpoints), use GovernorConfig::secure(). This provides a strict rate limit designed to prevent brute-forcing:

    • Burst size: 2 requests
    • Replenishment period: 4 seconds
    • Key extractor: PeerIpKeyExtractor (by IP)
    use tower_governor::governor::GovernorConfig;
    
    let config = GovernorConfig::secure();
  10. Implement a custom KeyExtractor

    main

    To define how clients are identified for rate limiting, implement the KeyExtractor trait. The trait requires defining a Key type (which must implement Clone + Hash + Eq + Debug) and an extract method that takes a &Request<T> and returns Result<Self::Key, GovernorError>.

    If the tracing feature is enabled, you can also implement name() and key_name() to provide metadata for logs and traces.

    impl KeyExtractor for MyCustomExtractor {
        type Key = String;
    
        fn extract<T>(&self, req: &Request<T>) -> Result<Self::Key, GovernorError> {
            // Logic to extract a unique identifier (e.g., an API key or User ID)
            let user_id = req.headers().get("x-user-id")
                .and_then(|v| v.to_str().ok())
                .map(|s| s.to_string())
                .ok_or(GovernorError::UnableToExtractKey)?;
            
            Ok(user_id)
        }
    }
  11. Configure a custom error handler for GovernorLayer

    main

    You can customize how rate-limiting errors (like TooManyRequests) are converted into HTTP responses by calling .error_handler() on a GovernorLayer.

    The handler is a closure with the signature Fn(GovernorError) -> Response<RespBody> + Send + Sync + 'static. If no handler is provided, the layer relies on the response body type implementing From<GovernorError>.

    layer.error_handler(|err| {
        // err is a GovernorError
        // Return a custom http::Response<RespBody>
        Response::new(body)
    })
  12. Configure rate limit burst size

    main

    The burst_size defines the maximum number of requests that can occur simultaneously before the middleware starts blocking requests. Once the burst is exhausted, clients must wait for the quota to replenish according to the configured period.

    Use .burst_size(size: u32) on the GovernorConfigBuilder to set this value. The value must be greater than zero.