Lazy-Limit is a lightweight and flexible Rust library for implementing rate limiting based on IP addresses or custom identifiers. It supports global rate limits, route-specific rules, and an override mode for fine-grained control. Designed for ease of use, it integrates seamlessly with asynchronous Rust applications using Tokio, making it ideal for web servers, APIs, or any networked application requiring rate limiting.
- Global Rate Limiting: Apply a default rate limit across all requests.
- Route-Specific Rules: Define custom rate limits for specific routes or endpoints.
- Override Mode: Bypass global limits to enforce only route-specific rules when needed.
- Match Prefix: Apply rate limit to a route prefix.
- Methods Matching: Apply rate limit to specific HTTP methods, like [POST, DELETE] for example.
- Memory Management: Built-in garbage collection to manage memory usage for request records.
- Asynchronous Design: Built on Tokio for non-blocking, high-performance rate limiting.
- Customizable Configuration: Set maximum memory usage, garbage collection intervals, and more.
- Thread-Safe: Uses
ArcandRwLockfor safe concurrent access. - Extensive Testing: Comprehensive unit tests and a demo example to verify functionality.
Add the following to your Cargo.toml:
[dependencies]
lazy-limit = "1"Ensure you have the required dependencies:
tokio = { version = "1", features = ["full"] }
once_cell = "1"The rate limiter must be initialized once at application startup using the init_rate_limiter! macro. You can specify a default global rule, optional route-specific rules, and memory limits.
use lazy_limit::*;
use std::time::Duration as StdDuration;
#[tokio::main]
async fn main() {
init_rate_limiter!(
default: RuleConfig::new(Duration::seconds(1), 5), // 5 req/s globally
max_memory: Some(64 * 1024 * 1024), // 64MB max memory
routes: [
("/api/login", RuleConfig::new(Duration::minutes(1), 3)), // 3 req/min
("/api/public", RuleConfig::new(Duration::seconds(1), 10)), // 10 req/s
("/api/premium", RuleConfig::new(Duration::seconds(1), 20)), // 20 req/s
("/api/prefix/", RuleConfig::new(Duration::seconds(1), 3).match_prefix(true)), // 3 req/s, match prefix only
("/api/method/", RuleConfig::new(Duration::seconds(1), 2).for_methods(vec![HttpMethod::POST])), // 2 req/s, for POST only
]
).await;
// Your application logic here
}Use the limit! macro to check if a request should be allowed based on the identifier (e.g., IP address) and route.
let allowed = limit!("1.1.1.1", "/api/public").await;
if allowed {
println!("Request allowed!");
} else {
println!("Request denied: rate limit exceeded.");
}If you only want to limit certain methods, you can also specify them:
let allowed = limit!("1.1.1.1", "/api/public", Some(HttpMethod::POST)).await;
if allowed {
println!("Post request allowed!");
} else {
println!("Post request denied: rate limit exceeded.");
}You can also map you own Methods with a helper function:
fn map_method(m: http::Method) -> HttpMethod {
match m {
http::Method::GET => HttpMethod::GET,
http::Method::POST => HttpMethod::POST,
http::Method::PUT => HttpMethod::PUT,
http::Method::DELETE => HttpMethod::DELETE,
http::Method::PATCH => HttpMethod::PATCH,
http::Method::HEAD => HttpMethod::HEAD,
http::Method::OPTIONS => HttpMethod::OPTIONS,
http::Method::CONNECT => HttpMethod::CONNECT,
http::Method::TRACE => HttpMethod::TRACE,
_ => HttpMethod::OTHER,
}
}
let allowed = limit!("1.1.1.1", "/api/public", Some(map_method(http::Method::POST))).await;Use the limit_override! macro to apply only route-specific rules, ignoring the global limit.
let allowed = limit_override!("1.1.1.1", "/api/premium").await;
if allowed {
println!("Request allowed in override mode!");
} else {
println!("Request denied in override mode.");
}The library includes a demo in examples/demo.rs that showcases its features:
- Basic Global Rate Limiting: Tests the global limit of 5 requests per second.
- Route-Specific Rules: Demonstrates how global and route-specific limits interact.
- Override Mode: Shows how to bypass global limits for specific routes.
- Multiple Users: Verifies independent rate limiting for different identifiers.
- Long Interval Rules: Tests rules with longer time windows (e.g., 3 requests per minute).
To run the demo:
cargo run --example demoExpected output includes detailed test results for each scenario, confirming the rate limiter's behavior.
lazy-limit/
├── examples/
│ └── demo.rs # Example demonstrating rate limiting features
├── src/
│ ├── config.rs # Configuration for rate limiter rules
│ ├── gc.rs # Garbage collection for memory management
│ ├── lib.rs # Main library entry point and macros
│ ├── limiter.rs # Core rate limiter implementation
│ └── types.rs # Data types for duration, rules, and request records
├── Cargo.toml # Project metadata and dependencies
├── LICENSE # MIT License
└── README.md # This file
- Default Rule: Set a global rate limit using
RuleConfig::new(Duration, limit). - Route-Specific Rules: Add rules for specific routes using the
routesfield ininit_rate_limiter!. - Max Memory: Limit memory usage for request records (default: 64MB).
- Garbage Collection Interval: Configure how often stale records are cleaned (default: 10 seconds).
Example configuration:
let config = LimiterConfig::new(RuleConfig::new(Duration::seconds(1), 5))
.add_route_rule("/api/login", RuleConfig::new(Duration::minutes(1), 3))
.with_max_memory(32 * 1024 * 1024) // 32MB
.with_gc_interval(5); // GC every 5 secondsThe library includes comprehensive unit tests to ensure reliability:
cargo test --allThis runs tests in src/lib.rs and src/limiter.rs, covering:
- Basic rate limiting
- Route-specific rules with global limits
- Override mode
- Multiple users
- Long interval rules
The rate limiter includes a garbage collector (gc.rs) that:
- Routine Cleanup: Removes stale records older than the longest rule interval plus a 5-minute buffer.
- Aggressive Cleanup: Triggered when memory usage exceeds the configured limit, removing oldest entries to stay within 80% of the max memory.
The garbage collector runs asynchronously in a Tokio task, ensuring non-blocking operation.
- Single Initialization: The rate limiter can only be initialized once. Attempting to call
init_rate_limiter!multiple times will panic. - Static Configuration: Rules are set at initialization and cannot be modified at runtime.
- Memory Estimation: Memory usage calculations are approximate and may vary based on the Rust allocator.
Contributions are welcome! Please submit issues or pull requests to the GitHub repository.
- Fork the repository.
- Create a new branch (
git checkout -b feature/your-feature). - Make your changes and commit (
git commit -m "Add your feature"). - Push to the branch (
git push origin feature/your-feature). - Open a pull request.
This project is licensed under the MIT License. See the LICENSE file for details.
For questions or support, please open an issue on the GitHub repository.