mirror of
https://github.com/actix/actix-extras.git
synced 2025-02-22 18:33:18 +01:00
83 lines
3.1 KiB
Rust
83 lines
3.1 KiB
Rust
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
|
|
}
|