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 en moka. También puedes definir tu propia implementación.

  • RateLimiter es una estructura que implementa Handler, y también hay un campo skipper 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 la Clave 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_headersAbrir en una Nueva Ventana agregará los siguientes encabezados a la respuesta:

    HeaderDescription
    X-RateLimit-LimitEl número máximo de solicitudes que el consumidor puede realizar por período de cuota.
    X-RateLimit-RemainingEl número de solicitudes que quedan en la ventana de límite de tasa actual.
    X-RateLimit-ResetLa 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"