[インデックス 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.DisableCompression
が false
であり、かつリクエストヘッダーに 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
メソッドのリクエストの場合には、DisableCompression
が false
であっても、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
:Transport
のDisableCompression
フィールドが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-Encoding
、Content-Length
ヘッダーに関するセクション) - Gzip圧縮に関する一般的な情報源 (例: http://www.gzip.org/zlib/zlib_faq.html#faq38)
- Go言語の
net/http
パッケージのドキュメントとソースコード。