Controle de Tráfego

Middleware que fornece funcionalidade de controle de tráfego.

Funcionalidades Principais

  • RateIssuer fornece uma abstração para identificar chaves de visitantes atribuídas. RemoteIpIssuer é uma de suas implementações, capaz de determinar visitantes com base no endereço IP da requisição. A chave não precisa ser necessariamente do tipo string - qualquer tipo que satisfaça as restrições Hash + Eq + Send + Sync + 'static pode ser usado como chave.

  • RateGuard oferece uma abstração para algoritmos de controle de tráfego. Implementações padrão incluem janela fixa (FixedGuard) e janela deslizante (SlidingGuard).

  • RateStore provê operações de armazenamento e recuperação de dados. MokaStore é uma implementação embutida de cache em memória baseada em moka. Você também pode definir sua própria implementação.

  • RateLimiter é uma estrutura que implementa Handler, contendo internamente um campo skipper para especificar requisições que podem ser ignoradas pelo cache. Por padrão, utiliza none_skipper que não ignora nenhuma requisição.

  • QuotaGetter fornece uma abstração para obtenção de cotas, permitindo adquirir um objeto de cota baseado na Key do visitante. Isso significa que podemos configurar informações como cotas de usuário em bancos de dados, permitindo alterações e recuperações dinâmicas.

Código de Exemplo

Exemplo de Cota Estática

main.rs
Cargo.toml
rate-limiter-static/src/main.rs
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("0.0.0.0:5800").bind().await;
    Server::new(acceptor).serve(router).await;
}

Exemplo de Cota Dinâmica

main.rs
Cargo.toml
rate-limiter-dynamic/src/main.rs
use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::LazyLock;

use salvo::Error;
use salvo::prelude::*;
use salvo::rate_limiter::{
    CelledQuota, MokaStore, QuotaGetter, RateIssuer, RateLimiter, SlidingGuard,
};

static USER_QUOTAS: LazyLock<HashMap<String, CelledQuota>> = LazyLock::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("0.0.0.0: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>
"#;