首页  >  文章  >  后端开发  >  `json.NewDecoder().Decode()` 是否会忽略 Go HTTP 请求中的上下文截止日期?

`json.NewDecoder().Decode()` 是否会忽略 Go HTTP 请求中的上下文截止日期?

Mary-Kate Olsen
Mary-Kate Olsen原创
2024-10-29 04:41:29674浏览

 Does `json.NewDecoder().Decode()` Ignore Context Deadlines in Go HTTP Requests?

Go json.NewDecoder().Decode() 忽略上下文截止日期?

问题:

在 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中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn