Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14987] ファイルの概要

このコミットは、Go言語の net/http パッケージにおける HEAD リクエストのレスポンス処理に関するバグ修正です。具体的には、HEAD リクエストに対するレスポンスが常に Content-Length ヘッダーを持つという誤った仮定を修正し、Transfer-Encoding ヘッダーや、Content-LengthTransfer-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-LengthTransfer-Encoding: chunked を含む可能性があるのと同様に、HEAD リクエストのレスポンスもこれらのヘッダーを含む可能性があります。

コミットメッセージに挙げられている実例(Google, BBC, CNN)が示すように、実際のウェブサーバーは HEAD レスポンスに対して以下の3つの異なるパターンでヘッダーを返します。

  1. Transfer-Encoding: chunked を持つ場合: メッセージボディがないにもかかわらず、GET リクエストでボディがチャンクエンコードされる場合に備えて、このヘッダーを返すサーバーが存在します。
  2. Content-Length を持つ場合: GET リクエストでボディの長さが固定されている場合に備えて、その長さを Content-Length ヘッダーで示すサーバーが存在します。
  3. どちらのヘッダーも持たない場合: 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-LengthTransfer-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つの有効なシナリオが存在します。

  1. Content-Length ヘッダーを持つ場合: GET リクエストで固定長のボディが返される場合に、その長さを HEAD レスポンスでも示すケース。
  2. Transfer-Encoding: chunked ヘッダーを持つ場合: GET リクエストでチャンクエンコードされたボディが返される場合に、そのエンコーディングを HEAD レスポンスでも示すケース。メッセージボディは存在しないため、実質的なチャンクデータは送信されません。
  3. どちらのヘッダーも持たない場合: 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つのファイルが変更されています。

  1. src/pkg/net/http/response_test.go:

    • respTests 変数に、HEAD リクエストに対する新しいテストケースが複数追加されています。
    • Transfer-Encoding: chunked を持つ HEAD レスポンスのテストケースが修正され、ProtoProtoMinorHTTP/1.1 に変更され、Closefalse に設定されています。
    • Content-Length を持つ HEAD レスポンス(HTTP/1.0とHTTP/1.1の両方)の新しいテストケースが追加されています。
    • Content-LengthTransfer-Encoding も持たない HEAD レスポンスの新しいテストケースが追加されています。
    • 既存のテストケースの一部で、HTTP/1.0 から HTTP/1.1 へのプロトコルバージョン変更が行われています。
  2. src/pkg/net/http/transfer.go:

    • newTransferWriter 関数内の if t.ResponseToHEAD ブロックが変更されています。
      • 以前は t.TransferEncoding = nilif 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-LengthTransfer-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 = nilErrMissingContentLength のチェックが削除されました。代わりに、if chunked(t.TransferEncoding) { t.ContentLength = -1 } が追加されました。これは、もしレスポンスに Transfer-Encoding: chunked が含まれている場合、Content-Length は不明である(-1)とマークすべきであることを意味します。これにより、HEAD レスポンスであっても Transfer-Encoding の情報が保持され、Content-Length の有無に柔軟に対応できるようになります。
  • shouldSendContentLength 関数:

    • この関数は、HTTPレスポンスを送信する際に Content-Length ヘッダーを含めるべきかどうかを決定します。
    • 以前のコードでは、if t.ResponseToHEAD { return true } という行があり、HEAD リクエストに対するレスポンスであれば無条件に Content-Length を送信すべきだと判断していました。これは、HEAD レスポンスには常に Content-Length があるという誤った仮定を強化していました。
    • 変更後: この行が削除されました。これにより、HEAD レスポンスであること自体が Content-Length 送信の決定要因ではなくなり、他の条件(例えば、ボディの長さが既知であるかなど)に基づいて判断されるようになります。
  • fixTransferEncoding 関数:

    • この関数は、レスポンスヘッダーから Transfer-Encoding を処理し、適切なエンコーディングを決定します。
    • 以前のコードでは、if requestMethod == "HEAD" { return nil, nil } というブロックがあり、HEAD リクエストの場合には Transfer-Encoding を無条件に無視していました。
    • 変更後: このブロックが削除されました。これにより、HEAD リクエストであっても Transfer-Encoding ヘッダーが正しく解析され、その情報がレスポンスオブジェクトに反映されるようになります。

これらの変更は、net/http パッケージがHTTP/1.1の仕様に準拠し、HEAD リクエストに対するレスポンスの多様な形式を正確に処理するための重要な修正です。これにより、Go言語のHTTPクライアントおよびサーバーの堅牢性と互換性が向上します。

関連リンク

参考にした情報源リンク