lazy-limit 1.0.1

lazy-limit is a lightweight Rust library for rate limiting by IP or custom ID, with support for global, router-specific, and fallback rules.
Documentation

Lazy-Limit

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.

Features

  • 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.
  • 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 Arc and RwLock for safe concurrent access.
  • Extensive Testing: Comprehensive unit tests and a demo example to verify functionality.

Installation

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"

Usage

Initializing the Rate Limiter

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
        ]
    ).await;

    // Your application logic here
}

Checking Rate Limits

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.");
}

Override Mode

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.");
}

Example Demo

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 demo

Expected output includes detailed test results for each scenario, confirming the rate limiter's behavior.

Project Structure

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

Configuration Options

  • Default Rule: Set a global rate limit using RuleConfig::new(Duration, limit).
  • Route-Specific Rules: Add rules for specific routes using the routes field in init_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 seconds

Testing

The library includes comprehensive unit tests to ensure reliability:

cargo test --all

This 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

Memory Management

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.

Limitations

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

Contributing

Contributions are welcome! Please submit issues or pull requests to the GitHub repository.

  1. Fork the repository.
  2. Create a new branch (git checkout -b feature/your-feature).
  3. Make your changes and commit (git commit -m "Add your feature").
  4. Push to the branch (git push origin feature/your-feature).
  5. Open a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Contact

For questions or support, please open an issue on the GitHub repository.