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

问题描述

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

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

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

是否有任何中间件或更好的方法来实现这一目标?

解决方法

向服务器 context.Context 添加超时

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

中间件可以像这样添加/修改请求上下文:

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(...)