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

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

このコミットは、Go言語のnet/httpパッケージにおけるHEADリクエストの挙動に関する修正です。具体的には、HEADリクエストに対してデフォルトでContent-Typeヘッダーが設定されないように変更し、本来のコンテンツタイプが推論されない問題を解決しています。

コミット

commit fb86bbe2397453aaf793ec00a7233b858f17bd2c
Author: Patrick Mylund Nielsen <patrick@patrickmn.com>
Date:   Mon Feb 6 17:55:47 2012 +1100

    net/http: Don't set Content-Type header for HEAD requests by default
    since the real type is not inferred.
    Fixes #2885.
    
    R=golang-dev, dsymonds, bradfitz
    CC=golang-dev
    https://golang.org/cl/5633045
---
 src/pkg/net/http/serve_test.go | 9 +++++++--
 src/pkg/net/http/server.go     | 2 +-\n 2 files changed, 8 insertions(+), 3 deletions(-)

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

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

元コミット内容

net/http: Don't set Content-Type header for HEAD requests by default
since the real type is not inferred.
Fixes #2885.

変更の背景

この変更は、Go言語のnet/httpパッケージがHTTPのHEADリクエストを処理する際の、Content-Typeヘッダーの扱いに起因する問題(Issue #2885)を修正するために行われました。

HTTPのHEADリクエストは、GETリクエストと同様にリソースのヘッダー情報を取得しますが、レスポンスボディは含みません。net/httpパッケージでは、レスポンスのContent-Typeヘッダーを決定するために、レスポンスボディの内容を「スニッフィング(推論)」するメカニズムが組み込まれていました。しかし、HEADリクエストにはボディがないため、このスニッフィング処理が正しく機能せず、結果としてContent-Typeヘッダーが誤って設定されたり、全く設定されなかったりする問題が発生していました。

この問題は、クライアントがHEADリクエストによってリソースのタイプを事前に知ることができず、その後のGETリクエストや他の処理に影響を与える可能性がありました。例えば、ファイルダウンロードの前にファイルタイプを確認したい場合などに不都合が生じます。

このコミットは、HEADリクエストの場合にはContent-Typeの自動推論を行わないようにすることで、この問題を回避し、より正確なHTTPヘッダーの挙動を実現することを目的としています。

前提知識の解説

HTTP HEADリクエスト

HTTPのHEADメソッドは、GETメソッドとほぼ同じですが、サーバーからのレスポンスにメッセージボディが含まれない点が異なります。サーバーは、GETリクエストに対するレスポンスと同じヘッダーを返しますが、ボディは送信しません。 HEADリクエストの主な用途は以下の通りです。

  • リソースの存在確認(ステータスコードの確認)
  • リソースのメタデータ(Content-Type, Content-Lengthなど)の取得
  • リソースが更新されたかどうかの確認(Last-Modifiedヘッダーなどを使用)

Content-Typeヘッダー

Content-Typeヘッダーは、HTTPレスポンスのメッセージボディに含まれるデータのメディアタイプ(MIMEタイプ)を示すために使用されます。例えば、text/htmlはHTMLドキュメント、application/jsonはJSONデータ、image/jpegはJPEG画像を示します。クライアントは、このヘッダーを見て、受信したデータをどのように解釈し、表示すべきかを判断します。

コンテンツスニッフィング (Content Sniffing)

コンテンツスニッフィングとは、HTTPレスポンスにContent-Typeヘッダーが明示的に指定されていない場合や、指定されたヘッダーが疑わしい場合に、ブラウザや他のクライアントがレスポンスボディの最初の数バイトを検査して、データの実際のタイプを推測するプロセスです。これは、サーバーが誤ったContent-Typeを送信したり、全く送信しなかったりした場合でも、コンテンツを正しく表示するために役立ちます。しかし、セキュリティ上のリスク(例: スクリプトインジェクション)を引き起こす可能性もあるため、通常はサーバー側で正確なContent-Typeを設定することが推奨されます。

Goのnet/httpパッケージでは、レスポンスライターがContent-Typeヘッダーを明示的に設定しない場合、Writeメソッドが呼び出された際に、書き込まれるデータの最初の数バイトを検査してContent-Typeを自動的に推測し、ヘッダーとして追加する機能がありました。

技術的詳細

Goのnet/httpパッケージのresponse構造体には、WriteHeaderメソッドとWriteメソッドがあります。通常、Content-Typeヘッダーは、Writeメソッドが呼び出された際に、書き込まれるデータの内容に基づいて自動的に推測され、設定されます。この推測処理は、w.needSniff = trueというフラグによって制御されていました。

問題は、HEADリクエストの場合、レスポンスボディが送信されないため、Writeメソッドが呼び出されず、結果としてContent-Typeの自動推測が行われない点にありました。しかし、以前の実装では、Content-Typeヘッダーが明示的に設定されていない場合に、無条件にneedSniffフラグをtrueに設定していました。これにより、HEADリクエストであってもContent-Typeヘッダーが設定されるべきだとシステムが「期待」してしまい、実際にはボディがないため推測できず、結果として不正確な、あるいは欠落したContent-Typeヘッダーが返されることがありました。

このコミットでは、server.go内のresponse.WriteHeaderメソッドにおいて、Content-Typeヘッダーの自動推測を行う条件に、リクエストメソッドがHEADではないこと(w.req.Method != "HEAD")を追加しました。これにより、HEADリクエストの場合には、Content-Typeヘッダーが明示的に設定されていない場合でも、コンテンツスニッフィングを行わないように変更されました。

また、serve_test.goには、HEADリクエストに対するレスポンスにContent-Typeヘッダーが含まれないことを検証するテストケースが追加されました。これは、この変更が意図した通りに機能していることを確認するためです。

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

diff --git a/src/pkg/net/http/serve_test.go b/src/pkg/net/http/serve_test.go
index 147c216ec7..e2860c3edc 100644
--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -504,8 +504,9 @@ func Test304Responses(t *testing.T) {
 }
 
 // TestHeadResponses verifies that responses to HEAD requests don't
-// declare that they're chunking in their response headers and aren't
-// allowed to produce output.\n+// declare that they're chunking in their response headers, aren't
+// allowed to produce output, and don't set a Content-Type since
+// the real type of the body data cannot be inferred.
 func TestHeadResponses(t *testing.T) {
  	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
  		_, err := w.Write([]byte("Ignored body"))
@@ -527,6 +528,10 @@ func TestHeadResponses(t *testing.T) {
  	if len(res.TransferEncoding) > 0 {
  		t.Errorf("expected no TransferEncoding; got %v", res.TransferEncoding)
  	}\n+\tct := res.Header.Get("Content-Type")
+\tif ct != "" {
+\t\tt.Errorf("expected no Content-Type; got %s", ct)
+\t}\n  	body, err := ioutil.ReadAll(res.Body)
  	if err != nil {
  		t.Error(err)
diff --git a/src/pkg/net/http/server.go b/src/pkg/net/http/server.go
index dea75b1dfd..288539ba57 100644
--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -341,7 +341,7 @@ func (w *response) WriteHeader(code int) {
  		}\n  	} else {
  		// If no content type, apply sniffing algorithm to body.
-\t\tif w.header.Get("Content-Type") == "" {\n+\t\tif w.header.Get("Content-Type") == "" && w.req.Method != "HEAD" {\n \t\t\tw.needSniff = true
  		}\n  	}

コアとなるコードの解説

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

 // If no content type, apply sniffing algorithm to body.
- if w.header.Get("Content-Type") == "" {
+ if w.header.Get("Content-Type") == "" && w.req.Method != "HEAD" {
  	w.needSniff = true
 }

この変更がこのコミットの核心です。response.WriteHeaderメソッド内で、Content-Typeヘッダーがまだ設定されていない場合に、ボディのコンテンツスニッフィングを行うかどうかを決定するロジックが修正されました。 変更前は、Content-Typeヘッダーが空であれば無条件にw.needSniff = trueとしていましたが、変更後はw.req.Method != "HEAD"という条件が追加されました。 これにより、リクエストメソッドがHEADである場合には、Content-Typeヘッダーが空であってもneedSniffフラグはtrueに設定されなくなります。結果として、HEADリクエストに対してはボディからのContent-Type推論が行われなくなり、不正確なContent-Typeが返されることを防ぎます。

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

 // TestHeadResponses verifies that responses to HEAD requests don't
-// declare that they're chunking in their response headers and aren't
-// allowed to produce output.
+// declare that they're chunking in their response headers, aren't
+// allowed to produce output, and don't set a Content-Type since
+// the real type of the body data cannot be inferred.
 func TestHeadResponses(t *testing.T) {
     // ... (既存のテストコード) ...
 	ct := res.Header.Get("Content-Type")
 	if ct != "" {
 		t.Errorf("expected no Content-Type; got %s", ct)
 	}
     // ... (既存のテストコード) ...
 }

TestHeadResponsesというテスト関数が修正され、HEADリクエストに対するレスポンスヘッダーにContent-Typeが含まれていないことを検証するアサーションが追加されました。 具体的には、res.Header.Get("Content-Type")Content-Typeヘッダーの値を取得し、それが空文字列("")でない場合にエラーを報告します。これは、HEADリクエストではボディがないため、Content-Typeが推論されるべきではないという新しい挙動をテストするためのものです。

関連リンク

参考にした情報源リンク