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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の ProxyFromEnvironment 関数に関するバグ修正と、それに関連するドキュメントの改善、およびテストの追加を行っています。具体的には、src/pkg/net/http/transport.gosrc/pkg/net/http/transport_test.go の2つのファイルが変更されています。

コミット

commit f5df930618a65c1b8ef9e798e679a618301fdbe9
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Feb 29 09:52:52 2012 -0800

    net/http: fix ProxyFromEnvironment bug, docs, add tests
    
    Fixes #2919 I believe. (gets as far as sending a CONNECT
    request to my little dummy logging proxy that doesn't actually
    support CONNECT now.)  Untested with a real CONNECT-supporting
    proxy, though.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5708055

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

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

元コミット内容

net/http: ProxyFromEnvironment のバグを修正し、ドキュメントを更新し、テストを追加。

これは #2919 を修正していると思われます。(CONNECTリクエストを、実際にはCONNECTをサポートしていないダミーのロギングプロキシに送信するところまでは到達します。)ただし、実際のCONNECTをサポートするプロキシでのテストは行われていません。

変更の背景

このコミットは、Go言語のIssue #2919 に関連するバグを修正するために行われました。Issue #2919 は、ProxyFromEnvironment 関数が環境変数 HTTP_PROXY からプロキシURLを解析する際に、スキーム(http://https:// など)が指定されていない場合に正しく処理できないという問題でした。

具体的には、HTTP_PROXY 環境変数に 127.0.0.1:8080 のようにスキームなしでホストとポートのみが指定された場合、url.Parse 関数はエラーを返さず、結果として proxyURL.Scheme が空文字列になります。この状態では、net/http パッケージがプロキシを正しく利用できず、HTTPリクエストが失敗したり、意図しない動作をしたりする可能性がありました。

このコミットは、このスキームが欠落しているケースを適切にハンドリングし、http:// スキームを補完することで、より堅牢なプロキシ設定の解釈を実現することを目的としています。

前提知識の解説

1. HTTPプロキシと環境変数 (HTTP_PROXY, NO_PROXY)

  • HTTPプロキシ: クライアントとサーバーの間に入って通信を中継するサーバーです。セキュリティ、キャッシュ、アクセス制御などの目的で利用されます。
  • HTTP_PROXY / http_proxy: HTTPリクエストに使用するプロキシサーバーのURLを指定するための環境変数です。通常、http://proxy.example.com:8080 のような形式で指定されます。Goの net/http パッケージを含む多くのHTTPクライアントライブラリは、この環境変数を自動的に読み取り、プロキシ設定として利用します。
  • HTTPS_PROXY / https_proxy: HTTPSリクエストに使用するプロキシサーバーのURLを指定するための環境変数です。
  • NO_PROXY / no_proxy: プロキシを使用しないホスト名のリストを指定するための環境変数です。カンマ区切りで複数のホストを指定できます。例えば、localhost,*.example.com のように指定すると、これらのホストへのリクエストではプロキシが使用されません。

2. net/http パッケージ

Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーションの構築や、外部APIとの連携など、HTTP通信を扱う上で中心的な役割を果たします。

3. url.Parse 関数

Go言語の net/url パッケージに含まれる関数で、文字列形式のURLを url.URL 構造体に解析します。この構造体には、スキーム(http, https など)、ホスト、パス、クエリパラメータなどのURLの各要素が格納されます。

4. HTTP CONNECT メソッド

HTTP/1.1のメソッドの一つで、主にHTTPS通信をプロキシ経由で行う際に使用されます。クライアントはプロキシに対して CONNECT メソッドを使って特定のホストとポートへのTCP接続の確立を要求します。プロキシが接続を確立すると、クライアントと目的のサーバー間で直接TCPストリームが確立され、プロキシは単にデータを中継するトンネルとして機能します。これにより、プロキシは暗号化されたHTTPSトラフィックの内容を解読することなく中継できます。

技術的詳細

ProxyFromEnvironment 関数は、環境変数 HTTP_PROXY (または http_proxy) の値を読み取り、それをプロキシURLとして解析します。元の実装では、url.Parse がエラーを返した場合にのみ、http:// を補完して再解析を試みていました。

しかし、127.0.0.1:8080 のようにスキームが欠落している文字列を url.Parse に渡した場合、url.Parse はエラーを返しません。その代わり、url.URL 構造体の Scheme フィールドが空文字列になります。元のコードではこのケースが考慮されていなかったため、proxyURL.Scheme が空のまま処理が進み、結果としてプロキシが正しく設定されないというバグがありました。

このコミットでは、以下の変更によってこの問題を解決しています。

  1. url.Parse の結果チェックの強化: proxyURL, err := url.Parse(proxy) の結果をチェックする条件に、err != nil に加えて proxyURL.Scheme == "" を追加しました。 if err != nil || proxyURL.Scheme == "" これにより、url.Parse がエラーを返さなかった場合でも、解析されたURLのスキームが空であれば、それはスキームが欠落している不正な形式であると判断されます。

  2. http:// スキームの補完: スキームが欠落していると判断された場合、http:// をプレフィックスとして元のプロキシ文字列に追加し、再度 url.Parse を試みます。 if u, err := url.Parse("http://" + proxy); err == nil { proxyURL = u; err = nil } この修正により、127.0.0.1:8080 のような形式のプロキシ設定も http://127.0.0.1:8080 として正しく解釈されるようになります。

  3. ドキュメントの更新: ProxyFromEnvironment 関数のコメントが更新され、プロキシが環境変数で定義されていない場合や、リクエストにプロキシが使用されるべきでない場合に、nil URLnil error が返されることが明示されました。これにより、関数の挙動がより明確になりました。

  4. テストの追加: src/pkg/net/http/transport_test.goTestProxyFromEnvironment という新しいテスト関数が追加されました。このテストは、スキームあり/なしのプロキシURL、HTTPSプロキシURL、空のプロキシ設定など、様々なシナリオで ProxyFromEnvironment が期待通りに動作するかを検証します。特に、127.0.0.1:8080 のようなスキームなしのケースが正しく http://127.0.0.1:8080 として解釈されることを確認しています。

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

src/pkg/net/http/transport.goProxyFromEnvironment 関数内の以下の行が変更されました。

--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -76,7 +76,9 @@ type Transport struct {
 // ProxyFromEnvironment returns the URL of the proxy to use for a
 // given request, as indicated by the environment variables
 // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy).
-// Either URL or an error is returned.
+// An error is returned if the proxy environment is invalid.
+// A nil URL and nil error are returned if no proxy is defined in the
+// environment, or a proxy should not be used for the given request.
 func ProxyFromEnvironment(req *Request) (*url.URL, error) {
  	proxy := getenvEitherCase("HTTP_PROXY")
  	if proxy == "" {
@@ -86,7 +88,7 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) {
  		return nil, nil
  	}
  	proxyURL, err := url.Parse(proxy)
-	if err != nil {
+	if err != nil || proxyURL.Scheme == "" {
  		if u, err := url.Parse("http://\" + proxy); err == nil {
  			proxyURL = u
  			err = nil

また、src/pkg/net/http/transport_test.goTestProxyFromEnvironment テストが追加されました。

--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -16,6 +16,7 @@ import (
  	. "net/http"
  	"net/http/httptest"
  	"net/url"
+	"os"
  	"runtime"
  	"strconv"
  	"strings"
@@ -727,6 +728,36 @@ func TestTransportAltProto(t *testing.T) {
  	}\n
  }\n
  \n+var proxyFromEnvTests = []struct {\n+	env     string\n+	wanturl string\n+	wanterr error\n+}{\n+	{\"127.0.0.1:8080\", \"http://127.0.0.1:8080\", nil},\n+	{\"http://127.0.0.1:8080\", \"http://127.0.0.1:8080\", nil},\n+	{\"https://127.0.0.1:8080\", \"https://127.0.0.1:8080\", nil},\n+	{\"\", \"<nil>\", nil},\n+}\n+\n+func TestProxyFromEnvironment(t *testing.T) {\n+	os.Setenv(\"HTTP_PROXY\", \"\")\n+	os.Setenv(\"http_proxy\", \"\")\n+	os.Setenv(\"NO_PROXY\", \"\")\n+	os.Setenv(\"no_proxy\", \"\")\n+	for i, tt := range proxyFromEnvTests {\n+		os.Setenv(\"HTTP_PROXY\", tt.env)\n+		req, _ := NewRequest(\"GET\", \"http://example.com\", nil)\n+		url, err := ProxyFromEnvironment(req)\n+		if g, e := fmt.Sprintf(\"%v\", err), fmt.Sprintf(\"%v\", tt.wanterr); g != e {\n+			t.Errorf(\"%d. got error = %q, want %q\", i, g, e)\n+			continue\n+		}\n+		if got := fmt.Sprintf(\"%s\", url); got != tt.wanturl {\n+			t.Errorf(\"%d. got URL = %q, want %q\", i, url, tt.wanturl)\n+		}\n+	}\n+}\n+\n // rgz is a gzip quine that uncompresses to itself.\n var rgz = []byte{\n  	0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,\n```

## コアとなるコードの解説

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

*   **ドキュメントの更新**:
    `ProxyFromEnvironment` 関数のコメントが修正され、プロキシが環境変数で定義されていない場合や、リクエストにプロキシが使用されるべきでない場合に、`nil URL` と `nil error` が返されることが明記されました。これは関数の振る舞いをより正確に記述し、利用者が期待する戻り値を理解しやすくするための改善です。

*   **プロキシURL解析ロジックの修正**:
    変更前: `if err != nil {`
    変更後: `if err != nil || proxyURL.Scheme == "" {`
    この変更がバグ修正の核心です。`url.Parse(proxy)` の結果、`err` が `nil` であっても、`proxyURL.Scheme` が空文字列である場合(例: `127.0.0.1:8080` のようにスキームが指定されていない場合)も、不正なプロキシURLとして扱われるようになりました。
    この条件が真の場合、内部の `if u, err := url.Parse("http://" + proxy); err == nil { ... }` ブロックが実行され、`http://` スキームを補完して再解析を試みます。これにより、スキームが欠落しているプロキシ設定も正しく解釈され、`net/http` クライアントがプロキシを介して通信できるようになります。

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

*   **`proxyFromEnvTests` 変数の追加**:
    様々なプロキシ環境変数の入力 (`env`) と、それに対応する期待されるURL (`wanturl`) およびエラー (`wanterr`) を定義した構造体のスライスです。これにより、複数のテストケースを簡潔に記述できます。
    特に `{"127.0.0.1:8080", "http://127.0.0.1:8080", nil}` のテストケースは、スキームが欠落している場合の修正が正しく機能するかを検証しています。

*   **`TestProxyFromEnvironment` 関数の追加**:
    このテスト関数は、`proxyFromEnvTests` の各テストケースをループで実行します。
    1.  `os.Setenv` を使用して、テストケースの `env` 値を `HTTP_PROXY` 環境変数に設定します。
    2.  `NewRequest` でダミーのHTTPリクエストを作成します。
    3.  `ProxyFromEnvironment` 関数を呼び出し、結果のURLとエラーを取得します。
    4.  取得したURLとエラーが、期待される `wanturl` および `wanterr` と一致するかを `t.Errorf` を使って検証します。
    このテストの追加により、`ProxyFromEnvironment` 関数の堅牢性が向上し、将来の回帰を防ぐのに役立ちます。

## 関連リンク

*   Go Issue #2919: [https://github.com/golang/go/issues/2919](https://github.com/golang/go/issues/2919)
*   Go CL 5708055: [https://golang.org/cl/5708055](https://golang.org/cl/5708055)

## 参考にした情報源リンク

*   Go Issue #2919 の内容
*   Go言語の `net/http` および `net/url` パッケージのドキュメント
*   HTTPプロキシ、環境変数に関する一般的な知識
*   HTTP `CONNECT` メソッドに関する情報