Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add salvo-rs/salvo-skills --skill "salvo-rate-limiter"
Install specific skill from multi-skill repository
# Description
Implement rate limiting to protect APIs from abuse. Use for preventing DDoS attacks and ensuring fair resource usage.
# SKILL.md
name: salvo-rate-limiter
description: Implement rate limiting to protect APIs from abuse. Use for preventing DDoS attacks and ensuring fair resource usage.
Salvo Rate Limiting
This skill helps implement rate limiting in Salvo applications to protect against abuse.
Setup
[dependencies]
salvo = { version = "1.88.1", features = ["rate-limiter"] }
Basic Rate Limiting
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[handler]
async fn api_handler() -> &'static str {
"API response"
}
#[tokio::main]
async fn main() {
// 10 requests per second per IP
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(10),
);
let router = Router::new()
.hoop(limiter)
.push(Router::with_path("api").get(api_handler));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Quota Types
use salvo::rate_limiter::BasicQuota;
// Per second
BasicQuota::per_second(10) // 10 requests/second
// Per minute
BasicQuota::per_minute(100) // 100 requests/minute
// Per hour
BasicQuota::per_hour(1000) // 1000 requests/hour
// Custom duration
use std::time::Duration;
BasicQuota::new(50, Duration::from_secs(30)) // 50 requests per 30 seconds
Rate Limit by IP Address
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer, // Limit by client IP
BasicQuota::per_minute(60),
);
Rate Limit by User ID
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, QuotaGetter, RateIssuer};
struct UserIdIssuer;
impl RateIssuer for UserIdIssuer {
type Key = String;
async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
// Get user ID from depot (set by auth middleware)
depot.get::<String>("user_id").cloned()
}
}
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
UserIdIssuer,
BasicQuota::per_minute(100),
);
Rate Limit by API Key
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RateIssuer};
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")
}
}
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
ApiKeyIssuer,
BasicQuota::per_minute(1000),
);
Dynamic Rate Limits
Different limits for different users:
use salvo::prelude::*;
use salvo::rate_limiter::{FixedGuard, MokaStore, RateLimiter, RateIssuer, QuotaGetter, BasicQuota};
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").cloned()
}
}
struct TieredQuota;
impl QuotaGetter<String> for TieredQuota {
type Quota = BasicQuota;
type Error = salvo::Error;
async fn get(&self, key: &String, depot: &Depot) -> Result<Self::Quota, Self::Error> {
let user_tier = depot.get::<String>("user_tier")
.map(|s| s.as_str())
.unwrap_or("free");
let quota = match user_tier {
"premium" => BasicQuota::per_minute(1000),
"basic" => BasicQuota::per_minute(100),
_ => BasicQuota::per_minute(10), // Free tier
};
Ok(quota)
}
}
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
UserIdIssuer,
TieredQuota,
);
Sliding Window Rate Limiting
use salvo::rate_limiter::{BasicQuota, SlidingGuard, MokaStore, RateLimiter, RemoteIpIssuer};
// Sliding window for smoother rate limiting
let limiter = RateLimiter::new(
SlidingGuard::new(), // Use sliding window instead of fixed
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_minute(60),
);
Custom Rate Limit Response
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[handler]
async fn rate_limit_exceeded(res: &mut Response) {
res.status_code(StatusCode::TOO_MANY_REQUESTS);
res.render(Json(serde_json::json!({
"error": "Rate limit exceeded",
"message": "Too many requests. Please try again later.",
"retry_after": 60
})));
}
// In your router setup, handle 429 status
Rate Limiting Specific Routes
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Strict limit for login
let login_limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_minute(5),
);
// Relaxed limit for general API
let api_limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_minute(100),
);
let router = Router::new()
.push(
Router::with_path("login")
.hoop(login_limiter)
.post(login_handler)
)
.push(
Router::with_path("api")
.hoop(api_limiter)
.get(api_handler)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Combining with Authentication
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RateIssuer};
// Rate limit authenticated users by user ID, anonymous by IP
struct SmartIssuer;
impl RateIssuer for SmartIssuer {
type Key = String;
async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
// Try user ID first (from auth middleware)
if let Some(user_id) = depot.get::<String>("user_id") {
return Some(format!("user:{}", user_id));
}
// Fall back to IP address
req.remote_addr()
.map(|addr| format!("ip:{}", addr))
}
}
Rate Limit Headers
Add headers to inform clients of their rate limit status:
use salvo::prelude::*;
#[handler]
async fn add_rate_limit_headers(
req: &mut Request,
depot: &mut Depot,
res: &mut Response,
ctrl: &mut FlowCtrl
) {
ctrl.call_next(req, depot, res).await;
// Add rate limit headers
res.headers_mut().insert("X-RateLimit-Limit", "100".parse().unwrap());
res.headers_mut().insert("X-RateLimit-Remaining", "95".parse().unwrap());
res.headers_mut().insert("X-RateLimit-Reset", "1609459200".parse().unwrap());
}
Complete Example
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[handler]
async fn public_api() -> &'static str {
"Public API response"
}
#[handler]
async fn login() -> &'static str {
"Login successful"
}
#[tokio::main]
async fn main() {
// General API: 100 requests/minute
let api_limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_minute(100),
);
// Login: 5 attempts/minute (prevent brute force)
let login_limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_minute(5),
);
let router = Router::new()
.push(
Router::with_path("api")
.hoop(api_limiter)
.get(public_api)
)
.push(
Router::with_path("login")
.hoop(login_limiter)
.post(login)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Best Practices
- Choose appropriate limits: Base on expected usage patterns
- Use different limits for different endpoints: Stricter for auth, relaxed for reads
- Identify users correctly: IP for anonymous, user ID for authenticated
- Inform clients: Return rate limit headers and retry-after
- Log rate limit hits: Monitor for abuse patterns
- Consider sliding window: Smoother rate limiting than fixed window
- Handle gracefully: Return helpful error messages
- Test under load: Verify limits work correctly
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.