Go语言 在http服务器请求上实现上下文超时

lf5gs5x2  于 12个月前  发布在  Go
关注(0)|答案(2)|浏览(92)

如何在HTTP服务器请求上实现60秒的上下文超时?一种类型的处理程序已经有了应用了值的上下文
我不知道如何以最好的方式实现它
找到这个答案https://stackoverflow.com/a/43322359/555222
如果超时,如何停止http请求处理程序继续执行?

实现前的原始代码

srv := &http.Server{
    Addr:               fmt.Sprintf("%s:%d", h.host, h.port),
    Handler:            http.HandlerFunc(h.serve),
}
err := srv.ListenAndServe()

...

func (h *HTTP) serve(w http.ResponseWriter, r *http.Request) {
    var allow []string
    for _, route := range h.routes {
        if route.regex != nil {
            //  Regex path
            matches := route.regex.FindStringSubmatch(r.URL.Path)
            len     := len(matches)
            if len > 0 {
                if len == 1 {
                    route.handler(w, r)  // ADD CONTEXT TIMEOUT 60 SECS
                }else{
                    //  Context with value
                    ctx := context.WithValue(r.Context(), ctx_http, matches[1:])
                    route.handler(w, r.WithContext(ctx))  // ADD CONTEXT TIMEOUT 60 SECS
                }
                return
            }
        }else{
            //  Path
            if strings.HasPrefix(r.URL.Path, route.path) {
                route.handler(w, r)  // ADD CONTEXT TIMEOUT 60 SECS
                return
            }
        }
    }
    
    http.Error(w, "Not found", http.StatusNotFound)
}

实现后的代码(不工作)

这是我所期望的工作

  • 在http请求处理程序中休眠10秒
  • 2秒上下文超时
    什么管用
  • 如果请求超时超过2秒,则返回HTTP 408,并关闭连接(如预期)
    什么行不通
  • 如果请求超过了超时时间,http请求处理程序将继续执行,并在超时后8秒打印req executed

输出

2023-08-28 20:14:47 context timeout exceeded
2023-08-28 20:14:47 cancel
2023-08-28 20:14:55 req completed

代码

func (h *HTTP) serve(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), time.Duration(2 * time.Second))
    defer func(){
        fmt.Println(time.Now(), "cancel")
        cancel()
    }()
    
    go func(){
        for _, route := range h.routes {
            if route.regex != nil {
                //  Regex path
                matches := route.regex.FindStringSubmatch(r.URL.Path)
                len     := len(matches)
                if len > 0 {
                    //  Slug group capture
                    if len > 1 {
                        ctx = context.WithValue(ctx, ctx_http, matches[1:])
                    }
                    
                    route.handler(w, r.WithContext(ctx))
                    cancel()
                    return
                }
            }else{
                //  Path
                if strings.HasPrefix(r.URL.Path, route.path) {
                    route.handler(w, r.WithContext(ctx))
                    cancel()
                    return
                }
            }
        }
        
        http.Error(w, "Not found", http.StatusNotFound)
        cancel()
    }()
    
    select {
    case <-ctx.Done():
        switch ctx.Err() {
        case context.DeadlineExceeded:
            fmt.Println(time.Now(), "context timeout exceeded")
            http.Error(w, "Timeout", http.StatusRequestTimeout)
        case context.Canceled:
            fmt.Println(time.Now(), "context cancelled by force. whole process is complete")
        }
    }
}

HTTP请求处理程序

h.Route(serv.ALL, "/timeout", func(w http.ResponseWriter, r *http.Request){
    defer serv.Recover(w)
    
    time.Sleep(10 * time.Second)
    fmt.Println(time.Now(), "req completed")
    
    io.WriteString(w, "timeout test")
})
m0rkklqb

m0rkklqb1#

这个问题明确地询问了关于创建超时上下文的问题,但是OP的注解表明他们希望在不使用上下文的情况下使请求超时。要做到这一点,在OP的原始代码中使用超时处理程序 Package 根处理程序:

srv := &http.Server{
    Addr:               fmt.Sprintf("%s:%d", h.host, h.port),
    Handler:            http.TimeoutHandler(http.HandlerFunc(h.serve), 60 * time.Second, "xyprob"),
}

这是对所提问题的回答。
使用以下代码实现HTTP请求的上下文超时:

func (h *HTTP) serve(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), time.Duration(2*time.Second))
    defer cancel()
    var allow []string
    for _, route := range h.routes {
        if route.regex != nil {
            //  Regex path
            matches := route.regex.FindStringSubmatch(r.URL.Path)
            len := len(matches)
            if len > 0 {
                //  Group captures
                if len > 1 {
                    ctx = context.WithValue(ctx, ctx_http, matches[1:])
                }

                route.handler(w, r.WithContext(ctx))
                return
            }
        } else {
            //  Path
            if strings.HasPrefix(r.URL.Path, route.path) {
                route.handler(w, r.WithContext(ctx))
                return
            }
        }
    }
    if len(allow) > 0 {
        w.Header().Set("Allow", strings.Join(allow, ", "))
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    http.Error(w, "Not found", http.StatusNotFound)
}

在处理程序中使用上下文:

h.Route(serv.ALL, "/timeout", func(w http.ResponseWriter, r *http.Request){
    defer serv.Recover(w)

    // Sleep for 10 seconds or wait for context to cancel.
    select {
    case <-time.After(10 * time.Second):
    case <-r.Context().Done():
        http.Error(w, "Timeout", http.StatusRequestTimeout)
        return
    }
        
    fmt.Println(time.Now(), "req completed")
    io.WriteString(w, "timeout test")
})
jc3wubiy

jc3wubiy2#

您可以使用WithTimout将Timeout添加到上下文,并可以使用r.WithContext(ctx)将上下文传递给请求,就像您在其中一个案例中所做的那样。

相关问题