https://github.com/jeanvydes/nervio-limiter
The agnostic rate limiter for Rust
https://github.com/jeanvydes/nervio-limiter
http-server rate-limit rate-limiting rust
Last synced: 2 months ago
JSON representation
The agnostic rate limiter for Rust
- Host: GitHub
- URL: https://github.com/jeanvydes/nervio-limiter
- Owner: JeanVydes
- License: mit
- Created: 2024-07-18T21:47:54.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2024-09-02T04:27:32.000Z (8 months ago)
- Last Synced: 2025-01-17T07:42:46.060Z (4 months ago)
- Topics: http-server, rate-limit, rate-limiting, rust
- Language: Rust
- Homepage:
- Size: 16.6 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Nervio Limiter
The agnostic rate limiter for the modern and fast web.
## Why?
Because i want
## Examples as Middleware
### Currently Supported Built In Middlewares
Feel free to build your own middleware, and with your custom logic. Check for `src/middlewares` for examples for available frameworks.Automically middlewares set `x-ratelimit-remaining`, `x-ratelimit-limit` and `x-ratelimit-reset`, but `limiter.limit_this()` used as base of the middleware return more, `x-ratelimit-bucket` and `x-ratelimit-key` headers that can be set. But for simplicity only the first three are set.
* Actix Web
### `Limiter::limit_this`
This functions is the golden chicken, is the way to rate limit, and its used in the middlewares, you have to provide a entity_key for example, an account ID, an IP, or whatever, that is the entity or object to rate limit, and a bucket config where this entity will obey.
```rust
// Example ID to Limit
let ip = "127.0.0.1";
let limiter = nervio_limiter::limiter::Limiter::new(...);// This can be put example in a middleware, route or what you need...
match limiter
.limit_this(
ip.to_owned(),
BucketConfig {
// Bucket Name
name: "my.global.bucket".to_owned(),
// Limit Entity Type
limit_by: LimitEntityType::IP,
// Maximum Requests per Cycle
max_requests_per_cycle: 120,
// And the cycle Duration
cycle_duration: Duration::from_secs(3600), // 1 Hour
// This will result in a bucket that rate limit at 120 Requests per Hour per entity (in this case an IP)
},
)
.await
{
Ok(limiter_headers) => {
// Do what you need with this headers, usually put in the response
}
Err(err) => {
// Errors, including be limitedmatch err {
LimiterError::Limited => {
// return a too_much_requests response
}
LimiterError::MemoryLimitExceeded => {
// return a too_much_requests response
}
LimiterError::RedisMemoryExceeded => {
// return a too_much_requests response
}
LimiterError::BothMemoryAndRedisMemoryExceeded => {
// return a too_much_requests response
}
_ => {
// Serious errors not related to limits
}
}
}
};
```### `Limiter::limit_this` response is a set of headers as LimiterHeaders
```rust
#[derive(Debug, Clone)]
pub struct LimiterHeaders {
pub key: String,
pub bucket: String,
pub limit: u64,
pub remaining: u64,
pub reset: u64,
}
```* `x-ratelimit-remaining`: Remaining available requests
* `x-ratelimit-limit`: Initial limit supply
* `x-ratelimit-reset`: Normally this is a *from now when will expire* but i dont like it because isn't precise, so i return a UNIX timestamp in millseconds.
* `x-ratelimit-bucket`: Bucket Name
* `x-ratelimit-key`: The internal key to track the rate limit for requests**NOTE** ActixWebLimiterMiddleware, is only available behind `actix-web` feature flag
## Actix Web
```rust
use std::{sync::Arc, time::Duration};
use actix_web::{App, HttpServer};
use nervio_limiter::{limiter::{BucketConfig, Limiter}, middleware::actix_web::ActixWebLimiterMiddleware, storage::StorageType};
use tokio::sync::Mutex;pub fn init_server() {
let limiter = Limiter::new(
StorageType::InMemory, // Storage Type (InMemory, Redis or RedisAndMemoryMix)
None, // MultiplexedConnection Redis Connection
Some(200), // InMemory Max Allowed (In MB)
None, // Redis Memory Max Allowed (In MB)
Duration::from_secs(10), // This option only works with RedisAndMemoryMix Storage Type, and means the time that a InMemory item can be in memory without accesing to it, before deleting it but keeps in Redis Memory, its like a cache for the cache
);let limiter_middleware = ActixWebLimiterMiddleware {
limiter: Arc::new(Mutex::new(limiter)), // Allow use the same limiter for multiple stuff, so just pass the pointer as mutex to
middleware_bucket_config: BucketConfig {
name: "limiter".to_owned(), // Bucker Name, used in keys, treat it like a id, recommended to keep it unique, or use it across multiples midlewares or routes the same rate limit
limit_by: nervio_limiter::limiter::LimitEntityType::Global, // Select the method to limit requests, global means a global limit for everyone, also exists others like IP, only Global and IP can be used for now for this middleware
max_requests_per_cycle: 1, // Max Request For Cycle
cycle_duration: Duration::from_secs(10), // Each cycle duration, when a cycle ends, the requests are refilled
},
};let server = HttpServer::new(move || {
App::new()
.wrap(limiter_middleware.clone()) // Clone our LimiterMiddleware
});
}
```## Or do you want a more granular rate limiter? For example per route
```rust
use std::time::Duration;use nervio_limiter::{errors::LimiterError, limiter::{self, BucketConfig}};
use actix_web::{HttpMessage, HttpRequest, Responder, Result};pub async fn controller(
req: HttpRequest,
state: MyExampleState,
) -> Result {
// In real cases for example me i would put the limiter in state, then we need to lock, and then we can use the limiter
let limiter_headers = state.limiter.lock().await.limit_this("_".to_owned(), BucketConfig {
name: "my_controller".to_string(), // This is the name of the limiter, use the same name to used across multiples routes the same limiter
limit_by: limiter::LimitEntityType::Global, // This is the type of the limiter, in this case is global
max_requests_per_cycle: 120, // This is the max requests per cycle
cycle_duration: Duration::from_secs(60), // This is the cycle duration
}).await.map_err(|err| {
let too_much_requests_res = actix_web::error::ErrorTooManyRequests("Too many requests".to_string());
match err {
LimiterError::Limited=> too_much_requests_res,
LimiterError::MemoryLimitExceeded => too_much_requests_res,
LimiterError::RedisMemoryExceeded => too_much_requests_res,
LimiterError::BothMemoryAndRedisMemoryExceeded => too_much_requests_res,
_ => actix_web::error::ErrorInternalServerError(err) // Other error not related to limits
}
})?;// Your other logic, you can optionally insert headers to the res
...
}
```## Contributions
Feel free to contribute to this project :)
## Community
(there aren't really a community, its just me) [https://discord.gg/xtBskESBnY](https://discord.gg/xtBskESBnY)