Rate Limiting middleware for Tower/Axum/Tonic/Hyper utilizing the governor crate

Overview

A Tower service and layer that provides a rate-limiting backend by governor. Based heavily on the work done for actix-governor. Works with Axum, Hyper, Tonic, and anything else based on Tower!

Features:

  • Rate limit requests based on peer IP address, IP address headers, globally, or via custom keys
  • Custom traffic limiting criteria per second, or to certain bursts
  • Simple to use
  • High customizability
  • High performance
  • Robust yet flexible API

How does it work?

Each governor middleware has a configuration that stores a quota. The quota specifies how many requests can be sent from an IP address before the middleware starts blocking further requests.

For example if the quota allowed ten requests a client could send a burst of ten requests in short time before the middleware starts blocking.

Once at least one element of the quota was used the elements of the quota will be replenished after a specified period.

For example if this period was 2 seconds and the quota was empty it would take 2 seconds to replenish one element of the quota. This means you could send one request every two seconds on average.

If there was a quota that allowed ten requests with the same period a client could again send a burst of ten requests and then had to wait two seconds before sending further requests or 20 seconds before the full quota would be replenished and he could send another burst.

Example

use axum::{error_handling::HandleErrorLayer, routing::get, BoxError, Router};
use std::net::SocketAddr;
use tower::ServiceBuilder;
use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer};

async fn hello() -> &'static str {
   "Hello world"
}

#[tokio::main]
async fn main() {
   // Configure tracing if desired
   // construct a subscriber that prints formatted traces to stdout
   let subscriber = tracing_subscriber::FmtSubscriber::new();
   // use that subscriber to process traces emitted after this point
   tracing::subscriber::set_global_default(subscriber).unwrap();

   // Allow bursts with up to five requests per IP address
   // and replenishes one element every two seconds
   // We Box it because Axum 0.6 requires all Layers to be Clone
   // and thus we need a static reference to it
   let governor_conf = Box::new(
       GovernorConfigBuilder::default()
           .per_second(2)
           .burst_size(5)
           .finish()
           .unwrap(),
   );

   // build our application with a route
   let app = Router::new()
       // `GET /` goes to `root`
       .route("/", get(hello))
       .layer(
           ServiceBuilder::new()
               // this middleware goes above `GovernorLayer` because it will receive
               // errors returned by `GovernorLayer`
               .layer(HandleErrorLayer::new(|e: BoxError| async move {
                   display_error(e)
               }))
               .layer(GovernorLayer {
                   // We can leak this because it is created once and then
                   config: Box::leak(governor_conf),
               }),
       );

   // run our app with hyper
   // `axum::Server` is a re-export of `hyper::Server`
   let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
   tracing::debug!("listening on {}", addr);
   axum::Server::bind(&addr)
       .serve(app.into_make_service_with_connect_info::<SocketAddr>())
       .await
       .unwrap();
}

Configuration presets

Instead of using the configuration builder you can use predefined presets.

  • GovernorConfig::default(): The default configuration which is suitable for most services. Allows bursts with up to eight requests and replenishes one element after 500ms, based on peer IP.

  • GovernorConfig::secure(): A default configuration for security related services. Allows bursts with up to two requests and replenishes one element after four seconds, based on peer IP.

For example the secure configuration can be used as a short version of this code:

use tower_governor::governor::GovernorConfigBuilder;

let config = GovernorConfigBuilder::default()
    .per_second(4)
    .burst_size(2)
    .finish()
    .unwrap();

Customize rate limiting key

By default, rate limiting is done using the peer IP address (i.e. the IP address of the HTTP client that requested your app: either your user or a reverse proxy, depending on your deployment setup). You can configure a different behavior which:

  1. can be useful in itself
  2. allows you to setup multiple instances of this middleware based on different keys (for example, if you want to apply rate limiting with different rates on IP and API keys at the same time)

This is achieved by defining a [KeyExtractor] and giving it to a [Governor] instance. Three ready-to-use key extractors are provided:

  • [PeerIpKeyExtractor]: this is the default, it uses the peer IP address of the request.
  • [SmartIpKeyExtractor]: Looks for common IP identification headers usually provided by reverse proxies in order(x-forwarded-for,x-real-ip, forwarded) and falls back to the peer IP address.
  • [GlobalKeyExtractor]: uses the same key for all incoming requests

Check out the custom_key_bearer example for more information.

Add x-ratelimit headers

By default, x-ratelimit-after is enabled but if you want to enable x-ratelimit-limit, x-ratelimit-whitelisted and x-ratelimit-remaining use the .use_headers() method on your GovernorConfig.

Error Handling

This crate surfaces a GovernorError with suggested headers, and includes a [display_error]: crate::errors::display_error() function that will turn those errors into a Response. Feel free to provide your own error handler that takes in a BoxError and returns a Response. Tower Layers require that all Errors be handled, or it will fail to compile.

Common pitfalls

  1. Do not construct the same configuration multiple times, unless explicitly wanted! This will create an independent rate limiter for each configuration! Instead pass the same configuration reference into Governor::new(), like it is described in the example.

  2. Be careful to create your server with .into_make_service_with_connect_info::<SocketAddr> instead of .into_make_service() if you are using the default PeerIpKeyExtractor. Otherwise there will be no peer ip address for Tower to find!

You might also like...
Yew + Axum + blog = Yab

Yew + Axum + blog = Yab

Demo of Rust and axum web framework

Demo of Rust and axum web framework Demonstration of: Rust: programming language that focuses on reliability and stability. axum: web framework that f

Rust Axum+SQLx Sample

rust-axum-sqlx-sample Install git clone https://github.com/web3ten0/rust-axum-sqlx-1.git cd rust-axum-sqlx-1/local docker-compose up -d sh scripts/exe

🪪 Session-based user authentication for Axum.

axum-login 🪪 Session-based user authentication for Axum. 🎨 Overview axum-login is a Tower middleware providing session-based user authentication for

Layers, extractors and template engine wrappers for axum based Web MVC applications

axum-template Layers, extractors and template engine wrappers for axum based Web MVC applications Getting started Cargo.toml [dependencies] axum-templ

Provides json/csv/protobuf streaming support for axum

axum streams for Rust Library provides HTTP response streaming support for axum web framework: JSON array stream format JSON lines stream format CSV s

Simple example of axum, sqlx with sqlite and utoipa (swagger) - without auth

axum_crud_api Simple example to learn creating CRUD rest apis in Rust with axum, sqlx with sqlite and utoipa (swagger) - without auth Also shows how t

Heavy Metal Leptos Stack with Tailwind, Axum, Sqlite, and Cargo Leptos

Heavy Metal Stack Leptos stack with Axum, TailwindCSS, and Sqlite This example creates a basic todo app with an Axum backend that uses Leptos' server

An adapter to easily allow an Axum server to be run within a Cloudflare worker.

axum-cloudflare-adapter An adapter to easily allow an Axum server to be run within a Cloudflare worker. Usage use worker::*; use axum::{ response

Comments
  • Error reported on axum version 0.6.1

    Error reported on axum version 0.6.1

    Hi, first of all thank you for such a useful crate, when I upgraded axum to 0.6.1 it apparently had a compatibility issue, see the log below for details:

    error[E0277]: the trait bound `GovernorLayer<'_, PeerIpKeyExtractor, governor::middleware::StateInformationMiddleware>: Clone` is not satisfied
       --> src/main.rs:50:9
        |
    49  |       let app = routes::build().layer(Extension(database)).layer(
        |                                                            ----- required by a bound introduced by this call
    50  | /         ServiceBuilder::new()
    51  | |             .layer(HandleErrorLayer::new(|err: BoxError| async move {
    52  | |                 (
    53  | |                     StatusCode::INTERNAL_SERVER_ERROR,
    ...   |
    58  | |                 config: &governor_conf,
    59  | |             }),
        | |______________^ the trait `Clone` is not implemented for `GovernorLayer<'_, PeerIpKeyExtractor, governor::middleware::StateInformationMiddleware>`
        |
        = note: required for `GovernorLayer<'_, PeerIpKeyExtractor, governor::middleware::StateInformationMiddleware>` to implement `Clone`
        = note: 2 redundant requirements hidden
        = note: required for `ServiceBuilder<Stack<GovernorLayer<'_, PeerIpKeyExtractor, governor::middleware::StateInformationMiddleware>, Stack<..., ...>>>` to implement `Clone`
    note: required by a bound in `Router::<S, B>::layer`
       --> /Users/wangeguo/.cargo/registry/src/mirrors.ustc.edu.cn-12df342d903acd47/axum-0.6.1/src/routing/mod.rs:316:30
        |
    316 |         L: Layer<Route<B>> + Clone + Send + 'static,
        |                              ^^^^^ required by this bound in `Router::<S, B>::layer`
    help: consider borrowing here
        |
    50  |         &ServiceBuilder::new()
        |         +
    
    For more information about this error, try `rustc --explain E0277`.
    
    opened by wangeguo 3
  • lib: make GovernorError pub

    lib: make GovernorError pub

    Making GovernorError pub. I need to match inner error types and have a custom error handling. In my case I use https://www.rfc-editor.org/rfc/rfc7807 https://crates.io/crates/http-api-problem to return errors in this format.

    opened by ernestas-poskus 1
  • Remove unused associated types and trait bounds from KeyExtractor, fix up tests

    Remove unused associated types and trait bounds from KeyExtractor, fix up tests

    Debug requirement was not strictly necessary and overlaps in purpose with name(). KeyExtractionError was entirely unused, and callers of the trait assume GovernorError.

    opened by untitaker 0
Owner
Ben Wishovich
I'm passionate about React, Typescript, Python, and Rust. I build unique experiences on the web. Full stack engineer with a background in SQA and Hardware
Ben Wishovich
Ergonomic and modular web framework built with Tokio, Tower, and Hyper

axum axum is a web application framework that focuses on ergonomics and modularity. More information about this crate can be found in the crate docume

Tokio 7.9k Dec 31, 2022
A cookie manager middleware built on top of tower.

tower-cookies A cookie manager middleware built on top of tower. Example With axum: use axum::{handler::get, Router}; use std::net::SocketAddr; use to

Imbolc 47 Dec 9, 2022
Rate limit middleware for Poem framework

Rate limit middleware for Poem framework Usage Check examples, poem-ratelimit is available on crates.io. A yaml configuration file is used to set limi

Devs Day 5 Sep 22, 2022
🔎 Prometheus metrics middleware for Axum

Axum-Prometheus A Prometheus middleware to collect HTTP metrics for Axum applications. axum-prometheus relies on metrics_exporter_prometheus as a back

Péter Leéh 14 Jan 4, 2023
Axum + JWT authentication Middleware that allows you to start building your application fast

axum_jwt_ware Integration Guide Simple Axum + JWT authentication middleware with implemented Login and refresh token. Goal I aim to simplify the proce

Eze Sunday 3 Dec 2, 2023
axum-serde is a library that provides multiple serde-based extractors and responders for the Axum web framework.

axum-serde ?? Overview axum-serde is a library that provides multiple serde-based extractors / responses for the Axum web framework. It also offers a

GengTeng 3 Dec 12, 2023
A crate built on top of `axum-sessions`, implementing the CSRF Synchronizer Token Pattern

Axum Synchronizer Token Pattern CSRF prevention This crate provides a Cross-Site Request Forgery protection layer and middleware for use with the axum

null 3 Dec 15, 2022
Write Rack middleware in Rust.

RacksOnRacks Write Rack middleware in Rust. This repo is a proof of concept and should be used as an example. The best way to use this at the moment w

Odin 5 Jul 11, 2023
Experiments with Rust CRDTs using Tokio web application framework Axum.

crdt-genome Synopsis Experiments with Rust CRDTs using Tokio web application framework Axum. Background Exploring some ideas of Martin Kleppmann, part

dougfort 3 Mar 18, 2022
Axum web framework tutorial for beginners.

Axum Tutorial For Beginners Hello web developers! This tutorial will cover how to write simple web applications in rust with axum framework. If you ar

Eray Karatay 46 Jan 5, 2023