【问题标题】:Preventing context deadline exceeded (Client.Timeout exceeded while awaiting headers) error with HTTP 200 OK使用 HTTP 200 OK 防止超出上下文期限(等待标头时超出 Client.Timeout)错误
【发布时间】:2021-02-13 14:24:15
【问题描述】:

我有一个客户端和服务器。服务器处理请求超过 2 秒。但是客户没有那么多时间。它需要在 1 秒内得到响应。这就是为什么它会响应

获取“http://localhost:8888”:超出上下文截止日期(等待标头时超出 Client.Timeout) 恐慌:运行时错误:无效的内存地址或 nil 指针取消引用

问题? 如何更改 server.go 以正确恢复所有错误 并且端点始终可用? 注意,客户端正在调用的端点应尽快处理请求,并在完成后立即返回 200 OK。

client.go

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func main() {

    c := &http.Client{Timeout: 2 * time.Second}

    res, err := c.Get("http://localhost:8888")
    if err != nil {
        log.Println(err)
    }
    var r []byte

    _, err = res.Body.Read(r)
    fmt.Println(string(r))
}

server.go

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

func slowHandler(w http.ResponseWriter, req *http.Request) {
    time.Sleep(2 * time.Second)
    io.WriteString(w, "I am slow!\n")
}

func main() {
    srv := http.Server{
        Addr:    ":8888",
        Handler: http.HandlerFunc(slowHandler),
    }

    if err := srv.ListenAndServe(); err != nil {
        fmt.Printf("Server failed: %s\n", err)
    }
}

【问题讨论】:

    标签: go tcp server-timing


    【解决方案1】:

    您可以通过实施恢复中间件从恐慌中恢复,例如:

     defer func() {
          if err := recover(); err != nil {
            log.Println("recovered from panic", err)
            fmt.Fprintf(w, "recovered from panic")
          }
        }()
    

    您可以将这篇有用的文章作为指导https://www.nicolasmerouze.com/middlewares-golang-best-practices-examples

    编辑

    您可以创建自定义中间件来处理自定义处理程序超时

    func timeOutMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            done := make(chan bool)
            ctx, cancelFunc := context.WithTimeout(r.Context(), time.Second*1)
            defer cancelFunc()
            go func() {
                next.ServeHTTP(w, r)
                close(done)
            }()
            select {
            case <-done:
                return
            case <-ctx.Done():
                w.WriteHeader(http.StatusOK)
                w.Write([]byte(`{"message": "handled time out"}`))
            }
        })
    
    }
    
    

    为了使其正常工作,您需要与客户端同步,因为如果客户端设置了较低的超时,那么将永远不会得到正确的响应,或者如果服务器设置的超时非常低,这也可能发生。

    也可以使用这个来完全读取响应字节

    defer res.Body.Close()
    body, _ := ioutil.ReadAll(res.Body)
    

    【讨论】:

    • 啊,我的错,不是服务器恐慌是客户端,因为由于超时,您正在读取 nil 响应,在您的示例中,客户端永远不会从服务器获得答案,因此,r 是 nil 并且恐慌。你现在可以做两件事: - 客户端错误处理if err, ok := err.(net.Error); ok &amp;&amp; err.Timeout() { - 服务器处理程序超时Handler: http.TimeoutHandler(http.HandlerFunc(slowHandler), 1*time.Second, "I timed out!\n"), 这将返回503,但一旦超时完成就会返回
    • 假设此服务是公共服务,它需要不可预测的时间,并且可能有很多客户端。有些人可以多分配 1 秒。您的代码在这种情况下不起作用,
    • 我不确定你所说的是否可能,我唯一能想到的就是在请求中接收参数/标头,然后根据它调整上下文超时,否则您无法知道客户端超时设置是什么。
    • 很遗憾,您无法从请求标头中获取客户端超时。
    • 我的意思是客户需要事先知道的自定义标头/参数
    猜你喜欢
    • 2022-01-15
    • 2020-09-23
    • 1970-01-01
    • 1970-01-01
    • 2019-09-20
    • 1970-01-01
    • 2018-03-31
    • 2019-12-25
    • 2022-07-28
    相关资源
    最近更新 更多