在 Echo/Go 上实现特定路由超时的最佳方法

Best way to implement timeout for specific route on Echo/Go

想知道在Echo库中为特定路由设置超时的最佳方法

我用context.WithTimeout实现了超时,但是用了几个函数来封装上下文,我认为这是错误的。

ctx, cancel := context.WithTimeout(ctx, 30*time.Second)

是否有任何中间件或更好的方法来实现它?

为服务器添加超时 context.Context

在不知道您要做什么的情况下回答这个问题有点棘手。我将首先回答如何使用 WithTimeout 和中间件来处理上下文包装。

一个中间件可以add/modify一个像这样的请求上下文:

func TimeoutMiddleware(timeout time.Duration, next func(w http.ResponseWriter, req *http.Request)) func(w http.ResponseWriter, req *http.Request) {
    return func(w http.ResponseWriter, req *http.Request) {
        // Wrap the existing context from the request
        ctx, cancel := context.WithTimeout(req.Context(), timeout)
        // Always do this to clean up contexts, otherwise they'll hang out
        // and gather since they are blocked go rountines
        defer cancel()

        // Put the new context into the request
        req = req.WithContext(ctx)

        // Pass the modified request forward
        next(w, req)

        // Handle any ctx timeout issues in the general middleware
        if err := ctx.Err(); err != nil {
            if errors.Is(err, context.DeadlineExceeded) {
                log.Println("HTTP Request timed out")
                w.Write([]byte("Timed out"))
            }
        }
    }
}

问题是 next(w, req) 需要您的 http 处理程序来处理上下文超时。如果 http 处理程序忽略上下文,则不会超时。比如这样:

func endless(w http.ResponseWriter, req *http.Request) {
    ctx := req.Context()
    // Just a busy loop....
    for {
        select {
        case <-ctx.Done():
            // Exit the handler if the context is Done(), even if our function is not.
            return
        default:
            fmt.Println("wait")
            time.Sleep(1 * time.Second)
        }
    }
}

如果您正在进行数据库调用或在处理程序中花费时间的事情,通常数据库 api 会接受 context.Context 以在上下文被取消时提前中止。

因此,此解决方案为进入您仍然需要管理的处理程序的请求上下文添加了超时。


客户端超时

您可以随时将超时添加到您的请求中:

    client := http.Client{
        Timeout: time.Second,
    }
    client.Do(...)