一键导入
salvo-concurrency-limiter
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
用 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.
Configure Cross-Origin Resource Sharing (CORS) and security headers. Use for APIs accessed from browsers on different domains.
Implement CSRF (Cross-Site Request Forgery) protection using cookie or session storage. Use for protecting forms and state-changing endpoints.
| name | salvo-concurrency-limiter |
| description | Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion. |
| version | 0.94.0 |
| tags | ["performance","concurrency","limiter"] |
max_concurrency(n) from salvo::concurrency_limiter is a hoop that wraps a tokio::sync::Semaphore. It try_acquires on each request — on failure it short-circuits with 429 Too Many Requests. Unlike rate limiters it caps parallel requests, not request frequency.
Lives in salvo-extra; enable the feature:
[dependencies]
salvo = { version = "0.94.0", features = ["concurrency-limiter"] }
Import path: salvo::concurrency_limiter::{max_concurrency, MaxConcurrency}.
use salvo::prelude::*;
use salvo::concurrency_limiter::max_concurrency;
#[handler]
async fn upload(res: &mut Response) {
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
res.render("Upload complete");
}
#[tokio::main]
async fn main() {
let router = Router::new().push(
Router::with_path("upload")
.hoop(max_concurrency(1)) // at most 1 in flight
.post(upload),
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Each hoop(max_concurrency(n)) call creates its own semaphore scoped to that route subtree. Reuse across subtrees by hoisting the hoop on a shared parent.
let router = Router::new()
.push(Router::with_path("upload")
.hoop(max_concurrency(2))
.post(upload_handler))
.push(Router::with_path("reports/generate")
.hoop(max_concurrency(1))
.post(generate_report))
.push(Router::with_path("api/{**rest}")
.hoop(max_concurrency(100))
.goal(api_handler))
.push(Router::with_path("health").get(health_check)); // no limit
hoop order matters — outer hoops run first. Put rate-limit/timeout outside the concurrency cap so rejected requests don't hold permits:
Router::with_path("api/{**rest}")
.hoop(rate_limiter) // runs first
.hoop(Timeout::new(Duration::from_secs(30)))
.hoop(max_concurrency(50)) // permit acquired last, released on handler exit
.goal(api_handler)
The middleware renders StatusError::too_many_requests().brief("Max concurrency reached."). Override in a catcher:
use salvo::catcher::Catcher;
#[handler]
async fn on_overload(res: &mut Response, ctrl: &mut FlowCtrl) {
if res.status_code() == Some(StatusCode::TOO_MANY_REQUESTS) {
res.render(Json(serde_json::json!({
"error": "busy",
"retry_after": 5,
})));
ctrl.skip_rest();
}
}
let service = Service::new(router).catcher(Catcher::default().hoop(on_overload));
| Workload | Limit |
|---|---|
| File upload | 1–5 |
| Image/video transcode | ~num_cpus::get() |
| Heavy DB queries | DB pool size |
| External API callouts | Vendor's concurrency |
| General API | 50–200 |
hoop(max_concurrency(..)) is a separate semaphore; instantiating it inside a loop or per-request builder creates independent limits and defeats the cap.Timeout so stuck handlers eventually free the slot.try_acquire: requests are rejected, not queued. If you want queueing, put a tower::limit layer in via tower-compat instead.