[インデックス 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プロトコルに関する一般的な知識