`json.NewDecoder().Decode()`는 Go HTTP 요청에서 컨텍스트 마감 기한을 무시합니까?

Mary-Kate Olsen
풀어 주다: 2024-10-29 04:41:29
원래의
672명이 탐색했습니다.

 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿