# route-ratelimit
[](https://crates.io/crates/route-ratelimit)
[](https://docs.rs/route-ratelimit)
[](https://opensource.org/licenses/MIT)
[](https://github.com/haut/route-ratelimit/actions/workflows/ci.yml)
[](https://blog.rust-lang.org/2025/06/26/Rust-1.88.0.html)
Route-based rate limiting middleware for [reqwest](https://github.com/seanmonstar/reqwest).
## Features
- **Endpoint matching**: Match requests by host, HTTP method, and path prefix
- **Multiple rate limits**: Stack burst and sustained limits on the same endpoint
- **Configurable behavior**: Choose to delay requests or return errors per endpoint
- **Lock-free performance**: Uses GCRA algorithm with atomic operations
- **Shared state**: Rate limits are tracked across all client clones
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
route-ratelimit = "0.1"
reqwest = "0.12"
reqwest-middleware = "0.4"
```
## Quick Start
```rust
use route_ratelimit::RateLimitMiddleware;
use reqwest_middleware::ClientBuilder;
use std::time::Duration;
#[tokio::main]
async fn main() {
let middleware = RateLimitMiddleware::builder()
.route(|r| r.limit(100, Duration::from_secs(10)))
.build();
let client = ClientBuilder::new(reqwest::Client::new())
.with(middleware)
.build();
// Requests are automatically rate-limited
client.get("https://api.example.com/data").send().await.unwrap();
}
```
## Usage
### Host-Scoped Routes
Organize rate limits by host for cleaner configuration:
```rust
use route_ratelimit::RateLimitMiddleware;
use std::time::Duration;
use http::Method;
let middleware = RateLimitMiddleware::builder()
.host("api.example.com", |host| {
host
// General limit for all endpoints on this host
.route(|r| r.limit(9000, Duration::from_secs(10)))
// Specific limit for /book endpoints (both limits apply)
.route(|r| r.path("/book").limit(1500, Duration::from_secs(10)))
// Method + path specific limits
.route(|r| {
r.method(Method::POST)
.path("/order")
.limit(3500, Duration::from_secs(10)) // Burst
.limit(36000, Duration::from_secs(600)) // Sustained
})
})
.build();
```
### Multiple Limits (Burst + Sustained)
Apply both burst and sustained limits to the same endpoint:
```rust
use route_ratelimit::RateLimitMiddleware;
use std::time::Duration;
let middleware = RateLimitMiddleware::builder()
.route(|r| {
r.path("/api")
.limit(100, Duration::from_secs(10)) // Burst: 100 req/10s
.limit(1000, Duration::from_secs(600)) // Sustained: 1000 req/10min
})
.build();
```
### Error Behavior
By default, requests are delayed until they can proceed. Use `ThrottleBehavior::Error` to fail fast:
```rust
use route_ratelimit::{RateLimitMiddleware, ThrottleBehavior};
use std::time::Duration;
let middleware = RateLimitMiddleware::builder()
.route(|r| {
r.limit(10, Duration::from_secs(1))
.on_limit(ThrottleBehavior::Error) // Return error immediately
})
.build();
```
## Route Matching
### All Matching Routes Apply
Routes are checked in order, and **all matching routes' limits are applied**. This allows layering general limits with specific ones:
```rust
use route_ratelimit::RateLimitMiddleware;
use std::time::Duration;
let middleware = RateLimitMiddleware::builder()
.host("api.example.com", |host| {
host
// This applies to ALL requests to api.example.com
.route(|r| r.limit(9000, Duration::from_secs(10)))
// This ALSO applies to /book requests (both limits enforced)
.route(|r| r.path("/book").limit(1500, Duration::from_secs(10)))
})
.build();
```
### Host Matching
Host matching uses only the hostname, **excluding the port**:
```rust
// Matches: https://api.example.com/path
// Matches: https://api.example.com:8443/path
// Does NOT match: https://other.example.com/path
### Path Matching
Path matching uses **segment boundaries**, not simple prefix matching:
| `/order` | `/order`, `/order/`, `/order/123` | `/orders`, `/order-test` |
| `/api/v1` | `/api/v1/users`, `/api/v1/` | `/api/v2`, `/api/v10` |
## Optional Features
### Tracing Support
Enable the `tracing` feature for diagnostic logging:
```toml
[dependencies]
route-ratelimit = { version = "0.1", features = ["tracing"] }
```
This enables warnings for potentially problematic configurations (e.g., catch-all routes preceding specific routes).
## Memory Management
For long-running applications, periodically clean up stale rate limit state:
```rust
use route_ratelimit::RateLimitMiddleware;
use std::time::Duration;
let middleware = RateLimitMiddleware::builder()
.route(|r| r.limit(100, Duration::from_secs(10)))
.build();
// Call periodically (e.g., every hour)
middleware.cleanup();
// Monitor state size
println!("Active rate limit entries: {}", middleware.state_count());
```
## Examples
See the [examples](examples/) directory for complete usage examples:
- [Polymarket API](examples/polymarket.rs) - Complete rate limit configuration for a real-world API
## Minimum Supported Rust Version
This crate requires Rust 1.88.0 or later.
## License
Licensed under the MIT License. See [LICENSE](LICENSE) for details.