[インデックス 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