salvo-timeout

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Salvo 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
/slow
, the request will timeout after 5 seconds and return a 408 Request Timeout response.
rust
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;
}
访问
/slow
时,请求会在5秒后超时,并返回408 Request Timeout响应。

Route-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 TypeRecommended Timeout
Health checks1-2 seconds
Simple API calls5-10 seconds
Database queries10-30 seconds
File uploads60-300 seconds
Report generation120-600 seconds
Real-time endpointsConsider 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

最佳实践

  1. Set appropriate timeouts: Match timeout to expected operation duration
  2. Use shorter timeouts for public APIs: Prevent abuse and resource exhaustion
  3. Longer timeouts for internal services: Trusted services may need more time
  4. Log timeouts: Track which requests are timing out for debugging
  5. Consider client expectations: Some clients may have their own timeouts
  6. Combine with rate limiting: Protect against slowloris attacks
  7. Graceful degradation: Return meaningful error messages on timeout
  8. No timeout for WebSocket: Long-lived connections shouldn't have request timeouts
  1. 设置合适的超时时间:超时时间应匹配操作的预期持续时间
  2. 公共API使用更短超时:防止滥用和资源耗尽
  3. 内部服务使用更长超时:可信服务可能需要更多处理时间
  4. 记录超时事件:跟踪哪些请求超时以便调试
  5. 考虑客户端预期:部分客户端自身可能设置了超时
  6. 与速率限制结合使用:抵御Slowloris攻击
  7. 优雅降级:超时返回有意义的错误信息
  8. WebSocket不设置超时:长连接不应有请求超时