[インデックス 14987] ファイルの概要
このコミットは、Go言語の net/http
パッケージにおける HEAD
リクエストのレスポンス処理に関するバグ修正です。具体的には、HEAD
リクエストに対するレスポンスが常に Content-Length
ヘッダーを持つという誤った仮定を修正し、Transfer-Encoding
ヘッダーや、Content-Length
も Transfer-Encoding
も持たないケースを適切に処理できるように改善しています。これにより、HTTP/1.1の仕様(RFC2616)に準拠し、より堅牢なHTTPクライアント/サーバー実装に貢献しています。
コミット
commit 3c77b8961c97816417fd66816e53a9aa6d64de46
Author: John Graham-Cumming <jgc@jgc.org>
Date: Fri Jan 25 10:20:19 2013 -0800
net/http: fix Content-Length/Transfer-Encoding on HEAD requests
net/http currently assumes that the response to a HEAD request
will always have a Content-Length header. This is incorrect.
RFC2616 says: "The HEAD method is identical to GET except that
the server MUST NOT return a message-body in the response. The
metainformation contained in the HTTP headers in response to a
HEAD request SHOULD be identical to the information sent in
response to a GET request. This method can be used for
obtaining metainformation about the entity implied by the
request without transferring the entity-body itself. This
method is often used for testing hypertext links for validity,
accessibility, and recent modification."
This means that three cases are possible: a Content-Length
header, a Transfer-Encoding header or neither. In the wild the
following sites exhibit these behaviours (curl -I):
HEAD on http://www.google.co.uk/ has Transfer-Encoding: chunked
HEAD on http://www.bbc.co.uk/ has Content-Length: 45247
HEAD on http://edition.cnn.com/ has neither header
This patch does not remove the ErrMissingContentLength error
for compatibility reasons, but it is no longer used.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7182045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3c77b8961c97816417fd66816e53a9aa6d64de46
元コミット内容
net/http
パッケージは現在、HEAD
リクエストに対するレスポンスが常に Content-Length
ヘッダーを持つと仮定しています。これは誤りです。
RFC2616には次のように記載されています:「HEAD
メソッドは GET
と同一ですが、サーバーはレスポンスにメッセージボディを返してはなりません。HEAD
リクエストに対するHTTPヘッダーに含まれるメタ情報は、GET
リクエストに対する情報と同一であるべきです。このメソッドは、エンティティボディ自体を転送することなく、リクエストによって示されるエンティティに関するメタ情報を取得するために使用できます。このメソッドは、ハイパーテキストリンクの有効性、アクセシビリティ、および最近の変更をテストするためによく使用されます。」
これは、HEAD
レスポンスには3つのケースが考えられることを意味します:Content-Length
ヘッダーを持つ場合、Transfer-Encoding
ヘッダーを持つ場合、またはどちらも持たない場合です。実際のウェブサイトでは、以下のサイトがこれらの挙動を示します(curl -I
で確認):
http://www.google.co.uk/
へのHEAD
リクエストはTransfer-Encoding: chunked
を持つhttp://www.bbc.co.uk/
へのHEAD
リクエストはContent-Length: 45247
を持つhttp://edition.cnn.com/
へのHEAD
リクエストはどちらのヘッダーも持たない
このパッチは、互換性のために ErrMissingContentLength
エラーを削除しませんが、もはや使用されなくなります。
変更の背景
Go言語の net/http
パッケージは、HTTP HEAD
リクエストに対するレスポンスの処理において、誤った仮定に基づいていました。具体的には、HEAD
レスポンスには常に Content-Length
ヘッダーが含まれると想定していました。しかし、これはHTTP/1.1の仕様であるRFC2616に反するものであり、実際のウェブサーバーの挙動とも乖離がありました。
RFC2616の定義によれば、HEAD
メソッドは GET
メソッドとほぼ同じですが、レスポンスにメッセージボディを含んではなりません。しかし、レスポンスヘッダーに含まれるメタ情報は GET
リクエストの場合と同一であるべきです。この「同一であるべき」という点が重要で、GET
リクエストが Content-Length
や Transfer-Encoding: chunked
を含む可能性があるのと同様に、HEAD
リクエストのレスポンスもこれらのヘッダーを含む可能性があります。
コミットメッセージに挙げられている実例(Google, BBC, CNN)が示すように、実際のウェブサーバーは HEAD
レスポンスに対して以下の3つの異なるパターンでヘッダーを返します。
Transfer-Encoding: chunked
を持つ場合: メッセージボディがないにもかかわらず、GET
リクエストでボディがチャンクエンコードされる場合に備えて、このヘッダーを返すサーバーが存在します。Content-Length
を持つ場合:GET
リクエストでボディの長さが固定されている場合に備えて、その長さをContent-Length
ヘッダーで示すサーバーが存在します。- どちらのヘッダーも持たない場合:
GET
リクエストでボディの長さが不明確であるか、あるいはサーバーがこれらのヘッダーをHEAD
レスポンスに含める必要がないと判断した場合です。
net/http
パッケージが Content-Length
の存在を前提としていたため、Transfer-Encoding
ヘッダーを持つ HEAD
レスポンスや、どちらのヘッダーも持たない HEAD
レスポンスを正しく処理できず、潜在的な互換性問題や誤動作を引き起こす可能性がありました。このコミットは、この誤った仮定を修正し、RFCに準拠したより柔軟な HEAD
レスポンス処理を実現することを目的としています。
前提知識の解説
HTTP HEADメソッド
HTTP HEAD
メソッドは、ウェブサーバーからリソースのヘッダー情報のみを取得するために使用されます。GET
メソッドと非常に似ていますが、GET
がリソースの完全な内容(メッセージボディ)を返すのに対し、HEAD
はメッセージボディを返しません。これは、リソースの存在確認、最終更新日時、コンテンツタイプ、コンテンツサイズなどのメタデータのみが必要な場合に非常に効率的です。例えば、リンクの有効性チェック、キャッシュの検証、大きなファイルのダウンロード前にサイズを確認する際などに利用されます。
RFC2616(HTTP/1.1の仕様)のセクション9.4には、HEAD
メソッドについて次のように記述されています。
「HEAD
メソッドは GET
と同一ですが、サーバーはレスポンスにメッセージボディを返してはなりません。HEAD
リクエストに対するHTTPヘッダーに含まれるメタ情報は、GET
リクエストに対する情報と同一であるべきです。」
この「同一であるべき」という点が、GET
レスポンスが持ちうる Content-Length
や Transfer-Encoding
といったヘッダーが、HEAD
レスポンスにも含まれる可能性があることを示唆しています。
HTTP Content-Lengthヘッダー
Content-Length
ヘッダーは、HTTPメッセージボディのオクテット(バイト)単位の長さを指定します。このヘッダーが存在する場合、クライアントはメッセージボディの全体を受信するまで接続を維持し、指定された長さのデータを受信した時点でボディの読み込みを終了します。これは、メッセージボディのサイズが事前に分かっている場合に主に使用されます。
HTTP Transfer-Encodingヘッダー (特にchunked)
Transfer-Encoding
ヘッダーは、メッセージボディがクライアントに転送される前に適用されたエンコーディング形式を示します。最も一般的な値は chunked
です。
Transfer-Encoding: chunked
は、メッセージボディのサイズが事前に不明な場合や、動的に生成される場合に特に有用です。このエンコーディングでは、メッセージボディが複数の「チャンク」に分割され、各チャンクは自身のサイズ情報と共に送信されます。最後のチャンクはサイズが0であり、これによってメッセージボディの終わりが示されます。これにより、サーバーはメッセージボディ全体をバッファリングすることなく、リアルタイムでデータを送信できます。
HEAD
リクエストの場合、メッセージボディは存在しませんが、GET
リクエストでボディがチャンクエンコードされる可能性がある場合、サーバーは HEAD
レスポンスにも Transfer-Encoding: chunked
を含めることがあります。これは、GET
レスポンスのメタ情報と同一であるべきというRFCの原則に従うためです。
RFC2616
RFC2616は、Hypertext Transfer Protocol -- HTTP/1.1 の仕様を定義する文書です。これはHTTPプロトコルの基本的な動作、ヘッダーフィールド、メソッドなどを詳細に規定しており、ウェブ技術の基盤となる重要な標準です。このコミットの背景にある問題は、net/http
パッケージがこのRFCの HEAD
メソッドに関する規定を完全に遵守していなかったことに起因します。
Go言語のnet/httpパッケージ
net/http
パッケージは、Go言語の標準ライブラリの一部であり、HTTPクライアントとサーバーの実装を提供します。このパッケージは、ウェブアプリケーションの構築、RESTful APIの作成、HTTPリクエストの送信など、Go言語におけるネットワーク通信の基盤となります。このコミットは、このパッケージのHTTPプロトコル準拠性を向上させるための重要な修正です。
技術的詳細
このコミットの技術的な核心は、net/http
パッケージが HEAD
リクエストのレスポンスを解析する際のロジックを、HTTP/1.1の仕様(RFC2616)に厳密に準拠させるように変更した点にあります。
以前の net/http
の実装では、HEAD
リクエストに対するレスポンスは常に Content-Length
ヘッダーを持つものと決めつけていました。この仮定は、メッセージボディを持たない HEAD
レスポンスにおいて、ボディの長さを指定する Content-Length
が存在しない場合や、Transfer-Encoding: chunked
が指定されている場合に問題を引き起こしていました。
コミットメッセージが指摘するように、HEAD
レスポンスには以下の3つの有効なシナリオが存在します。
Content-Length
ヘッダーを持つ場合:GET
リクエストで固定長のボディが返される場合に、その長さをHEAD
レスポンスでも示すケース。Transfer-Encoding: chunked
ヘッダーを持つ場合:GET
リクエストでチャンクエンコードされたボディが返される場合に、そのエンコーディングをHEAD
レスポンスでも示すケース。メッセージボディは存在しないため、実質的なチャンクデータは送信されません。- どちらのヘッダーも持たない場合:
GET
リクエストでボディの長さが不明確であるか、サーバーがこれらのヘッダーをHEAD
レスポンスに含める必要がないと判断した場合。
このパッチは、これらのシナリオを適切に処理するために、主に src/pkg/net/http/transfer.go
内のレスポンスヘッダー処理ロジックを修正しています。
Transfer-Encoding
の扱い: 以前はHEAD
リクエストの場合、Transfer-Encoding
ヘッダーを無条件に無視(nil
に設定)していました。しかし、RFCの「メタ情報はGET
と同一であるべき」という原則に従い、Transfer-Encoding: chunked
が存在する場合はこれを認識し、Content-Length
を-1
(不明) に設定するように変更されました。これにより、HEAD
レスポンスでもTransfer-Encoding
の情報が正しく伝達されるようになります。Content-Length
の強制: 以前はHEAD
レスポンスに対してContent-Length
が存在しない場合にErrMissingContentLength
を返す可能性がありましたが、このチェックは削除されました。これにより、Content-Length
が存在しないHEAD
レスポンスもエラーなく処理できるようになります。コミットメッセージにある通り、ErrMissingContentLength
自体は互換性のために削除されていませんが、このコンテキストでは使用されなくなりました。- テストケースの拡充:
src/pkg/net/http/response_test.go
には、HEAD
リクエストに対する様々なレスポンスヘッダーの組み合わせ(Transfer-Encoding: chunked
のみ、Content-Length
のみ、どちらもなし)を検証する新しいテストケースが追加されています。これにより、修正が正しく機能していることが保証されます。また、一部のテストケースでHTTPプロトコルバージョンがHTTP/1.0
からHTTP/1.1
に変更されており、より現代的なHTTPの挙動を反映しています。
これらの変更により、net/http
パッケージは HEAD
リクエストのレスポンスをより正確に解釈し、ウェブサーバーの多様な実装に対応できるようになりました。
コアとなるコードの変更箇所
このコミットでは、主に以下の2つのファイルが変更されています。
-
src/pkg/net/http/response_test.go
:respTests
変数に、HEAD
リクエストに対する新しいテストケースが複数追加されています。Transfer-Encoding: chunked
を持つHEAD
レスポンスのテストケースが修正され、Proto
とProtoMinor
がHTTP/1.1
に変更され、Close
がfalse
に設定されています。Content-Length
を持つHEAD
レスポンス(HTTP/1.0とHTTP/1.1の両方)の新しいテストケースが追加されています。Content-Length
もTransfer-Encoding
も持たないHEAD
レスポンスの新しいテストケースが追加されています。- 既存のテストケースの一部で、
HTTP/1.0
からHTTP/1.1
へのプロトコルバージョン変更が行われています。
-
src/pkg/net/http/transfer.go
:newTransferWriter
関数内のif t.ResponseToHEAD
ブロックが変更されています。- 以前は
t.TransferEncoding = nil
とif t.ContentLength < 0 { return nil, ErrMissingContentLength }
が含まれていましたが、これらが削除されました。 - 代わりに
if chunked(t.TransferEncoding) { t.ContentLength = -1 }
が追加され、Transfer-Encoding: chunked
が存在する場合にContentLength
を-1
に設定するようになりました。
- 以前は
shouldSendContentLength
関数内のif t.ResponseToHEAD { return true }
の行が削除されています。fixTransferEncoding
関数内のif requestMethod == "HEAD" { return nil, nil }
のブロックが削除されています。
コアとなるコードの解説
src/pkg/net/http/response_test.go
の変更点
このファイルは、net/http
パッケージがHTTPレスポンスを正しく解析できるかを検証するためのテストケースを定義しています。変更の主な目的は、HEAD
リクエストに対するレスポンスの多様なパターンを網羅的にテストすることです。
HTTP/1.0
からHTTP/1.1
への変更: 既存のチャンクエンコードされたレスポンスのテストケースで、プロトコルバージョンがHTTP/1.0
からHTTP/1.1
に変更されています。これは、Transfer-Encoding: chunked
がHTTP/1.1で導入された機能であるため、より正確なテスト環境を反映しています。また、Close
フィールドがtrue
からfalse
に変更されているのは、HTTP/1.1ではデフォルトでコネクションが持続される(Keep-Alive)ためです。HEAD
リクエストの新しいテストケース:- チャンクエンコードされた
HEAD
レスポンス:Transfer-Encoding: chunked
ヘッダーを持つHEAD
レスポンスが正しく解析され、TransferEncoding
フィールドが[]string{"chunked"}
となり、ContentLength
が-1
(不明) となることを検証します。以前はHEAD
レスポンスのTransfer-Encoding
は無視されると仮定されていましたが、このテストはそれを覆し、RFCの「メタ情報はGET
と同一であるべき」という原則に従った挙動を期待します。 Content-Length
を持つHEAD
レスポンス:Content-Length
ヘッダーを持つHEAD
レスポンスが正しく解析され、ContentLength
フィールドにその値が設定されることを検証します。HTTP/1.0とHTTP/1.1の両方でテストされています。- ヘッダーを持たない
HEAD
レスポンス:Content-Length
もTransfer-Encoding
も持たないHEAD
レスポンスが正しく処理され、ContentLength
が-1
となることを検証します。
- チャンクエンコードされた
これらの追加されたテストケースは、net/http
パッケージが HEAD
リクエストに対するレスポンスを、RFC2616の仕様と実際のウェブサーバーの挙動に合わせて、より柔軟かつ正確に処理できるようになったことを保証します。
src/pkg/net/http/transfer.go
の変更点
このファイルは、HTTPメッセージの転送エンコーディングやコンテンツ長に関する低レベルな処理を扱います。ここでの変更は、HEAD
リクエストのレスポンスヘッダーの解釈ロジックを直接修正しています。
-
newTransferWriter
関数:- この関数は、HTTPレスポンスの書き込み準備を行う際に、レスポンスの特性(
HEAD
リクエストに対するものか、ボディがあるかなど)に基づいてtransferWriter
構造体を初期化します。 - 以前のコードでは、
HEAD
リクエストに対するレスポンスの場合、t.TransferEncoding
を無条件にnil
に設定し、ContentLength
が負の値であればErrMissingContentLength
を返していました。これは、HEAD
レスポンスにはTransfer-Encoding
が存在せず、Content-Length
が必須であるという誤った仮定に基づいています。 - 変更後:
t.TransferEncoding = nil
とErrMissingContentLength
のチェックが削除されました。代わりに、if chunked(t.TransferEncoding) { t.ContentLength = -1 }
が追加されました。これは、もしレスポンスにTransfer-Encoding: chunked
が含まれている場合、Content-Length
は不明である(-1
)とマークすべきであることを意味します。これにより、HEAD
レスポンスであってもTransfer-Encoding
の情報が保持され、Content-Length
の有無に柔軟に対応できるようになります。
- この関数は、HTTPレスポンスの書き込み準備を行う際に、レスポンスの特性(
-
shouldSendContentLength
関数:- この関数は、HTTPレスポンスを送信する際に
Content-Length
ヘッダーを含めるべきかどうかを決定します。 - 以前のコードでは、
if t.ResponseToHEAD { return true }
という行があり、HEAD
リクエストに対するレスポンスであれば無条件にContent-Length
を送信すべきだと判断していました。これは、HEAD
レスポンスには常にContent-Length
があるという誤った仮定を強化していました。 - 変更後: この行が削除されました。これにより、
HEAD
レスポンスであること自体がContent-Length
送信の決定要因ではなくなり、他の条件(例えば、ボディの長さが既知であるかなど)に基づいて判断されるようになります。
- この関数は、HTTPレスポンスを送信する際に
-
fixTransferEncoding
関数:- この関数は、レスポンスヘッダーから
Transfer-Encoding
を処理し、適切なエンコーディングを決定します。 - 以前のコードでは、
if requestMethod == "HEAD" { return nil, nil }
というブロックがあり、HEAD
リクエストの場合にはTransfer-Encoding
を無条件に無視していました。 - 変更後: このブロックが削除されました。これにより、
HEAD
リクエストであってもTransfer-Encoding
ヘッダーが正しく解析され、その情報がレスポンスオブジェクトに反映されるようになります。
- この関数は、レスポンスヘッダーから
これらの変更は、net/http
パッケージがHTTP/1.1の仕様に準拠し、HEAD
リクエストに対するレスポンスの多様な形式を正確に処理するための重要な修正です。これにより、Go言語のHTTPクライアントおよびサーバーの堅牢性と互換性が向上します。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/7182045
- GitHub コミットページ: https://github.com/golang/go/commit/3c77b8961c97816417fd66816e53a9aa6d64de46
参考にした情報源リンク
- RFC2616 - Hypertext Transfer Protocol -- HTTP/1.1:
- 特にセクション 9.4 "HEAD" メソッドに関する記述: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
- セクション 4.4 "Message Length" (Content-LengthとTransfer-Encodingに関する記述): https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
- MDN Web Docs - HTTP
Content-Length
: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Length - MDN Web Docs - HTTP
Transfer-Encoding
: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Transfer-Encoding