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

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

このコミットは、Go言語の net/http/cgi パッケージにおいて、CGI環境変数 REQUEST_URI が存在しない場合でもHTTPリクエストのURLを正しく構築できるようにするための修正です。具体的には、REQUEST_URI が提供されない場合に、SCRIPT_NAMEPATH_INFO、および QUERY_STRING といった他のCGI環境変数を組み合わせてURLを再構築するフォールバックロジックが追加されました。これにより、一部のCGI実装やWebサーバー環境で発生する互換性の問題が解決されます。

コミット

548e58781bd5d1201d3095351ec819bc447c0c47 Author: Alex Brainman alex.brainman@gmail.com Date: Tue Jan 8 17:23:46 2013 +1100

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

https://github.com/golang/go/commit/548e58781bd5d1201d3095351ec819bc447c0c47

元コミット内容

net/http/cgi: make it work without REQUEST_URI environment variable

Fixes #4367.

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

変更の背景

この変更は、Goの net/http/cgi パッケージが、CGIアプリケーションとして動作する際に REQUEST_URI 環境変数が設定されていない環境で正しく機能しないという問題(Go issue #4367)を解決するために行われました。

CGI (Common Gateway Interface) は、Webサーバーが外部プログラム(CGIスクリプト)と情報をやり取りするための標準的なプロトコルです。Webサーバーは、HTTPリクエストに関する様々な情報を環境変数としてCGIスクリプトに渡します。REQUEST_URI は、リクエストされたURI(Uniform Resource Identifier)のパス部分とクエリ文字列を含む変数であり、通常はリクエストされたリソースを特定するために使用されます。

しかし、すべてのWebサーバーやCGI実装が REQUEST_URI を常に提供するわけではありません。特に古いCGI環境や特定のWebサーバー設定では、この変数が利用できない場合があります。net/http/cgi パッケージが REQUEST_URI に強く依存していると、そのような環境ではGoのCGIアプリケーションが正しくリクエストを処理できず、URLの解析に失敗する可能性がありました。

このコミットは、REQUEST_URI が利用できない場合に備えて、CGI仕様で定義されている他の関連する環境変数(SCRIPT_NAMEPATH_INFOQUERY_STRING)を組み合わせてフォールバックとしてURLを構築するロジックを追加することで、この互換性の問題を解消することを目的としています。

前提知識の解説

CGI (Common Gateway Interface)

CGIは、Webサーバーが外部の実行可能プログラム(CGIスクリプト)と通信するための標準的なインターフェースです。WebサーバーはクライアントからのHTTPリクエストを受け取ると、そのリクエストを処理するためにCGIスクリプトを起動し、リクエストに関する情報を環境変数や標準入力(POSTリクエストの場合)を通じてスクリプトに渡します。スクリプトは処理結果を標準出力に書き出し、Webサーバーはそれを受け取ってクライアントに返します。

CGI環境変数

CGIスクリプトに渡される主要な環境変数には以下のようなものがあります。

  • REQUEST_URI:

    • リクエストされたURIのパス部分とクエリ文字列全体を含みます。
    • 例: /path/to/script/extra/path?query=string
    • この変数は便利ですが、すべてのCGI実装で利用可能とは限りません。
  • SCRIPT_NAME:

    • 実行されているCGIスクリプトの仮想パス(Webサーバーのドキュメントルートからの相対パス)を示します。
    • 例: /path/to/script
  • PATH_INFO:

    • SCRIPT_NAME の後に続くパス情報で、CGIスクリプトが追加のパスコンポーネントとして解釈する部分です。
    • 例: /extra/path (上記の REQUEST_URI の例の場合)
  • QUERY_STRING:

    • URLの ? の後に続くクエリ文字列全体を含みます。
    • 例: query=string

これらの変数を組み合わせることで、REQUEST_URI が利用できない場合でも、リクエストされた完全なURIを再構築することが可能です。具体的には、SCRIPT_NAME + PATH_INFO + ? + QUERY_STRING の形式でURIを構築できます。

Goの net/http/cgi パッケージ

Go言語の標準ライブラリ net/http/cgi パッケージは、GoプログラムをCGIスクリプトとして動作させるための機能を提供します。このパッケージは、CGI環境変数からHTTPリクエストを構築したり、HTTPレスポンスをCGIの形式で出力したりする役割を担います。特に RequestFromMap 関数は、CGI環境変数を表すマップから *http.Request オブジェクトを生成する中心的な関数です。

技術的詳細

このコミットの主要な変更は、src/pkg/net/http/cgi/child.go ファイル内の RequestFromMap 関数にあります。この関数は、CGI環境変数をキーと値のペアで持つ map[string]string を受け取り、それに基づいて *http.Request オブジェクトを構築します。

変更前は、http.RequestURL フィールドを構築する際に、主に REQUEST_URI 環境変数に依存していました。しかし、REQUEST_URI が空の場合、URLの構築が不完全になるか、エラーになる可能性がありました。

今回の修正では、以下のフォールバックロジックが導入されました。

  1. まず、params["REQUEST_URI"] の値を取得し、uriStr に格納します。
  2. uriStr が空文字列 ("") であるかどうかをチェックします。
  3. もし uriStr が空であれば、REQUEST_URI が利用できないと判断し、以下のCGI環境変数を組み合わせて uriStr を再構築します。
    • uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
    • さらに、QUERY_STRING (params["QUERY_STRING"]) が空でなければ、"?" + s の形式で uriStr に追加します。
  4. この再構築された uriStr を使用して、http.RequestURL フィールドが解析されます。

このロジックにより、REQUEST_URI が提供されないCGI環境でも、GoのCGIアプリケーションがリクエストされたURLを正確に特定し、処理を続行できるようになります。

また、src/pkg/net/http/cgi/child_test.go には、REQUEST_URI が存在しない場合の新しいテストケース TestRequestWithoutRequestURI が追加されました。このテストは、フォールバックロジックが期待通りに機能し、SCRIPT_NAMEPATH_INFOQUERY_STRING から正しいURLが構築されることを検証します。

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

src/pkg/net/http/cgi/child.go

--- a/src/pkg/net/http/cgi/child.go
+++ b/src/pkg/net/http/cgi/child.go
@@ -91,10 +91,19 @@ func RequestFromMap(params map[string]string) (*http.Request, error) {
 
 	// TODO: cookies.  parsing them isn't exported, though.
 
+	uriStr := params["REQUEST_URI"]
+	if uriStr == "" {
+		// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
+		uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
+		s := params["QUERY_STRING"]
+		if s != "" {
+			uriStr += "?" + s
+		}
+	}
 	if r.Host != "" {
 		// Hostname is provided, so we can reasonably construct a URL,
 		// even if we have to assume 'http' for the scheme.
-		rawurl := "http://" + r.Host + params["REQUEST_URI"]
+		rawurl := "http://" + r.Host + uriStr
 		url, err := url.Parse(rawurl)
 		if err != nil {
 			return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
@@ -104,7 +113,6 @@ func RequestFromMap(params map[string]string) (*http.Request, error) {\n 	// Fallback logic if we don't have a Host header or the URL\n 	// failed to parse\n 	if r.URL == nil {\n-		uriStr := params["REQUEST_URI"]
 		url, err := url.Parse(uriStr)
 		if err != nil {
 			return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)

src/pkg/net/http/cgi/child_test.go

--- a/src/pkg/net/http/cgi/child_test.go
+++ b/src/pkg/net/http/cgi/child_test.go
@@ -82,6 +82,28 @@ func TestRequestWithoutHost(t *testing.T) {
 		t.Fatalf("unexpected nil URL")
 	}
 	if g, e := req.URL.String(), "/path?a=b"; e != g {
-		t.Errorf("expected URL %q; got %q", e, g)
+		t.Errorf("URL = %q; want %q", g, e)
+	}
+}
+
+func TestRequestWithoutRequestURI(t *testing.T) {
+	env := map[string]string{
+		"SERVER_PROTOCOL": "HTTP/1.1",
+		"HTTP_HOST":       "example.com",
+		"REQUEST_METHOD":  "GET",
+		"SCRIPT_NAME":     "/dir/scriptname",
+		"PATH_INFO":       "/p1/p2",
+		"QUERY_STRING":    "a=1&b=2",
+		"CONTENT_LENGTH":  "123",
+	}
+	req, err := RequestFromMap(env)
+	if err != nil {
+		t.Fatalf("RequestFromMap: %v", err)
+	}
+	if req.URL == nil {
+		t.Fatalf("unexpected nil URL")
+	}
+	if g, e := req.URL.String(), "http://example.com/dir/scriptname/p1/p2?a=1&b=2"; e != g {
+		t.Errorf("URL = %q; want %q", g, e)
 	}
 }

コアとなるコードの解説

child.go の変更点

RequestFromMap 関数内で、まず uriStr := params["REQUEST_URI"]REQUEST_URI の値を取得します。

その直後に以下の新しいブロックが追加されました。

	if uriStr == "" {
		// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
		uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
		s := params["QUERY_STRING"]
		if s != "" {
			uriStr += "?" + s
		}
	}

この if ブロックは、REQUEST_URI が空の場合に実行されます。

  1. uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]: SCRIPT_NAMEPATH_INFO を連結してURIのパス部分を構築します。例えば、SCRIPT_NAME/dir/scriptnamePATH_INFO/p1/p2 なら、uriStr/dir/scriptname/p1/p2 となります。
  2. s := params["QUERY_STRING"]: QUERY_STRING の値を取得します。
  3. if s != "" { uriStr += "?" + s }: QUERY_STRING が空でなければ、? を前置して uriStr に追加します。これにより、完全なURI(パス + クエリ文字列)が uriStr に格納されます。

この変更により、r.Host != "" の条件分岐内での rawurl の構築や、r.URL == nil のフォールバックロジック内での url.Parse(uriStr) の呼び出しにおいて、常に有効な uriStr が使用されるようになります。以前は params["REQUEST_URI"] を直接使用していた箇所が、新しい uriStr 変数に置き換えられています。

child_test.go の変更点

TestRequestWithoutRequestURI という新しいテスト関数が追加されました。 このテストでは、REQUEST_URI を含まないCGI環境変数のマップ env を作成します。 SCRIPT_NAMEPATH_INFOQUERY_STRING にはそれぞれ値が設定されています。

	env := map[string]string{
		"SERVER_PROTOCOL": "HTTP/1.1",
		"HTTP_HOST":       "example.com",
		"REQUEST_METHOD":  "GET",
		"SCRIPT_NAME":     "/dir/scriptname",
		"PATH_INFO":       "/p1/p2",
		"QUERY_STRING":    "a=1&b=2",
		"CONTENT_LENGTH":  "123",
	}

この env マップを RequestFromMap に渡し、返された req.URL.String() が期待される完全なURL ("http://example.com/dir/scriptname/p1/p2?a=1&b=2") と一致するかどうかを検証しています。これにより、REQUEST_URI がない場合のフォールバックロジックが正しく機能することが保証されます。

関連リンク

参考にした情報源リンク