問題:
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 中国語 Web サイトの他の関連記事を参照してください。