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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージにおいて、HEAD リクエスト時に Accept-Encoding ヘッダーを送信しないようにする変更を導入しています。これは、特定のWebサーバー(特にNginx)における既知のバグを回避するための対応です。

コミット

  • コミットハッシュ: ddda7980c52414d6de69758c588bca1ba418d95d
  • Author: Brad Fitzpatrick bradfitz@golang.org
  • Date: Tue May 21 15:21:30 2013 -0700
  • コミットメッセージ:
    net/http: don't send Accept-Encoding on HEAD requests
    
    Works around a bug in nginx: http://trac.nginx.org/nginx/ticket/358
    
    Fixes #5522
    
    R=iant
    CC=gobot, golang-dev
    https://golang.org/cl/9627043
    

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ddda7980c52414d6de69758c588bca1ba418d95d

元コミット内容

net/http: don't send Accept-Encoding on HEAD requests

Works around a bug in nginx: http://trac.nginx.org/nginx/ticket/358

Fixes #5522

R=iant
CC=gobot, golang-dev
https://golang.org/cl/9627043

変更の背景

この変更の主な背景は、Nginxウェブサーバーの特定のバージョンに存在したバグを回避することです。具体的には、Nginxのチケット #358 (http://trac.nginx.org/nginx/ticket/358) で報告されている問題で、HEAD リクエストに対して Accept-Encoding ヘッダーが送信された場合に、Nginxが誤った Content-Length ヘッダーを返すという挙動がありました。

HEAD リクエストは、リソースのヘッダー情報のみを取得するために使用され、通常はレスポンスボディを含みません。しかし、Nginxのバグにより、Accept-Encoding: gzip のようなヘッダーが存在すると、Nginxは圧縮後のボディサイズを想定した Content-Length を返してしまうことがありました。これにより、クライアント側(Goの net/http クライアント)は、実際には存在しないボディを読み込もうとしてハングアップしたり、エラーになったりする可能性がありました。

Goの net/http パッケージは、デフォルトでHTTP圧縮(gzip)を要求するために Accept-Encoding: gzip ヘッダーを自動的に追加する動作を持っていました。この自動的なヘッダー追加がNginxのバグをトリガーしていたため、HEAD リクエストの場合に限り、このヘッダーの自動追加を抑制することで、Nginxのバグによる問題を回避することが目的とされました。これはGoのIssue #5522 (http://golang.org/issue/5522) で追跡されていました。

前提知識の解説

HTTP HEAD メソッド

HTTP HEAD メソッドは、GET メソッドとほぼ同じですが、サーバーからのレスポンスにメッセージボディが含まれない点が異なります。主に、リソースのメタデータ(例: Content-Type, Content-Length, Last-Modified など)を取得したり、リソースが存在するかどうかを確認したりするために使用されます。帯域幅を節約し、不要なデータ転送を避けるのに役立ちます。

Accept-Encoding ヘッダー

Accept-Encoding HTTPヘッダーは、クライアントが理解できるコンテンツエンコーディング(圧縮アルゴリズム)を指定するために使用されます。最も一般的な値は gzip で、これはGzip圧縮をサポートしていることを示します。サーバーは、このヘッダーを見て、クライアントがサポートするエンコーディングでレスポンスボディを圧縮して送信するかどうかを決定します。

HTTP圧縮 (Gzip)

HTTP圧縮は、ウェブサーバーがレスポンスボディを圧縮してクライアントに送信することで、データ転送量を削減し、ウェブページのロード時間を短縮する技術です。Gzipは最も広く使用されている圧縮アルゴリズムの一つです。クライアントは Accept-Encoding ヘッダーで圧縮を要求し、サーバーは Content-Encoding ヘッダーで実際に使用した圧縮方式を通知します。

Content-Length ヘッダー

Content-Length HTTPヘッダーは、メッセージボディのオクテット単位のサイズを示します。これは、クライアントがレスポンスボディの終わりを判断するために使用されます。圧縮されたレスポンスの場合、この値は圧縮後のボディサイズを示します。

Nginx

Nginx (engine-x) は、高性能なHTTPおよびリバースプロキシサーバー、メールプロキシサーバー、汎用TCP/UDPプロキシサーバーです。静的コンテンツの配信、ロードバランシング、キャッシュ、APIゲートウェイなど、様々な用途で広く利用されています。このコミットの背景にあるバグは、Nginxの特定のバージョンにおける挙動の不整合に起因していました。

Go言語の net/http パッケージ

Go言語の標準ライブラリである net/http パッケージは、HTTPクライアントとサーバーの実装を提供します。このパッケージを使用することで、簡単にHTTPリクエストを送信したり、HTTPサーバーを構築したりすることができます。クライアント側では、http.Client 構造体を通じてリクエストを送信し、http.Transport が実際のネットワーク通信を担当します。Transport は、接続の再利用、プロキシ設定、TLS設定、そしてこのコミットで変更された圧縮の自動処理など、低レベルのHTTP通信の詳細を管理します。

技術的詳細

このコミットは、Goの net/http パッケージ内の transport.go ファイルと transport_test.go ファイルに変更を加えています。

src/pkg/net/http/transport.go の変更

transport.go ファイルは、HTTPトランスポート層の実装を含んでいます。特に、(*persistConn).roundTrip メソッドは、実際のHTTPリクエストを送信し、レスポンスを受信するロジックを処理します。

変更前は、pc.t.DisableCompressionfalse であり、かつリクエストヘッダーに Accept-Encoding が設定されていない場合に、自動的に Accept-Encoding: gzip ヘッダーを追加していました。

// Before:
if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" {
    requestedGzip = true
    req.extraHeaders().Set("Accept-Encoding", "gzip")
}

このコミットでは、この条件に req.Method != "HEAD" という条件が追加されました。

// After:
if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" && req.Method != "HEAD" {
    // Request gzip only, not deflate. Deflate is ambiguous and
    // not as universally supported anyway.
    // See: http://www.gzip.org/zlib/zlib_faq.html#faq38
    //
    // Note that we don't request this for HEAD requests,
    // due to a bug in nginx:
    //   http://trac.nginx.org/nginx/ticket/358
    //   http://golang.org/issue/5522
    requestedGzip = true
    req.extraHeaders().Set("Accept-Encoding", "gzip")
}

これにより、HEAD メソッドのリクエストの場合には、DisableCompressionfalse であっても、Accept-Encoding: gzip ヘッダーが自動的に追加されなくなります。コメントも追加され、NginxのバグとGoの関連Issueが明記されています。

src/pkg/net/http/transport_test.go の変更

transport_test.go ファイルは、net/http パッケージのトランスポート層のテストを含んでいます。このコミットでは、TestTransportGzip テスト関数が修正され、HEAD リクエストに対する Accept-Encoding ヘッダーの挙動が正しくテストされるようになりました。

変更前は、HEAD リクエストの場合に単にリターンするだけのロジックがありました。

// Before:
if req.Method == "HEAD" {
    return
}

変更後、HEAD リクエストの場合に Accept-Encoding ヘッダーが送信されていないことを確認するアサーションが追加されました。

// After:
if req.Method == "HEAD" {
    if g := req.Header.Get("Accept-Encoding"); g != "" {
        t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g)
    }
    return
}

このテストの追加により、HEAD リクエスト時に Accept-Encoding ヘッダーが意図せず送信されていないことが保証されるようになりました。

コアとなるコードの変更箇所

src/pkg/net/http/transport.go

--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -831,10 +831,15 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err
 	// uncompress the gzip stream if we were the layer that
 	// requested it.
 	requestedGzip := false
-	if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" {
+	if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" && req.Method != "HEAD" {
 		// Request gzip only, not deflate. Deflate is ambiguous and
 		// not as universally supported anyway.
 		// See: http://www.gzip.org/zlib/zlib_faq.html#faq38
+		//
+		// Note that we don't request this for HEAD requests,
+		// due to a bug in nginx:
+		//   http://trac.nginx.org/nginx/ticket/358
+		//   http://golang.org/issue/5522
 		requestedGzip = true
 		req.extraHeaders().Set("Accept-Encoding", "gzip")
 	}

src/pkg/net/http/transport_test.go

--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -585,13 +585,16 @@ func TestTransportGzip(t *testing.T) {
 	const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
 	const nRandBytes = 1024 * 1024
 	ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
+		if req.Method == "HEAD" {
+			if g := req.Header.Get("Accept-Encoding"); g != "" {
+				t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g)
+			}
+			return
+		}
 		if g, e := req.Header.Get("Accept-Encoding"), "gzip"; g != e {
 			t.Errorf("Accept-Encoding = %q, want %q", g, e)
 		}
 		rw.Header().Set("Content-Encoding", "gzip")
-		if req.Method == "HEAD" {
-			return
-		}
 
 		var w io.Writer = rw
 		var buf bytes.Buffer

コアとなるコードの解説

src/pkg/net/http/transport.go の変更解説

この変更は、Accept-Encoding: gzip ヘッダーをリクエストに自動的に追加する条件に req.Method != "HEAD" を追加することで、HEAD リクエストの場合にはこの自動追加を行わないようにしています。

  • !pc.t.DisableCompression: TransportDisableCompression フィールドが false であることを確認します。これは、クライアントが圧縮を許可していることを意味します。
  • req.Header.Get("Accept-Encoding") == "": リクエストヘッダーに Accept-Encoding がまだ設定されていないことを確認します。これにより、ユーザーが明示的に Accept-Encoding を設定している場合には、Goが上書きしないようにします。
  • req.Method != "HEAD": このコミットで追加された最も重要な条件です。 これにより、リクエストメソッドが HEAD でない場合にのみ、Accept-Encoding: gzip ヘッダーが追加されるようになります。

この変更により、GoのHTTPクライアントは、Nginxのバグをトリガーする可能性のある HEAD リクエストでの Accept-Encoding ヘッダーの送信を停止し、より堅牢な動作を実現します。

src/pkg/net/http/transport_test.go の変更解説

このテストの変更は、HEAD リクエスト時に Accept-Encoding ヘッダーが送信されないことを検証するためのものです。

  • if req.Method == "HEAD": サーバー側で受け取ったリクエストが HEAD メソッドである場合に、以下の検証ロジックを実行します。
  • if g := req.Header.Get("Accept-Encoding"); g != "": 受け取った HEAD リクエストの Accept-Encoding ヘッダーの値を取得し、それが空文字列でない(つまり、何らかの値が設定されている)場合にエラーを報告します。
  • t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g): エラーメッセージを出力し、テストを失敗させます。これは、HEAD リクエストでは Accept-Encoding が送信されるべきではないという期待に反する挙動があったことを示します。

このテストの追加により、transport.go で行われた変更が正しく機能していることが保証され、将来的に同様の回帰バグが発生するのを防ぐことができます。

関連リンク

  • Nginx Trac Ticket #358: http://trac.nginx.org/nginx/ticket/358
  • Go Issue #5522: http://golang.org/issue/5522
  • Go CL 9627043: https://golang.org/cl/9627043

参考にした情報源リンク

  • 上記の「関連リンク」に記載されているNginxのチケット、GoのIssue、およびGoの変更リスト(CL)。
  • HTTP/1.1 RFC 2616 (特に HEAD メソッド、Accept-EncodingContent-Length ヘッダーに関するセクション)
  • Gzip圧縮に関する一般的な情報源 (例: http://www.gzip.org/zlib/zlib_faq.html#faq38)
  • Go言語の net/http パッケージのドキュメントとソースコード。