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

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

このコミットは、Go言語の標準ライブラリであるnet/httpパッケージにおけるHTTPプロキシのパース処理のバグを修正するものです。具体的には、http_proxy環境変数で指定されたプロキシURLの解析が不適切であったために、プロキシが正しく適用されないケースがあった問題(Issue 2919)を解決します。

コミット

commit fb2caa3244184d73d0185dce2c8b594ff6e60c06
Author: Russ Cox <rsc@golang.org>
Date:   Sun Feb 12 23:19:50 2012 -0500

    net/http: fix http_proxy parsing
    
    Fixes #2919.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5645089

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

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

元コミット内容

net/http: fix http_proxy parsing

Fixes #2919.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5645089

変更の背景

このコミットは、Go言語のnet/httpパッケージがHTTPプロキシを適切に処理できないという問題、特にIssue 2919で報告された「net/http: not using proxy for https get」というバグを修正するために行われました。

当時のnet/httpパッケージは、http_proxy環境変数からプロキシ設定を読み取る際に、プロキシURLのパース方法に問題がありました。具体的には、http_proxyの値がhttp://のようなスキームを含まない形式(例: localhost:8080)で指定された場合、url.ParseRequest関数がこれを正しくURLとして認識できず、結果としてプロキシが適用されないという挙動を示していました。特にHTTPSリクエストの場合にこの問題が顕著でした。

この問題は、ユーザーが環境変数を通じてプロキシを設定しているにもかかわらず、Goアプリケーションがそのプロキシを使用しないという予期せぬ動作を引き起こし、ネットワーク接続に影響を与える可能性がありました。そのため、プロキシ設定の堅牢性を高め、ユーザーの期待通りの動作を保証するために、このパースロジックの修正が必要とされました。

前提知識の解説

1. net/httpパッケージ

Go言語の標準ライブラリであるnet/httpパッケージは、HTTPクライアントとサーバーの実装を提供します。ウェブアプリケーションの構築や、HTTPリクエストの送信など、ネットワーク通信の基盤となります。

2. HTTPプロキシ

HTTPプロキシは、クライアントとサーバーの間に位置し、クライアントからのリクエストをサーバーに転送し、サーバーからのレスポンスをクライアントに転送する役割を担います。これにより、セキュリティの強化、キャッシュによるパフォーマンス向上、アクセス制御、匿名性の確保などが可能になります。

3. http_proxy環境変数

多くのオペレーティングシステムやアプリケーションでは、HTTPプロキシの設定を環境変数を通じて行います。http_proxy環境変数は、HTTPリクエストに使用するプロキシサーバーのアドレスを指定するために広く利用されています。通常、http://host:portのような形式で指定されます。

4. url.URL構造体

Go言語のnet/urlパッケージに含まれるurl.URL構造体は、URLの各構成要素(スキーム、ホスト、パス、クエリなど)を構造化して表現するためのものです。URLの解析や構築に利用されます。

5. url.Parseurl.ParseRequest (当時の状況)

  • url.Parse(rawurl string) (*URL, error): この関数は、与えられた文字列を絶対URLとして解析しようとします。スキーム(例: http://)が含まれていない場合、相対URLとして扱われる可能性があります。
  • url.ParseRequest(rawurl string) (*URL, error): この関数は、HTTPリクエストラインの一部として解釈されるURLを解析するために設計されていました。これは、ブラウザがアドレスバーに入力された文字列を解釈する方法に似ており、スキームが省略されている場合でも、一般的なホスト名やパスをURLとして解釈しようとします。しかし、この関数は後に非推奨となり、Go 1.16で削除されました。これは、その挙動が曖昧で、セキュリティ上の懸念があったためです。このコミットが作成された2012年当時はまだ存在し、使用されていました。

このコミットの文脈では、url.ParseRequestがスキームなしのプロキシURLを正しく解釈できないという問題が焦点となっています。

技術的詳細

このコミットの技術的な核心は、src/pkg/net/http/transport.goファイル内のProxyFromEnvironment関数の修正にあります。この関数は、http_proxy環境変数からプロキシURLを解析し、*url.URL型で返す役割を担っています。

修正前のコードでは、http_proxyの値(proxy変数)を直接url.ParseRequest(proxy)で解析しようとしていました。しかし、前述の通り、http_proxyhost:portのようなスキームなしの形式で指定された場合、url.ParseRequestはこれを有効なURLとして認識できず、エラーを返していました。このエラーが発生すると、ProxyFromEnvironment関数は「invalid proxy address」というエラーを返してしまい、プロキシが使用されませんでした。

修正後のコードでは、この問題を解決するために以下のロジックが導入されました。

  1. まず、url.Parse(proxy)を試みます。これは、http_proxyが完全なURL形式(例: http://host:port)で指定されている場合に正しく解析されることを期待します。
  2. もしurl.Parse(proxy)がエラーを返した場合、それはproxy文字列がスキームを含まない形式である可能性が高いと判断します。
  3. この場合、"http://"というスキームをproxy文字列の先頭に付加し、url.Parse("http://" + proxy)を再度試みます。
  4. この再試行が成功した場合、proxyURLは正しく解析されたURLとなり、エラーもnilにリセットされます。これにより、スキームが省略されたプロキシ設定も正しく処理できるようになります。
  5. 最終的に、どちらのパース試行も失敗した場合にのみ、fmt.Errorf("invalid proxy address %q: %v", proxy, err)というより詳細なエラーメッセージを返します。これにより、デバッグ時の情報も増えます。

この変更により、http_proxy環境変数がhost:port形式で指定された場合でも、net/httpパッケージが自動的にhttp://スキームを補完してプロキシを正しく認識し、利用できるようになりました。

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

--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -85,16 +85,16 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) {
 	if !useProxy(canonicalAddr(req.URL)) {
 		return nil, nil
 	}
-	proxyURL, err := url.ParseRequest(proxy)
+	proxyURL, err := url.Parse(proxy)
 	if err != nil {
-		return nil, errors.New("invalid proxy address")
-	}\n-\tif proxyURL.Host == "" {\n-\t\tproxyURL, err = url.ParseRequest("http://" + proxy)\n-\t\tif err != nil {\n-\t\t\treturn nil, errors.New("invalid proxy address")
+		if u, err := url.Parse("http://" + proxy); err == nil {
+			proxyURL = u
+			err = nil
 		}
 	}
+	if err != nil {
+		return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
+	}
 	return proxyURL, nil
 }
 

コアとなるコードの解説

変更はsrc/pkg/net/http/transport.goファイルのProxyFromEnvironment関数内で行われています。

  • - proxyURL, err := url.ParseRequest(proxy)

    • 変更前は、http_proxy環境変数から取得したプロキシ文字列proxyurl.ParseRequest関数で直接解析しようとしていました。これが、スキームなしのプロキシURLを正しく扱えない原因でした。
  • + proxyURL, err := url.Parse(proxy)

    • 変更後、まずurl.Parse関数を使用してプロキシ文字列を解析します。url.Parseはより厳密なURL解析を行い、完全なURL形式を期待します。
  • - if err != nil { return nil, errors.New("invalid proxy address") }

    • 変更前は、最初のurl.ParseRequestがエラーを返した場合、すぐに「invalid proxy address」という汎用的なエラーを返して処理を終了していました。
  • - if proxyURL.Host == "" { ... }

    • 変更前は、url.ParseRequestで解析したproxyURLHostフィールドが空の場合(つまり、スキームなしでホスト名が認識されなかった場合)、"http://"を付加して再度url.ParseRequestを試みていました。しかし、この二度目の試行もurl.ParseRequestの挙動に依存しており、問題の根本的な解決にはなっていませんでした。
  • + if err != nil { if u, err := url.Parse("http://" + proxy); err == nil { proxyURL = u; err = nil } }

    • これが変更の核心部分です。最初のurl.Parse(proxy)がエラーを返した場合(つまり、proxyが完全なURL形式でなかった場合)、このブロックに入ります。
    • ここで、"http://"proxy文字列の先頭に付加し、再度url.Parseを試みます。
    • もしこの再試行が成功した場合(err == nil)、新しく解析されたURL uproxyURLに代入し、エラーもnilにリセットします。これにより、host:port形式のプロキシも正しくURLとして認識されるようになります。
  • + if err != nil { return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) }

    • 最終的なエラーチェックです。最初のurl.Parseも、http://を付加した後のurl.Parseも両方失敗した場合にのみ、このエラーハンドリングに到達します。
    • fmt.Errorfを使用することで、元のプロキシ文字列proxyと、発生した具体的なエラーerrをエラーメッセージに含めるようになり、デバッグ情報が格段に向上しました。

この修正により、ProxyFromEnvironment関数は、http_proxy環境変数がhttp://host:port形式でもhost:port形式でも、より堅牢にプロキシURLを解析できるようになりました。

関連リンク

参考にした情報源リンク