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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の requestwrite_test.go ファイルに対する変更です。このファイルは、HTTPリクエストの書き込み(シリアライズ)に関するテストケースを定義しており、net/http クライアントが送信するリクエストがHTTP仕様に準拠し、かつ期待される動作をすることを確認するために使用されます。

コミット

  • コミットハッシュ: cd566958e938c695d09730dfcb7c2b8e76658f89
  • Author: Brad Fitzpatrick bradfitz@golang.org
  • Date: Wed Feb 13 18:33:15 2013 -0800

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

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

元コミット内容

net/http: test that we preserve Go 1.0 Request.Write Host behavior

Fixes #4792

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7314093

変更の背景

このコミットの主な目的は、Go 1.0における net/http.Request.Write メソッドの Host ヘッダーに関する既存の動作を、将来のバージョンでも維持することを保証するためのテストを追加することです。コミットメッセージにある Fixes #4792 は、Goプロジェクトのイシュートラッカーにおける特定のバグまたは振る舞いに関する議論に対応していることを示唆しています。

具体的には、Request.Host フィールドが空文字列であり、かつ Request.URL.Host も空である場合に、Host ヘッダーがどのように生成されるかという点に焦点が当てられています。Go 1.0では、この特定の条件下で Host ヘッダーが空文字列として送信されるという動作がありました。このコミットは、この動作が意図されたものであり、後方互換性を保つために変更されないことをテストによって保証しようとしています。

Request.Header["Host"] に値が設定されている場合でも、Request.HostRequest.URL.Host が空であれば、Request.Header["Host"] の値が無視され、空の Host ヘッダーが送信されるという、一見すると直感に反するかもしれないGo 1.0の振る舞いを固定化することが目的です。これは、HTTPリクエストの Host ヘッダーの決定ロジックが複雑であり、特に Request 構造体の複数のフィールド(HostURL.HostHeader["Host"])が相互に作用するため、このようなテストが重要になります。

前提知識の解説

HTTP Host ヘッダー

HTTP/1.1では、Host ヘッダーは必須であり、リクエストされたリソースのインターネットホストとポート番号を指定します。これは、単一のIPアドレスで複数のウェブサイト(バーチャルホスト)をホストするサーバーにおいて、どのウェブサイトへのリクエストであるかを識別するために不可欠です。

例: Host: example.com または Host: example.com:8080

Goの net/http パッケージ

net/http パッケージは、Go言語でHTTPクライアントとサーバーを実装するための基本的な機能を提供します。

  • http.Request 構造体: HTTPリクエストを表す構造体です。この構造体には、リクエストメソッド(GET, POSTなど)、URL、ヘッダー、ボディなどの情報が含まれます。
    • Request.Host フィールド: このフィールドは、リクエストの Host ヘッダーの値を明示的に設定するために使用されます。このフィールドが設定されている場合、Request.URL.Host よりも優先されます。
    • Request.URL フィールド: リクエストのURLを表す url.URL 型の構造体です。
      • url.URL.Host フィールド: URLから解析されたホスト名とポート番号が含まれます。Request.Host が設定されていない場合、この値が Host ヘッダーのデフォルト値として使用されます。
    • Request.Header フィールド: リクエストヘッダーを表す http.Header 型のマップです。Host ヘッダーは通常、Request.Host または Request.URL.Host から自動的に生成されますが、Request.Header["Host"] を介して手動で設定することも可能です。ただし、Request.Host が設定されている場合は、Request.Header["Host"] よりも優先されます。

Goにおける後方互換性

Go言語は、後方互換性を非常に重視しています。これは、既存のGoプログラムが新しいバージョンのGoコンパイラやライブラリで引き続き動作することを意味します。APIの変更は慎重に行われ、既存の動作を変更する場合は、その影響を最小限に抑えるように努めます。このコミットのように、特定の振る舞いをテストで固定化することは、将来の変更が意図せず既存の動作を壊すことを防ぐための重要な手段です。

技術的詳細

このコミットで追加されたテストケースは、net/http パッケージがHTTPリクエストを書き出す際の Host ヘッダーの生成ロジックの特定のコーナーケースを扱っています。

通常、http.RequestWrite メソッドでバイトストリームに変換する際、Host ヘッダーは以下の優先順位で決定されます。

  1. Request.Host フィールドが設定されている場合、その値が使用されます。
  2. Request.Host が設定されておらず、Request.URL.Host が設定されている場合、その値が使用されます。
  3. 上記いずれも設定されていない場合、Request.Header["Host"] の値が使用されることがあります(ただし、これは推奨される方法ではありません)。

しかし、Go 1.0の特定の振る舞いとして、Request.Host が明示的に空文字列 ("") に設定され、かつ Request.URL.Host も空文字列である場合、たとえ Request.Header["Host"] に値が設定されていても、Host ヘッダーは空文字列として送信されるという動作がありました。

このコミットは、この「Go 1.0の動作」を明示的にテストすることで、将来の変更がこの特定の振る舞いを意図せず変更しないことを保証します。これは、既存のGoアプリケーションがこの振る舞いに依存している可能性があるため、後方互換性を維持するために重要です。

テストケースでは、以下の条件が設定されています。

  • Request.Method: "GET"
  • Request.Host: "" (空文字列)
  • Request.URL.Scheme: "http"
  • Request.URL.Host: "" (空文字列)
  • Request.URL.Path: "/search"
  • Request.ProtoMajor: 1, Request.ProtoMinor: 1 (HTTP/1.1)
  • Request.Header: Header{"Host": []string{"bad.example.com"}} (Host ヘッダーに「bad.example.com」という値が設定されている)

この設定にもかかわらず、期待される出力 (WantWrite) では Host: (空のHostヘッダー) が含まれています。これは、Request.Host が明示的に空文字列に設定されている場合、Request.Header["Host"] の値が無視されるというGo 1.0の動作を検証しています。

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

src/pkg/net/http/requestwrite_test.go ファイルに以下のテストケースが追加されました。

--- a/src/pkg/net/http/requestwrite_test.go
+++ b/src/pkg/net/http/requestwrite_test.go
@@ -328,6 +328,31 @@ var reqWriteTests = []reqWriteTest{\
 			"User-Agent: Go http package\r\n" +\
 			"X-Foo: X-Bar\r\n\r\n",
 	},
+
+	// If no Request.Host and no Request.URL.Host, we send
+	// an empty Host header, and don't use
+	// Request.Header["Host"]. This is just testing that
+	// we don't change Go 1.0 behavior.
+	{
+		Req: Request{
+			Method: "GET",
+			Host:   "",
+			URL: &url.URL{
+				Scheme: "http",
+				Host:   "",
+				Path:   "/search",
+			},
+			ProtoMajor: 1,
+			ProtoMinor: 1,
+			Header: Header{
+				"Host": []string{"bad.example.com"},
+			},
+		},
+
+		WantWrite: "GET /search HTTP/1.1\r\n" +\
+			"Host: \r\n" +\
+			"User-Agent: Go http package\r\n\r\n",
+	},
 }
 
 func TestRequestWrite(t *testing.T) {

コアとなるコードの解説

追加されたテストケースは reqWriteTests スライスの一部として定義されています。reqWriteTests は、http.Request オブジェクトと、そのオブジェクトが Write メソッドによってシリアライズされた場合に期待されるHTTPリクエストの生文字列 (WantWrite) のペアを格納するスライスです。

この新しいテストケースは、以下の Request 構造体で構成されています。

  • Method: "GET": HTTP GETメソッドを指定します。
  • Host: "": Request 構造体の Host フィールドを明示的に空文字列に設定します。これがこのテストケースの核心です。
  • URL: &url.URL{Scheme: "http", Host: "", Path: "/search"}: URLのスキームを "http"、ホストを空文字列、パスを "/search" に設定します。URL.Host も空である点が重要です。
  • ProtoMajor: 1, ProtoMinor: 1: HTTP/1.1プロトコルバージョンを指定します。
  • Header: Header{"Host": []string{"bad.example.com"}}: Request.Header マップに Host ヘッダーを「bad.example.com」という値で追加します。

WantWrite フィールドには、この Request オブジェクトが Write メソッドによってシリアライズされた場合に期待されるHTTPリクエストの生文字列が記述されています。注目すべきは、Host: \r\n の部分です。これは、Request.HostRequest.URL.Host が両方とも空である場合、Request.Header["Host"] に値が設定されていても、Host ヘッダーが空文字列として出力されるというGo 1.0の動作を正確に反映しています。

TestRequestWrite 関数は、この reqWriteTests スライスをイテレートし、各テストケースの Req オブジェクトを Write メソッドでシリアライズし、その結果が WantWrite と一致するかどうかを検証します。これにより、Go 1.0の Host ヘッダーの振る舞いが将来のバージョンでも維持されることが保証されます。

関連リンク

参考にした情報源リンク