一键导入
salvo-rate-limiter
Implement rate limiting to protect APIs from abuse. Use for preventing DDoS attacks and ensuring fair resource usage.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Implement rate limiting to protect APIs from abuse. Use for preventing DDoS attacks and ensuring fair resource usage.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management.
Create basic Salvo web applications with handlers, routers, and server setup. Use when starting a new Salvo project or adding basic HTTP endpoints.
Implement caching strategies for improved performance. Use for reducing database load and speeding up responses.
Compress HTTP responses using gzip, brotli, zstd, or deflate. Use for reducing bandwidth and improving load times.
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
Configure Cross-Origin Resource Sharing (CORS) and security headers. Use for APIs accessed from browsers on different domains.
| name | salvo-rate-limiter |
| description | Implement rate limiting to protect APIs from abuse. Use for preventing DDoS attacks and ensuring fair resource usage. |
| version | 0.94.0 |
| tags | ["security","rate-limiting","throttling"] |
[dependencies]
salvo = { version = "0.94.0", features = ["rate-limiter"] }
A RateLimiter combines four pieces:
| Component | Purpose | Built-ins |
|---|---|---|
RateIssuer | Identify client | RemoteIpIssuer, TrustedProxyIssuer |
RateGuard | Limiting algorithm | FixedGuard (needs BasicQuota), SlidingGuard (needs CelledQuota) |
RateStore | Persist state | MokaStore |
QuotaGetter | Lookup quota for key | any Clone Quota, or custom impl |
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[handler]
async fn api() -> &'static str { "ok" }
#[tokio::main]
async fn main() {
let limiter = RateLimiter::new(
FixedGuard::default(),
MokaStore::default(),
RemoteIpIssuer,
BasicQuota::per_second(10),
);
let router = Router::new().hoop(limiter).push(Router::with_path("api").get(api));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
BasicQuota::per_second(10)
BasicQuota::per_minute(100)
BasicQuota::per_hour(1000)
BasicQuota::set_seconds(50, 30) // 50 per 30s
BasicQuota::set_minutes(500, 5)
BasicQuota::set_hours(5000, 2)
// Raw: BasicQuota::new(limit, time::Duration::seconds(n)) -- NOTE: time::Duration, not std::time
SlidingGuard requires CelledQuota (limit split into N cells). Passing BasicQuota will not compile.
use salvo::rate_limiter::{CelledQuota, SlidingGuard, MokaStore, RateLimiter, RemoteIpIssuer};
let limiter = RateLimiter::new(
SlidingGuard::default(),
MokaStore::default(),
RemoteIpIssuer,
CelledQuota::per_minute(60, 6), // 60 req/min split into 6 x 10s cells
);
CelledQuota has the same per_/set_ constructors as BasicQuota but each takes an extra cells: usize parameter.
RemoteIpIssuer uses the direct connection IP, which is usually the proxy's address. Use TrustedProxyIssuer to read X-Forwarded-For / X-Real-IP only when the direct peer is one of your configured proxies:
use std::net::IpAddr;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, TrustedProxyIssuer};
let limiter = RateLimiter::new(
FixedGuard::default(),
MokaStore::default(),
TrustedProxyIssuer::new([
"10.0.0.5".parse::<IpAddr>().unwrap(),
"10.0.0.6".parse::<IpAddr>().unwrap(),
]),
BasicQuota::per_minute(100),
);
Do not use the deprecated ForwardedHeaderIssuer for public services; it trusts client-controlled forwarded headers even when the request reaches Salvo directly.
RateIssuer::issue takes (&mut Request, &Depot):
use salvo::prelude::*;
use salvo::rate_limiter::RateIssuer;
struct UserIdIssuer;
impl RateIssuer for UserIdIssuer {
type Key = String;
async fn issue(&self, _req: &mut Request, depot: &Depot) -> Option<Self::Key> {
depot.get::<String>("user_id").ok().cloned()
}
}
struct ApiKeyIssuer;
impl RateIssuer for ApiKeyIssuer {
type Key = String;
async fn issue(&self, req: &mut Request, _depot: &Depot) -> Option<Self::Key> {
req.header::<String>("x-api-key")
}
}
Hybrid (user-if-authed, IP otherwise):
struct SmartIssuer;
impl RateIssuer for SmartIssuer {
type Key = String;
async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
if let Ok(id) = depot.get::<String>("user_id") {
Some(format!("user:{id}"))
} else {
Some(format!("ip:{}", req.remote_addr().ip()?))
}
}
}
A closure Fn(&mut Request, &Depot) -> Option<K> also implements RateIssuer directly.
GOTCHA: QuotaGetter::get takes only &Q — no Depot. Look up quota by key alone (e.g. from a static map or DB).
use std::borrow::Borrow;
use std::hash::Hash;
use salvo::Error;
use salvo::rate_limiter::{BasicQuota, QuotaGetter};
struct TieredQuota;
impl QuotaGetter<String> for TieredQuota {
type Quota = BasicQuota;
type Error = Error;
async fn get<Q>(&self, key: &Q) -> Result<Self::Quota, Self::Error>
where
String: Borrow<Q>,
Q: Hash + Eq + Sync,
{
// Lookup tier by key (from DB, cache, etc.)
Ok(BasicQuota::per_minute(100))
}
}
Any Clone + Send + Sync quota type auto-implements QuotaGetter returning itself, which is why BasicQuota::per_second(10) works as the fourth argument directly.
Enable built-in X-RateLimit-Limit / -Remaining / -Reset headers — no manual middleware needed:
let limiter = RateLimiter::new(FixedGuard::default(), MokaStore::default(),
RemoteIpIssuer, BasicQuota::per_minute(100))
.add_headers(true);
When the limit is exceeded, Salvo returns 429 Too Many Requests.
let login_limiter = RateLimiter::new(FixedGuard::default(), MokaStore::default(),
RemoteIpIssuer, BasicQuota::per_minute(5));
let api_limiter = RateLimiter::new(FixedGuard::default(), MokaStore::default(),
RemoteIpIssuer, BasicQuota::per_minute(100));
let router = Router::new()
.push(Router::with_path("login").hoop(login_limiter).post(login))
.push(Router::with_path("api").hoop(api_limiter).get(api));
Skip rate limiting for some requests:
let limiter = RateLimiter::new(/* ... */)
.with_skipper(|req: &mut Request, _: &Depot| {
req.uri().path().starts_with("/health")
});