Limitador de velocidad
Middleware que proporciona funcionalidad de control de flujo.
Características Principales
RateIssuer
proporciona una abstracción del valor clave asignado para identificar la identidad del visitante.RemoteIpIssuer
es una implementación del mismo que puede determinar el visitante en función de la dirección IP solicitada. Los tipos de restricción Eq + Send + Sync + 'static` pueden ser utilizados como llaves.RateGuard
proporciona una abstracción para el algoritmo de control de flujo. De forma predeterminada, se implementan dos implementaciones de ventana fija (FixedGuard
) y ventana deslizante (SlidingGuard
).RateStore
proporciona acceso a los datos.MokaStore
es una implementación de memoria caché integrada basada enmoka
. También puedes definir tu propia implementación.RateLimiter
es una estructura que implementaHandler
, y también hay un camposkipper
dentro, que se puede especificar para omitir ciertas solicitudes que no requieren almacenamiento en caché. De forma predeterminada,none_skipper
se usará para no omitir cualquier solicitud.QuotaGetter
proporciona la abstracción de adquisición de cuota, que puede obtener un objeto de cuota de acuerdo con laClave
del visitante, lo que significa que podemos configurar la cuota de usuario y otra información en la base de datos, cambiarla dinámicamente y adquirirla dinámicamente.La posibilidad de agregar encabezados de límite de velocidad a la respuesta con la función de instancia
RateLimiter::add_headers
agregará los siguientes encabezados a la respuesta:Header Description X-RateLimit-Limit
El número máximo de solicitudes que el consumidor puede realizar por período de cuota. X-RateLimit-Remaining
El número de solicitudes que quedan en la ventana de límite de tasa actual. X-RateLimit-Reset
La hora a la que se restablece la ventana de límite de velocidad actual en segundos de la época UTC.
Ejemplo
Uso de referencia estática
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(1),
);
let router = Router::with_hoop(limiter).get(hello);
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
[package]
name = "example-rate-limiter-static"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
salvo = { workspace = true, features = ["rate-limiter"]}
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
Uso de referencia dinámica
use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
use once_cell::sync::Lazy;
use salvo::prelude::*;
use salvo::rate_limiter::{
CelledQuota, MokaStore, QuotaGetter, RateIssuer, RateLimiter, SlidingGuard,
};
use salvo::Error;
static USER_QUOTAS: Lazy<HashMap<String, CelledQuota>> = Lazy::new(|| {
let mut map = HashMap::new();
map.insert("user1".into(), CelledQuota::per_second(1, 1));
map.insert("user2".into(), CelledQuota::set_seconds(1, 1, 5));
map.insert("user3".into(), CelledQuota::set_seconds(1, 1, 10));
map
});
struct UserIssuer;
impl RateIssuer for UserIssuer {
type Key = String;
async fn issue(&self, req: &mut Request, _depot: &Depot) -> Option<Self::Key> {
req.query::<Self::Key>("user")
}
}
struct CustomQuotaGetter;
impl QuotaGetter<String> for CustomQuotaGetter {
type Quota = CelledQuota;
type Error = Error;
async fn get<Q>(&self, key: &Q) -> Result<Self::Quota, Self::Error>
where
String: Borrow<Q>,
Q: Hash + Eq + Sync,
{
USER_QUOTAS
.get(key)
.cloned()
.ok_or_else(|| Error::other("user not found"))
}
}
#[handler]
async fn limited() -> &'static str {
"Limited page"
}
#[handler]
async fn home() -> Text<&'static str> {
Text::Html(HOME_HTML)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let limiter = RateLimiter::new(
SlidingGuard::new(),
MokaStore::new(),
UserIssuer,
CustomQuotaGetter,
);
let router = Router::new()
.get(home)
.push(Router::with_path("limited").hoop(limiter).get(limited));
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static HOME_HTML: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>Rate Limiter Dynmaic</title>
</head>
<body>
<h2>Rate Limiter Dynamic</h2>
<p>
This example shows how to set limit for different users.
</p>
<p>
<a href="/limited?user=user1" target="_blank">Limited page for user1: 1/second</a>
</p>
<p>
<a href="/limited?user=user2" target="_blank">Limited page for user2: 1/5seconds</a>
</p>
<p>
<a href="/limited?user=user3" target="_blank">Limited page for user3: 1/10seconds</a>
</p>
</body>
</html>
"#;
[package]
name = "example-rate-limiter-dynamic"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
salvo = { workspace = true, features = ["rate-limiter"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
once_cell = "1"