问题:
在 Go 程序中如果有上下文截止日期,使用 ioutil.ReadAll() 读取响应正文会导致超出预期截止日期错误。但是,使用 json.NewDecoder(resp.Body).Decode() 会返回 nil。
代码示例:
<code class="go">package main import ( "context" "encoding/json" "fmt" "io/ioutil" "net/http" "time" ) var url string = "http://ip.jsontest.com/" func main() { readDoesntFail() readFails() } type IpResponse struct { Ip string } func readDoesntFail() { ctx, _ := context.WithTimeout(context.Background(), time.Second*5) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { panic(err) } resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } ipResponse := new(IpResponse) time.Sleep(time.Second * 6) fmt.Println("before reading response body, context error is:", ctx.Err()) err = json.NewDecoder(resp.Body).Decode(ipResponse) if err != nil { panic(err) } fmt.Println("Expected panic but there was none") } func readFails() { ctx, _ := context.WithTimeout(context.Background(), time.Second*5) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { panic(err) } resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } time.Sleep(time.Second * 6) fmt.Println("before reading response body, context error is:", ctx.Err()) _, err = ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("received expected error", err) } }</code>
答案:
在net/http包中,缓冲区可以用来处理请求。因此,在您尝试读取传入的响应正文之前,可能会部分或全部读取并缓冲它。因此,过期的上下文可能不会妨碍您完成正文的阅读。这正是在这种情况下发生的情况。
为了更好地理解,让我们更改代码以创建一个有意推迟响应的测试 HTTP 服务器:
<code class="go">ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s := []byte(`{"ip":"12.34.56.78"}`) w.Write(s[:10]) if f, ok := w.(http.Flusher); ok { f.Flush() } time.Sleep(time.Second * 6) w.Write(s[10:]) })) defer ts.Close() url = ts.URL readDoesntFail() readFails()</code>
修改后的示例发送一个 JSON与 ip.jsontest.com 的响应类似的对象,但在刷新之前仅传输主体的前 10 个字节。然后它会暂停传输 6 秒,给客户端一个超时的机会。
当我们执行 readDoesntFail() 时,我们观察到以下行为:
before reading response body, context error is: context deadline exceeded panic: Get "http://127.0.0.1:38230": context deadline exceeded goroutine 1 [running]: main.readDoesntFail() /tmp/sandbox721114198/prog.go:46 +0x2b4 main.main() /tmp/sandbox721114198/prog.go:28 +0x93
在这种情况下, json.Decoder.Decode() 尝试从连接读取,因为数据尚未缓冲。一旦上下文过期,从连接中进一步读取会导致超出截止日期错误。然而,在最初的示例中, json.Decoder.Decode() 正在读取已经缓冲的数据,使过期的上下文变得无关紧要。
以上是`json.NewDecoder().Decode()` 是否会忽略 Go HTTP 请求中的上下文截止日期?的详细内容。更多信息请关注PHP中文网其他相关文章!