[インデックス 17136] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおける ReadResponse
関数の挙動を改善するものです。具体的には、ReadResponse
関数が nil
の Request
パラメータを受け取った場合でも正しく動作するように修正されています。これにより、HTTPレスポンスの読み込み処理の柔軟性が向上しました。
コミット
- コミットハッシュ:
e3dbb1a31059994de2b20fedca0a8061cc90a7dd
- Author: Taru Karttunen taruti@taruti.net
- Date: Fri Aug 9 15:11:03 2013 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e3dbb1a31059994de2b20fedca0a8061cc90a7dd
元コミット内容
net/http: Make ReadResponse work with a nil Request parameter
Fixes #5583
R=golang-dev, dave, bradfitz
CC=golang-dev
https://golang.org/cl/9821043
変更の背景
net/http
パッケージの ReadResponse
関数は、HTTPレスポンスを bufio.Reader
から読み込むための関数です。この関数は、対応する http.Request
オブジェクトを req
パラメータとして受け取ります。以前の実装では、この req
パラメータが nil
であると、レスポンスボディの読み込み方法(特に Content-Length
や Transfer-Encoding
が指定されていない場合の挙動)を決定する際に問題が発生する可能性がありました。
HTTP/1.0 のレスポンスでは、Content-Length
ヘッダがない場合、サーバーは接続を閉じることでレスポンスボディの終了を示します("Connection: close")。しかし、HTTP/1.1 では、Content-Length
または Transfer-Encoding: chunked
がない場合、レスポンスボディの終了はリクエストメソッドに依存することがあります。例えば、HEAD
リクエストに対するレスポンスにはボディがないと見なされます。
このコミット以前は、ReadResponse
が nil
の Request
を受け取ると、レスポンスボディの長さを正しく判断できず、予期せぬ動作やエラーを引き起こす可能性がありました。この修正は、req
が nil
の場合でも、ReadResponse
が堅牢に動作するようにするためのものです。具体的には、req
が nil
の場合は GET
リクエストであると仮定することで、この問題を解決しています。
前提知識の解説
- HTTP (Hypertext Transfer Protocol): Web上でデータを交換するためのプロトコル。リクエストとレスポンスの形式が定義されています。
- HTTPリクエスト (http.Request): クライアントがサーバーに送信する情報。メソッド(GET, POSTなど)、URL、ヘッダ、ボディなどを含みます。
- HTTPレスポンス (http.Response): サーバーがクライアントに返す情報。ステータスコード(200 OK, 404 Not Foundなど)、ヘッダ、ボディなどを含みます。
net/http
パッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。bufio.Reader
: バッファリングされたI/O操作を提供するGoの型。効率的な読み込みを可能にします。ReadResponse
関数:net/http
パッケージ内の関数で、bufio.Reader
からHTTPレスポンスを解析し、http.Response
オブジェクトとして返します。Content-Length
ヘッダ: HTTPメッセージのボディのバイト数を示すヘッダ。Transfer-Encoding: chunked
ヘッダ: HTTP/1.1 で使用されるエンコーディング方式で、ボディをチャンク(塊)に分割して送信します。これにより、事前にボディの全長を知らなくてもストリーミングが可能になります。Connection: close
ヘッダ: HTTP/1.0 でよく使われるヘッダで、レスポンスの送信後に接続を閉じることを示します。これにより、ボディの終了を通知する役割も果たします。RequestMethod
: HTTPリクエストのメソッド(GET, POST, HEADなど)。レスポンスボディの処理に影響を与えることがあります。例えば、HEAD
リクエストに対するレスポンスにはボディが含まれないことが期待されます。
技術的詳細
このコミットの主要な変更点は、net/http
パッケージ内の ReadResponse
関数と、レスポンスボディの読み込みを処理する内部関数 readTransfer
の挙動にあります。
-
ReadResponse
関数の変更:- 以前の
ReadResponse
関数のドキュメントでは、req
パラメータは「このレスポンスに対応するリクエストを指定する」とされていました。 - 修正後、ドキュメントは「
req
パラメータはオプションで、このレスポンスに対応するリクエストを指定する。nil
の場合、GET
リクエストであると仮定される」と変更されました。 - コードレベルでは、
resp = new(Response)
ではなくresp := &Response{Request: req,}
と初期化されるようになりました。これは、Response
オブジェクトのRequest
フィールドが、関数呼び出し時に渡されたreq
パラメータで直接初期化されることを意味します。これにより、resp.Request = req
という明示的な代入が不要になります。
- 以前の
-
readTransfer
関数の変更:readTransfer
関数は、HTTPメッセージ(リクエストまたはレスポンス)のボディの読み込み方法を決定する内部関数です。この関数は、Content-Length
やTransfer-Encoding
ヘッダ、そしてリクエストメソッドに基づいてボディの長さを判断します。- 以前の実装では、
transferReader
構造体のRequestMethod
フィールドは、レスポンスの場合rr.Request.Method
から直接設定されていました。しかし、rr.Request
がnil
の場合、これはパニックを引き起こす可能性がありました。 - 修正後、
transferReader
の初期化時にRequestMethod: "GET"
がデフォルトで設定されるようになりました。 - レスポンスを処理する
case *Response:
ブロック内で、rr.Request
がnil
でない場合にのみt.RequestMethod = rr.Request.Method
が実行されるようになりました。これにより、nil
ポインタ参照によるパニックが回避されます。 - リクエストを処理する
case *Request:
ブロックでは、以前はt.RequestMethod = "GET"
が明示的に設定されていましたが、これはtransferReader
の初期化時に既にGET
が設定されているため、削除されました。
これらの変更により、ReadResponse
関数が nil
の Request
パラメータを受け取った場合でも、readTransfer
関数が RequestMethod
を安全に「GET」として扱い、レスポンスボディの読み込みロジックが正しく適用されるようになりました。特に、Content-Length
や Transfer-Encoding
がないHTTP/1.0のレスポンスにおいて、接続が閉じられるまでボディを読み込むという挙動が保証されます。
コアとなるコードの変更箇所
src/pkg/net/http/response.go
--- a/src/pkg/net/http/response.go
+++ b/src/pkg/net/http/response.go
@@ -98,18 +98,17 @@ func (r *Response) Location() (*url.URL, error) {
return url.Parse(lv)
}
-// ReadResponse reads and returns an HTTP response from r. The
-// req parameter specifies the Request that corresponds to
-// this Response. Clients must call resp.Body.Close when finished
-// reading resp.Body. After that call, clients can inspect
-// resp.Trailer to find key/value pairs included in the response
-// trailer.
-func ReadResponse(r *bufio.Reader, req *Request) (resp *Response, err error) {
-
+// ReadResponse reads and returns an HTTP response from r.
+// The req parameter optionally specifies the Request that corresponds
+// to this Response. If nil, a GET request is assumed.
+// Clients must call resp.Body.Close when finished reading resp.Body.
+// After that call, clients can inspect resp.Trailer to find key/value
+// pairs included in the response trailer.
+func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) {
tp := textproto.NewReader(r)
- resp = new(Response)
-
- resp.Request = req
+ resp := &Response{
+ Request: req,
+ }
// Parse the first line of the response.
line, err := tp.ReadLine()
src/pkg/net/http/transfer.go
--- a/src/pkg/net/http/transfer.go
+++ b/src/pkg/net/http/transfer.go
@@ -254,7 +254,7 @@ func bodyAllowedForStatus(status int) bool {
// msg is *Request or *Response.
func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
-\tt := &transferReader{}\n+\tt := &transferReader{RequestMethod: "GET"}\n
\t// Unify input
\tisResponse := false
\tswitch rr := msg.(type) {
@@ -262,11 +262,13 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
\t\tt.Header = rr.Header
\t\tt.StatusCode = rr.StatusCode
-\t\tt.RequestMethod = rr.Request.Method
\t\tt.ProtoMajor = rr.ProtoMajor
\t\tt.ProtoMinor = rr.ProtoMinor
\t\tt.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header)
\t\tisResponse = true
+\t\tif rr.Request != nil {
+\t\t\tt.RequestMethod = rr.Request.Method
+\t\t}\n \tcase *Request:
\t\tt.Header = rr.Header
\t\tt.ProtoMajor = rr.ProtoMajor
\t\tt.ProtoMinor = rr.ProtoMinor
@@ -274,7 +276,6 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
\t\t// Transfer semantics for Requests are exactly like those for
\t\t// Responses with status code 200, responding to a GET method
\t\tt.StatusCode = 200
-\t\tt.RequestMethod = "GET"\n \tdefault:
\t\tpanic("unexpected type")
\t}
src/pkg/net/http/response_test.go
テストケースが追加されています。Content-Length
がなく Connection: close
ヘッダを持つHTTP/1.0のレスポンスを、Request
が nil
の状態で ReadResponse
が正しく処理できることを検証するテストです。
コアとなるコードの解説
src/pkg/net/http/response.go
の変更
ReadResponse
関数のシグネチャとコメントの変更:- 以前の
func ReadResponse(r *bufio.Reader, req *Request) (resp *Response, err error)
からfunc ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
に変更されました。これは、名前付き戻り値resp
を削除し、より一般的なGoの慣習に従ったものです。 - コメントが更新され、
req
パラメータがオプションであり、nil
の場合はGET
リクエストが仮定されることが明記されました。これは、関数の新しい挙動を明確に示しています。
- 以前の
Response
オブジェクトの初期化:resp = new(Response)
とresp.Request = req
の2行が、resp := &Response{Request: req,}
の1行に集約されました。これは、Response
構造体の新しいインスタンスを作成し、同時にそのRequest
フィールドを渡されたreq
パラメータで初期化する、より簡潔な方法です。機能的には同じですが、コードの可読性と簡潔性が向上しています。
src/pkg/net/http/transfer.go
の変更
transferReader
の初期化におけるRequestMethod
のデフォルト設定:t := &transferReader{}
がt := &transferReader{RequestMethod: "GET"}
に変更されました。これは、transferReader
のインスタンスが作成される際に、デフォルトでRequestMethod
が"GET"
に設定されることを意味します。これにより、後続のロジックでRequestMethod
が明示的に設定されない場合でも、安全なデフォルト値が保証されます。
- レスポンス処理における
RequestMethod
の条件付き設定:case *Response:
ブロック内で、以前は無条件にt.RequestMethod = rr.Request.Method
が実行されていました。これがif rr.Request != nil { t.RequestMethod = rr.Request.Method }
に変更されました。この変更により、rr.Request
がnil
の場合にnil
ポインタのデリファレンスによるパニックが発生するのを防ぎます。rr.Request
がnil
であれば、transferReader
の初期化時に設定されたデフォルトの"GET"
がそのまま使用されます。
- リクエスト処理における
RequestMethod
の設定の削除:case *Request:
ブロック内で、以前はt.RequestMethod = "GET"
が明示的に設定されていました。これは、transferReader
の初期化時に既に"GET"
がデフォルトで設定されているため、冗長となり削除されました。
これらの変更は、ReadResponse
関数が nil
の Request
パラメータを受け取った場合でも、HTTPレスポンスのボディの読み込みロジックが堅牢かつ安全に動作するようにするためのものです。特に、Content-Length
や Transfer-Encoding
ヘッダがない場合のボディの終了判断において、GET
リクエストのセマンティクスをデフォルトで適用することで、予期せぬ動作を防ぎます。
関連リンク
- Go
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - HTTP/1.1 仕様 (RFC 2616): https://www.rfc-editor.org/rfc/rfc2616 (特にセクション 4.4 Message Length)
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語の公式ドキュメント: https://go.dev/doc/
- HTTPプロトコルに関する一般的な知識