[インデックス 14629] ファイルの概要
このドキュメントは、Go言語の標準ライブラリ net/http
パッケージにおけるコミット f85b94aa85e6dffca51979538a9b4f0d96a3a645
の詳細な技術解説を提供します。このコミットは、http.Client
がリクエストを送信する際に、リクエストURLの Host
フィールドが空である場合に、より分かりやすいエラーを返すように改善するものです。
コミット
commit f85b94aa85e6dffca51979538a9b4f0d96a3a645
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Dec 12 12:23:01 2012 -0800
net/http: return nicer error when Client request Host is blank
Update #4271
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6931052
---
src/pkg/net/http/transport.go | 3 +++
src/pkg/net/http/transport_test.go | 14 ++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/src/pkg/net/http/transport.go b/src/pkg/net/http/transport.go
index 7b4afeb8ef..1dd5cc5308 100644
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -144,6 +144,9 @@ func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {
}\n \t\treturn rt.RoundTrip(req)\n \t}\n+\tif req.URL.Host == \"\" {\n+\t\treturn nil, errors.New(\"http: no Host in request URL\")\n+\t}\n \ttreq := &transportRequest{Request: req}\n \tcm, err := t.connectMethodForRequest(treq)\n \tif err != nil {\ndiff --git a/src/pkg/net/http/transport_test.go b/src/pkg/net/http/transport_test.go
index f1d415888c..4647d20fb3 100644
--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -1062,6 +1062,20 @@ func TestTransportAltProto(t *testing.T) {\n \t}\n }\n \n+func TestTransportNoHost(t *testing.T) {\n+\ttr := &Transport{}\n+\t_, err := tr.RoundTrip(&Request{\n+\t\tHeader: make(Header),\n+\t\tURL: &url.URL{\n+\t\t\tScheme: \"http\",\n+\t\t},\n+\t})\n+\twant := \"http: no Host in request URL\"\n+\tif got := fmt.Sprint(err); got != want {\n+\t\tt.Errorf(\"error = %v; want %q\", err, want)\n+\t}\n+}\n+\n var proxyFromEnvTests = []struct {\n \tenv string\n \twanturl string\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/f85b94aa85e6dffca51979538a9b4f0d96a3a645](https://github.com/golang/go/commit/f85b94aa85e6dffca51979538a9b4f0d96a3a645)
## 元コミット内容
`net/http: return nicer error when Client request Host is blank`
このコミットは、`http.Client` がリクエストを処理する際に、リクエストの `URL.Host` フィールドが空である場合に、より明確なエラーメッセージを返すように変更します。
## 変更の背景
HTTPリクエストにおいて、`Host` ヘッダは非常に重要な要素です。これは、クライアントがどのサーバーにリクエストを送信しているか、特に単一のIPアドレスで複数のドメインをホストしているバーチャルホスト環境において、サーバーが適切なコンテンツを返すために必要となります。
Go言語の `net/http` パッケージでは、`http.Request` 構造体の中に `URL` フィールドがあり、その `URL` 構造体の中に `Host` フィールドが存在します。通常、`http.NewRequest` などを使用してリクエストを作成する場合、URLが適切にパースされ、`Host` フィールドも自動的に設定されます。しかし、開発者が手動で `http.Request` を構築したり、URLのパースに問題があったり、あるいは意図的に `Host` フィールドを空にした場合、`http.Client` がリクエストを送信しようとした際に、予期せぬ動作や不明瞭なエラーが発生する可能性がありました。
このコミットが行われる前は、`Host` フィールドが空のリクエストが `http.Transport` の `RoundTrip` メソッドに渡された場合、内部的な処理の途中でエラーが発生し、そのエラーメッセージが必ずしも「`Host` がない」ことを明確に示しているとは限りませんでした。これにより、デバッグが困難になる可能性がありました。
`Update #4271` という記述は、この変更が特定のGoのIssue(問題報告)に対応するものであることを示唆しています。このIssueは、`Host` フィールドが空の場合のエラーメッセージの改善を求めるものであったと推測されます。より分かりやすいエラーメッセージを提供することで、開発者は問題の原因を迅速に特定し、修正できるようになります。
## 前提知識の解説
### Go言語の `net/http` パッケージ
`net/http` パッケージは、Go言語でHTTPクライアントとサーバーを実装するための標準ライブラリです。
* **`http.Client`**: HTTPリクエストを送信するためのクライアントです。通常、`http.Client` の `Do` メソッドを使用してリクエストを送信します。
* **`http.Request`**: HTTPリクエストを表す構造体です。リクエストメソッド(GET, POSTなど)、URL、ヘッダ、ボディなどの情報を含みます。
* **`http.Response`**: HTTPレスポンスを表す構造体です。ステータスコード、ヘッダ、ボディなどの情報を含みます。
* **`http.Transport`**: `http.Client` の内部で実際にHTTPリクエストを送信する役割を担う構造体です。コネクションの再利用、プロキシ設定、TLS設定などを管理します。`RoundTrip` メソッドは、単一のHTTPトランザクション(リクエストの送信とレスポンスの受信)を実行します。
* **`url.URL`**: URLをパースして表現するための構造体です。`Scheme` (http, httpsなど), `Host` (ドメイン名やIPアドレスとポート番号), `Path` (パス), `RawQuery` (クエリパラメータ) などのフィールドを持ちます。
### HTTPの `Host` ヘッダ
HTTP/1.1では、`Host` ヘッダは必須です。クライアントは、リクエストを送信する際に、ターゲットサーバーのホスト名とオプションでポート番号をこのヘッダに含める必要があります。これは、特にバーチャルホスティング(一つのIPアドレスで複数のドメインを運用する)環境において、サーバーがどのドメイン宛のリクエストであるかを識別するために不可欠です。
例:
`GET /index.html HTTP/1.1`
`Host: www.example.com`
### エラーハンドリング
Go言語では、エラーは `error` インターフェースを実装する値として返されます。慣例として、関数は成功時には `nil` を返し、エラー発生時には非`nil`のエラー値を返します。このコミットでは、`errors.New` 関数を使用して新しいエラー文字列を作成しています。
## 技術的詳細
このコミットの技術的な変更点は、`src/pkg/net/http/transport.go` ファイル内の `Transport` 構造体の `RoundTrip` メソッドに、`req.URL.Host` が空であるかどうかのチェックを追加したことです。
変更前は、`req.URL.Host` が空の場合、`RoundTrip` メソッドの後の処理で、`Host` ヘッダの構築やコネクションの確立の段階でエラーが発生していました。これらのエラーは、例えば「アドレス解決に失敗した」といった、直接的に「`Host` がない」ことを示さないメッセージであった可能性があります。
変更後は、`RoundTrip` メソッドの冒頭で、以下のように明示的なチェックが追加されました。
```go
if req.URL.Host == "" {
return nil, errors.New("http: no Host in request URL")
}
このコードスニペットは、以下の動作を保証します。
Transport.RoundTrip
メソッドが呼び出された際、まず最初に渡されたRequest
オブジェクトのURL.Host
フィールドが空文字列であるかをチェックします。- もし
Host
フィールドが空であれば、即座にnil
レスポンスと、errors.New("http: no Host in request URL")
というエラーを返します。
この変更により、Host
フィールドが空であるという特定の問題に対して、非常に明確で診断しやすいエラーメッセージが提供されるようになりました。これにより、開発者はエラーログを見ただけで、リクエストの Host
が設定されていないことが原因であるとすぐに理解できます。
また、src/pkg/net/http/transport_test.go
には、この新しいエラーハンドリングを検証するためのテストケース TestTransportNoHost
が追加されています。このテストは、Host
フィールドが空の Request
を作成し、Transport.RoundTrip
を呼び出した際に、期待されるエラーメッセージ "http: no Host in request URL"
が返されることを確認します。これにより、将来の変更によってこの重要なエラーチェックが誤って削除されたり、動作が変わったりすることを防ぎます。
コアとなるコードの変更箇所
src/pkg/net/http/transport.go
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -144,6 +144,9 @@ func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {
}\n \t\treturn rt.RoundTrip(req)\n \t}\n+\tif req.URL.Host == "" {\n+\t\treturn nil, errors.New("http: no Host in request URL")\n+\t}\n \ttreq := &transportRequest{Request: req}\n \tcm, err := t.connectMethodForRequest(treq)\n \tif err != nil {\n```
### `src/pkg/net/http/transport_test.go`
```diff
--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -1062,6 +1062,20 @@ func TestTransportAltProto(t *testing.T) {\n \t}\n }\n \n+func TestTransportNoHost(t *testing.T) {\n+\ttr := &Transport{}\n+\t_, err := tr.RoundTrip(&Request{\n+\t\tHeader: make(Header),\n+\t\tURL: &url.URL{\n+\t\t\tScheme: "http",\n+\t\t},\n+\t})\n+\twant := "http: no Host in request URL"\n+\tif got := fmt.Sprint(err); got != want {\n+\t\tt.Errorf("error = %v; want %q", err, want)\n+\t}\n+}\n+\n var proxyFromEnvTests = []struct {\n \tenv string\n \twanturl string\n```
## コアとなるコードの解説
### `transport.go` の変更
`Transport.RoundTrip` メソッドは、`http.Client` が実際にHTTPリクエストをネットワーク経由で送信する際の中心的なロジックを含んでいます。このメソッドの冒頭に `if req.URL.Host == ""` という条件分岐が追加されました。
* `req.URL.Host == ""`:これは、HTTPリクエストのURL構造体内の `Host` フィールドが空文字列であるかどうかをチェックしています。`Host` フィールドは、通常、`http://example.com:8080` の `example.com:8080` の部分に相当します。これが空であるということは、リクエストがどのホストに送られるべきか不明確であることを意味します。
* `return nil, errors.New("http: no Host in request URL")`:もし `Host` フィールドが空であれば、この行が実行されます。
* `nil` レスポンス:有効なHTTPレスポンスは得られないため、レスポンスは `nil` となります。
* `errors.New("http: no Host in request URL")`:`errors.New` 関数は、指定された文字列をエラーメッセージとする新しいエラー値を生成します。このエラーメッセージは、問題が「リクエストURLにHostがない」ことであることを明確に示しています。
この変更により、`Host` がないという特定のエラーケースが、より一般的なネットワークエラーやプロトコルエラーとして扱われる前に捕捉され、より具体的なエラーメッセージが返されるようになりました。これは、エラーの診断とデバッグの効率を大幅に向上させます。
### `transport_test.go` の変更
`TestTransportNoHost` という新しいテスト関数が追加されました。
* `tr := &Transport{}`:新しい `http.Transport` インスタンスを作成します。
* `_, err := tr.RoundTrip(&Request{...})`:`RoundTrip` メソッドを呼び出します。ここで渡される `Request` オブジェクトは、意図的に `URL.Host` フィールドを空にしています。
* `URL: &url.URL{Scheme: "http"}`:`url.URL` 構造体を初期化する際に、`Scheme` は `"http"` と設定されていますが、`Host` フィールドは明示的に設定されていないため、そのデフォルト値である空文字列が使用されます。
* `want := "http: no Host in request URL"`:期待されるエラーメッセージを定義します。これは、`transport.go` で返されるエラーメッセージと完全に一致します。
* `if got := fmt.Sprint(err); got != want { ... }`:`RoundTrip` から返されたエラー `err` を文字列に変換し (`fmt.Sprint(err)`)、それが期待されるエラーメッセージ `want` と一致するかどうかを検証します。一致しない場合は、テストが失敗し、詳細なエラーメッセージが出力されます。
このテストケースは、`transport.go` で実装された新しいエラーハンドリングが正しく機能していることを保証します。これにより、将来のコード変更がこの重要なエラーチェックの動作を損なうことを防ぎ、コードの堅牢性を高めます。
## 関連リンク
* Go言語 `net/http` パッケージのドキュメント: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
* Go言語 `net/url` パッケージのドキュメント: [https://pkg.go.dev/net/url](https://pkg.go.dev/net/url)
* HTTP/1.1 仕様 (RFC 2616) - Host ヘッダに関する記述: [https://www.rfc-editor.org/rfc/rfc2616#section-14.23](https://www.rfc-editor.org/rfc/rfc2616#section-14.23)
## 参考にした情報源リンク
* Go言語の公式ドキュメント
* HTTP/1.1 プロトコル仕様
* Go言語のGitHubリポジトリ (コミット履歴と関連Issue)