#流量控制
提供流量控制功能的中介軟體。
#主要功能
-
RateIssuer提供分配識別訪問者身份鍵值的抽象功能。RemoteIpIssuer是其實作之一,可依據請求的 IP 位址識別訪問者。鍵值不限於字串類型,任何符合Hash + Eq + Send + Sync + 'static約束的型別皆可作為鍵值。 -
RateGuard提供流量控制演算法的抽象功能。預設實作了固定視窗(FixedGuard)與滑動視窗(SlidingGuard)兩種演算法。 -
RateStore提供資料存取操作的抽象功能。MokaStore是基於moka的內建記憶體快取實作。您也可以自訂實作方式。 -
RateLimiter是實作Handler的結構體,其內部包含skipper欄位,可用於指定跳過不需限流的請求。預設使用none_skipper,即不跳過任何請求。 -
QuotaGetter提供配額取得機制的抽象功能,可依據訪問者的Key取得配額物件,這意味著我們能將使用者配額等資訊配置於資料庫中,實現動態調整與即時取得。
範例程式碼
#靜態配額範例
main.rs
Cargo.toml
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:8698").bind().await;
Server::new(acceptor).serve(router).await;
}
[package]
name = "example-rate-limiter-static"
version.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true
[dependencies]
salvo = { workspace = true, features = ["rate-limiter"] }
tokio = { workspace = true, features = ["macros"] }
tracing.workspace = true
tracing-subscriber.workspace = true
#動態配額範例
main.rs
Cargo.toml
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:8698").bind().await;
Server::new(acceptor).serve(router).await;
}
static HOME_HTML: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>Rate Limiter Dynamic</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.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true
[dependencies]
salvo = { workspace = true, features = ["rate-limiter"] }
tokio = { workspace = true, features = ["macros"] }
tracing.workspace = true
tracing-subscriber.workspace = true