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:
parent
bedc1353c8
commit
438d10a3f9
@ -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"
|
39
actix-limitation/examples/README.md
Normal file
39
actix-limitation/examples/README.md
Normal 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
|
82
actix-limitation/examples/scoped_limiters.rs
Normal file
82
actix-limitation/examples/scoped_limiters.rs
Normal 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
|
||||
}
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user