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"
|
actix-web = "4"
|
||||||
static_assertions = "1"
|
static_assertions = "1"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
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.
|
/// Consumes one rate limit unit, returning the status.
|
||||||
pub async fn count(&self, key: impl Into<String>) -> Result<Status, Error> {
|
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);
|
let status = Status::new(count, self.limit, reset);
|
||||||
|
|
||||||
if count > self.limit {
|
if dbg!(count) > dbg!(self.limit) {
|
||||||
Err(Error::LimitExceeded(status))
|
Err(Error::LimitExceeded(status))
|
||||||
} else {
|
} else {
|
||||||
Ok(status)
|
Ok(status)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user