salvo-timeout
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSalvo Request Timeout
Salvo 请求超时
This skill helps configure request timeouts in Salvo applications to prevent slow requests from consuming server resources indefinitely.
本技能可帮助在Salvo应用中配置请求超时,防止慢请求无限期占用服务器资源。
Why Use Timeouts?
为什么要使用超时?
- Prevent resource exhaustion: Long-running requests can consume connections and memory
- Improve reliability: Fail fast rather than hang indefinitely
- Better user experience: Users get quick feedback instead of waiting forever
- Protection against attacks: Slowloris and similar attacks are mitigated
- 防止资源耗尽:长时间运行的请求会占用连接和内存
- 提升可靠性:快速失败而非无限期挂起
- 优化用户体验:用户能快速得到反馈,无需永久等待
- 抵御攻击:缓解Slowloris及类似攻击的影响
Setup
配置步骤
Timeout is built into Salvo core:
toml
[dependencies]
salvo = "0.89.0"超时功能已内置在Salvo核心中:
toml
[dependencies]
salvo = "0.89.0"Basic Timeout
基础超时配置
rust
use std::time::Duration;
use salvo::prelude::*;
#[handler]
async fn fast_handler() -> &'static str {
"Hello, World!"
}
#[handler]
async fn slow_handler() -> &'static str {
// Simulates a slow operation
tokio::time::sleep(Duration::from_secs(10)).await;
"This takes a while..."
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new()
// Apply 5-second timeout to all routes
.hoop(Timeout::new(Duration::from_secs(5)))
.push(Router::with_path("fast").get(fast_handler))
.push(Router::with_path("slow").get(slow_handler));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}When accessing , the request will timeout after 5 seconds and return a 408 Request Timeout response.
/slowrust
use std::time::Duration;
use salvo::prelude::*;
#[handler]
async fn fast_handler() -> &'static str {
"Hello, World!"
}
#[handler]
async fn slow_handler() -> &'static str {
// 模拟慢操作
tokio::time::sleep(Duration::from_secs(10)).await;
"This takes a while..."
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new()
// 为所有路由应用5秒超时
.hoop(Timeout::new(Duration::from_secs(5)))
.push(Router::with_path("fast").get(fast_handler))
.push(Router::with_path("slow").get(slow_handler));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}访问时,请求会在5秒后超时,并返回408 Request Timeout响应。
/slowRoute-Specific Timeouts
路由专属超时配置
Apply different timeouts to different routes:
rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
let router = Router::new()
.push(
Router::with_path("quick")
.hoop(Timeout::new(Duration::from_secs(2)))
.get(quick_handler)
)
.push(
Router::with_path("standard")
.hoop(Timeout::new(Duration::from_secs(30)))
.get(standard_handler)
)
.push(
Router::with_path("long-running")
.hoop(Timeout::new(Duration::from_secs(300)))
.get(long_running_handler)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}为不同路由设置不同的超时时间:
rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
let router = Router::new()
.push(
Router::with_path("quick")
.hoop(Timeout::new(Duration::from_secs(2)))
.get(quick_handler)
)
.push(
Router::with_path("standard")
.hoop(Timeout::new(Duration::from_secs(30)))
.get(standard_handler)
)
.push(
Router::with_path("long-running")
.hoop(Timeout::new(Duration::from_secs(300)))
.get(long_running_handler)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Timeout with API Routes
API路由超时配置
rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Short timeout for API endpoints
let api_timeout = Timeout::new(Duration::from_secs(10));
// Longer timeout for file uploads
let upload_timeout = Timeout::new(Duration::from_secs(120));
let router = Router::new()
.push(
Router::with_path("api")
.hoop(api_timeout)
.push(Router::with_path("users").get(list_users))
.push(Router::with_path("products").get(list_products))
)
.push(
Router::with_path("upload")
.hoop(upload_timeout)
.post(handle_upload)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// API端点设置短超时
let api_timeout = Timeout::new(Duration::from_secs(10));
// 文件上传设置较长超时
let upload_timeout = Timeout::new(Duration::from_secs(120));
let router = Router::new()
.push(
Router::with_path("api")
.hoop(api_timeout)
.push(Router::with_path("users").get(list_users))
.push(Router::with_path("products").get(list_products))
)
.push(
Router::with_path("upload")
.hoop(upload_timeout)
.post(handle_upload)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Global Timeout with Exceptions
全局超时与例外配置
rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Default 30-second timeout
let default_timeout = Timeout::new(Duration::from_secs(30));
let router = Router::new()
.hoop(default_timeout) // Apply to all routes
.push(Router::with_path("health").get(health_check))
.push(Router::with_path("api/{**rest}").get(api_handler))
.push(
// Override with longer timeout for specific route
Router::with_path("reports/generate")
.hoop(Timeout::new(Duration::from_secs(300)))
.post(generate_report)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// 默认30秒超时
let default_timeout = Timeout::new(Duration::from_secs(30));
let router = Router::new()
.hoop(default_timeout) // 应用于所有路由
.push(Router::with_path("health").get(health_check))
.push(Router::with_path("api/{**rest}").get(api_handler))
.push(
// 为特定路由覆盖为更长超时
Router::with_path("reports/generate")
.hoop(Timeout::new(Duration::from_secs(300)))
.post(generate_report)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Timeout with Custom Error Response
自定义错误响应的超时配置
Handle timeout errors with a custom catcher:
rust
use std::time::Duration;
use salvo::prelude::*;
use salvo::catcher::Catcher;
#[handler]
async fn handle_timeout(res: &mut Response, ctrl: &mut FlowCtrl) {
if res.status_code() == Some(StatusCode::REQUEST_TIMEOUT) {
res.render(Json(serde_json::json!({
"error": "Request Timeout",
"message": "The request took too long to process",
"code": 408
})));
ctrl.skip_rest();
}
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(Timeout::new(Duration::from_secs(5)))
.get(slow_handler);
let service = Service::new(router).catcher(
Catcher::default().hoop(handle_timeout)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(service).await;
}使用自定义捕获器处理超时错误:
rust
use std::time::Duration;
use salvo::prelude::*;
use salvo::catcher::Catcher;
#[handler]
async fn handle_timeout(res: &mut Response, ctrl: &mut FlowCtrl) {
if res.status_code() == Some(StatusCode::REQUEST_TIMEOUT) {
res.render(Json(serde_json::json!({
"error": "Request Timeout",
"message": "The request took too long to process",
"code": 408
})));
ctrl.skip_rest();
}
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(Timeout::new(Duration::from_secs(5)))
.get(slow_handler);
let service = Service::new(router).catcher(
Catcher::default().hoop(handle_timeout)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(service).await;
}Combining Timeout with Other Middleware
超时与其他中间件结合使用
rust
use std::time::Duration;
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[tokio::main]
async fn main() {
// Rate limiter
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(10),
);
// Timeout
let timeout = Timeout::new(Duration::from_secs(30));
let router = Router::new()
.hoop(limiter) // Rate limit first
.hoop(timeout) // Then apply timeout
.push(Router::with_path("api/{**rest}").get(api_handler));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}rust
use std::time::Duration;
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[tokio::main]
async fn main() {
// 速率限制器
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(10),
);
// 超时配置
let timeout = Timeout::new(Duration::from_secs(30));
let router = Router::new()
.hoop(limiter) // 先应用速率限制
.hoop(timeout) // 再应用超时
.push(Router::with_path("api/{**rest}").get(api_handler));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Practical Timeout Values
实用超时值参考
| Endpoint Type | Recommended Timeout |
|---|---|
| Health checks | 1-2 seconds |
| Simple API calls | 5-10 seconds |
| Database queries | 10-30 seconds |
| File uploads | 60-300 seconds |
| Report generation | 120-600 seconds |
| Real-time endpoints | Consider no timeout |
| 端点类型 | 推荐超时时间 |
|---|---|
| 健康检查 | 1-2秒 |
| 简单API调用 | 5-10秒 |
| 数据库查询 | 10-30秒 |
| 文件上传 | 60-300秒 |
| 报表生成 | 120-600秒 |
| 实时端点 | 建议不设置超时 |
Example: Microservices Gateway
示例:微服务网关
rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
let router = Router::new()
// Auth service - should be fast
.push(
Router::with_path("auth/{**rest}")
.hoop(Timeout::new(Duration::from_secs(5)))
.goal(auth_proxy)
)
// User service - moderate
.push(
Router::with_path("users/{**rest}")
.hoop(Timeout::new(Duration::from_secs(15)))
.goal(users_proxy)
)
// Analytics service - can be slow
.push(
Router::with_path("analytics/{**rest}")
.hoop(Timeout::new(Duration::from_secs(60)))
.goal(analytics_proxy)
)
// Report service - long running
.push(
Router::with_path("reports/{**rest}")
.hoop(Timeout::new(Duration::from_secs(300)))
.goal(reports_proxy)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}rust
use std::time::Duration;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
let router = Router::new()
// 认证服务 - 响应应快速
.push(
Router::with_path("auth/{**rest}")
.hoop(Timeout::new(Duration::from_secs(5)))
.goal(auth_proxy)
)
// 用户服务 - 中等时长
.push(
Router::with_path("users/{**rest}")
.hoop(Timeout::new(Duration::from_secs(15)))
.goal(users_proxy)
)
// 分析服务 - 可允许较慢响应
.push(
Router::with_path("analytics/{**rest}")
.hoop(Timeout::new(Duration::from_secs(60)))
.goal(analytics_proxy)
)
// 报表服务 - 长时间运行
.push(
Router::with_path("reports/{**rest}")
.hoop(Timeout::new(Duration::from_secs(300)))
.goal(reports_proxy)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Best Practices
最佳实践
- Set appropriate timeouts: Match timeout to expected operation duration
- Use shorter timeouts for public APIs: Prevent abuse and resource exhaustion
- Longer timeouts for internal services: Trusted services may need more time
- Log timeouts: Track which requests are timing out for debugging
- Consider client expectations: Some clients may have their own timeouts
- Combine with rate limiting: Protect against slowloris attacks
- Graceful degradation: Return meaningful error messages on timeout
- No timeout for WebSocket: Long-lived connections shouldn't have request timeouts
- 设置合适的超时时间:超时时间应匹配操作的预期持续时间
- 公共API使用更短超时:防止滥用和资源耗尽
- 内部服务使用更长超时:可信服务可能需要更多处理时间
- 记录超时事件:跟踪哪些请求超时以便调试
- 考虑客户端预期:部分客户端自身可能设置了超时
- 与速率限制结合使用:抵御Slowloris攻击
- 优雅降级:超时返回有意义的错误信息
- WebSocket不设置超时:长连接不应有请求超时