1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-02-22 18:33:18 +01:00

doc: example for scoped limiters

This commit is contained in:
Raphael C 2023-11-09 23:29:38 +01:00
parent bedc1353c8
commit 438d10a3f9
4 changed files with 124 additions and 2 deletions

View File

@ -38,3 +38,4 @@ actix-session = { version = "0.8", optional = true }
actix-web = "4"
static_assertions = "1"
uuid = { version = "1", features = ["v4"] }
pretty_env_logger = "0.5"

View File

@ -0,0 +1,39 @@
# Examples
We leverage redis to store state of the ratelimiting.
So you will need to have a redis instance available on localhost.
You can start this redis instance with Docker:
```
docker run -d -p 6379:6379 --name limiter-redis redis
# Clean up: you can rm the docker this way
# docker rm -f limiter-redis
```
## scoped_limiters
This example present how to use multiple limiters.
This allow different configurations and the ability to scope them.
### Starting the example server
```bash
RUST_LOG=debug cargo run --example scoped_limiters
```
> RUST_LOG=debug is used to print logs, see crate pretty_env_logger for more details.
### Testing with curl
```bash
curl -X PUT localhost:8080/scoped/sms -v
```
first request should work fine
doing a second request within 60 seconds should yield `HTTP/1.1 429 Too Many Requests`
after 60 seconds you should be able to make 1 request again
```bash
curl localhost:8080
```
This route should work 30 times, or 29 if you previously requested the /scoped/sms route

View File

@ -0,0 +1,82 @@
use std::{collections::HashMap, time::Duration};
use actix_limitation::{Limiter, RateLimiter};
use actix_web::{dev::ServiceRequest, get, put, web, App, HttpServer, Responder};
use redis::Client;
#[get("/")]
async fn index() -> impl Responder {
"index"
}
#[put("/sms")]
async fn send_sms() -> impl Responder {
"sending an expensive sms"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
pretty_env_logger::init();
// Create an Hashmap to store the multiples [Limiter](Limiter)
let mut limiters = HashMap::new();
// Create and connect a redis Client.
let redis_client = Client::open("redis://127.0.0.1/").expect("creation of the redis client");
// Create a default limiter
let default_limiter = Limiter::builder_with_redis_client(redis_client.clone())
// specifying with key_by that we take the user IP address as a identifier.
.key_by(|req: &ServiceRequest| {
req.connection_info()
.realip_remote_addr()
.map(|ip| ip.to_string())
})
// Allowing a maximum of 30 requests per minute
.limit(30)
.period(Duration::from_secs(60))
.build()
.unwrap();
limiters.insert("default", default_limiter);
let scope_limiter = Limiter::builder_with_redis_client(redis_client)
.key_by(|req: &ServiceRequest| {
req.connection_info()
.realip_remote_addr()
// ⚠️ we prepend "scoped" to the key in order to isolate this count from the default count
//
// If we were using the same key, a request to this route would always return too many requests
// in this context because the default limiter at the root would be reached first and would count 1 before we check for this.
// To mitigate this issue you could also specify a different namespace with the redis_client passed as parameter: `redis://127.0.0.1/2`
.map(|ip| format!("scoped-{}", ip))
})
// Allowing only 1 request per minute
.limit(1)
.period(Duration::from_secs(60))
.build()
.unwrap();
limiters.insert("scoped", scope_limiter);
// Passing this limiters as app_data so it can be accessed by the middleware.
let limiters = web::Data::new(limiters);
HttpServer::new(move || {
App::new()
// Using the default limiter for all the routes
// ⚠️ This limiter will count and apply the limits before the one in "/scoped"
.wrap(RateLimiter::scoped("default"))
.app_data(limiters.clone())
.service(
web::scope("/scoped")
// Wrapping only for this scope the scoped limiter
.wrap(RateLimiter::scoped("scoped"))
// This route will only be available 1 time every minutes
// Note: the root limiter default will also limit this route
.service(send_sms),
)
// This route is only limited by the default limiter
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

View File

@ -140,10 +140,10 @@ impl Limiter {
/// Consumes one rate limit unit, returning the status.
pub async fn count(&self, key: impl Into<String>) -> Result<Status, Error> {
let (count, reset) = self.track(key).await?;
let (count, reset) = dbg!(self.track(key).await?);
let status = Status::new(count, self.limit, reset);
if count > self.limit {
if dbg!(count) > dbg!(self.limit) {
Err(Error::LimitExceeded(status))
} else {
Ok(status)