[インデックス 13715] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージ内の ServeMux
における、暗黙的なリダイレクトの挙動を修正するものです。具体的には、ホスト名を含むパターン(例: example.com/path/
)が ServeMux
に登録された際に、末尾のスラッシュがないパス(例: example.com/path
)へのアクセスに対して生成される暗黙的なリダイレクトが、誤ってホスト名を含んだままのリダイレクト先(例: example.com/path/
)を生成してしまう問題を解決します。この修正により、ホスト名を含むパターンに対する暗黙的なリダイレクトは、パス部分のみをリダイレクトハンドラに渡すようになり、正しいパス(例: /path/
)へリダイレクトされるようになります。また、この変更を検証するためのテストケースも追加されています。
コミット
commit db7dbe32aa449d64194f811d532c9d3fcc5c3255
Author: Christian Himpel <chressie@googlemail.com>
Date: Fri Aug 31 12:00:01 2012 -0400
net/http: fix inserting of implicit redirects in serve mux
In serve mux, if pattern contains a host name, pass only the path to
the redirect handler.
Add tests for serve mux redirections.
R=rsc
CC=bradfitz, gobot, golang-dev
https://golang.org/cl/6329045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/db7dbe32aa449d64194f811d532c9d3fcc5c3255
元コミット内容
net/http
: ServeMux
における暗黙的なリダイレクトの挿入を修正。
ServeMux
において、パターンがホスト名を含む場合、リダイレクトハンドラにはパスのみを渡すようにする。
ServeMux
のリダイレクトに関するテストを追加。
変更の背景
Goの net/http
パッケージの ServeMux
は、登録されたパターンが末尾にスラッシュを持つ場合(例: /foo/
)、末尾にスラッシュがないパス(例: /foo
)へのリクエストに対して、自動的に末尾にスラッシュを追加したパス(例: /foo/
)へリダイレクトする機能(暗黙的なリダイレクト)を提供します。これは、URLの正規化と一貫性のために重要な挙動です。
しかし、このコミット以前の ServeMux
では、登録されるパターンがホスト名を含む場合(例: example.com/foo/
)に問題がありました。この場合、example.com/foo
へのリクエストに対して生成される暗黙的なリダイレクトが、リダイレクト先のURLとして example.com/foo/
をそのまま使用していました。これは一見正しいように見えますが、RedirectHandler
は通常、絶対パスまたは相対パスを受け取ることを想定しており、ホスト名を含む文字列をそのまま渡すと、意図しない挙動や冗長なリダイレクトが発生する可能性がありました。特に、RedirectHandler
が内部で http.StatusMovedPermanently
(301 Moved Permanently) を使用してリダイレクトを行う際、リダイレクト先の Location
ヘッダには、パス部分のみが適切に設定されるべきでした。
このコミットは、ホスト名を含むパターンが登録された際に、暗黙的なリダイレクトのために RedirectHandler
に渡すURLからホスト名を取り除き、パス部分のみを渡すようにすることで、この問題を解決し、より堅牢で正しいリダイレクト挙動を保証することを目的としています。
前提知識の解説
net/http
パッケージ: Go言語の標準ライブラリで、HTTPクライアントおよびサーバーの実装を提供します。ウェブアプリケーション開発の基盤となります。http.ServeMux
: HTTPリクエストのURLパスに基づいてハンドラをルーティングするためのHTTPマルチプレクサ(ルーター)です。Handle
メソッドを使って特定のパターンにハンドラを登録します。- ハンドラ (Handler):
http.Handler
インターフェースを実装する型で、ServeHTTP(ResponseWriter, *Request)
メソッドを持ちます。このメソッドが実際のリクエスト処理を行います。 - パターンマッチング:
ServeMux
は、登録されたパターンとリクエストURLを比較して、適切なハンドラを選択します。パターンには、完全一致、プレフィックスマッチ(末尾にスラッシュがある場合)、ホスト名を含むパターンなどがあります。- プレフィックスマッチと暗黙的なリダイレクト:
ServeMux
は、パターンが/
で終わる場合(例:/path/
)、そのパターンはプレフィックスとして扱われます。例えば、/path/
が登録されている場合、/path/subpath
のようなリクエストもこのハンドラにルーティングされます。また、/path
のように末尾にスラッシュがないリクエストが来た場合、ServeMux
は自動的にhttp.StatusMovedPermanently
(301) で/path/
へリダイレクトします。これが「暗黙的なリダイレクト」です。
- プレフィックスマッチと暗黙的なリダイレクト:
http.RedirectHandler
: 指定されたURLにクライアントをリダイレクトするためのhttp.Handler
を返す関数です。第一引数にリダイレクト先のURL、第二引数にHTTPステータスコード(例:http.StatusMovedPermanently
)を取ります。http.StatusMovedPermanently
(301): 恒久的なリダイレクトを示すHTTPステータスコードです。ブラウザやクライアントは、このリダイレクトをキャッシュし、以降の同じURLへのリクエストは直接新しいURLへ送るべきだと解釈します。- ホスト名を含むパターン:
ServeMux
は、example.com/path/
のようにホスト名を含むパターンも登録できます。これにより、特定のホスト名に対するリクエストを処理するハンドラを定義できます。
技術的詳細
このコミットの技術的な核心は、http.ServeMux
の Handle
メソッド内で暗黙的なリダイレクトを設定するロジックの変更にあります。
ServeMux
の Handle
メソッドは、パターンが末尾にスラッシュを持ち、かつそのスラッシュなしのパターンが明示的に登録されていない場合に、暗黙的なリダイレクトハンドラを登録します。このリダイレクトハンドラは、末尾にスラッシュがないURLへのリクエストを、スラッシュ付きのURLへリダイレクトするために使用されます。
変更前のコードでは、この暗黙的なリダイレクトハンドラを生成する際に、登録された pattern
そのものを http.RedirectHandler
に渡していました。
// 変更前
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(pattern, StatusMovedPermanently)}
ここで問題となるのは、pattern
が example.com/someDir/
のようにホスト名を含む場合です。この pattern
をそのまま RedirectHandler
に渡すと、リダイレクト先の Location
ヘッダには example.com/someDir/
が設定されます。これは、RedirectHandler
が期待するパス形式(/someDir/
のような絶対パス)とは異なり、場合によっては不適切なリダイレクトを引き起こす可能性がありました。特に、RedirectHandler
は内部で http.Request.URL.Path
を基準にリダイレクトを処理するため、ホスト名が含まれていると意図しない挙動になることが考えられます。
このコミットでは、この問題を解決するために、pattern
がホスト名を含むかどうかをチェックし、もし含む場合はパス部分のみを抽出して RedirectHandler
に渡すように修正されました。
// 変更後
path := pattern
if pattern[0] != '/' { // パターンが '/' で始まらない場合、ホスト名を含むと判断
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):] // 最初の '/' 以降をパスとして抽出
}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently)}
この変更により、example.com/someDir/
のようなパターンが登録された場合でも、暗黙的なリダイレクトハンドラには /someDir/
というパスが渡されるようになります。これにより、http.RedirectHandler
は正しくリダイレクト先の Location
ヘッダを生成し、クライアントは期待通りのパスへリダイレクトされるようになります。
また、この修正の正しさを検証するために、src/pkg/net/http/serve_test.go
に新しいテストケースが追加されました。これらのテストケースは、ホスト名を含むパターンに対する暗黙的なリダイレクトが正しく機能し、期待される Location
ヘッダが返されることを確認します。具体的には、http.StatusMovedPermanently
(301) のレスポンスコードと、その Location
ヘッダの内容を検証するロジックが追加されています。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の2つのファイルにあります。
-
src/pkg/net/http/server.go
:ServeMux
のHandle
メソッド内の暗黙的なリダイレクト設定ロジック。pattern
がホスト名を含む場合に、RedirectHandler
に渡す文字列からホスト名を取り除き、パス部分のみを抽出するロジックが追加されました。
-
src/pkg/net/http/serve_test.go
:ServeMux
のリダイレクト挙動を検証するためのテスト。vtests
変数に、ホスト名を含むパターンに対するリダイレクトの新しいテストケースが追加されました。- テストの検証ロジックが拡張され、
http.StatusMovedPermanently
(301) のレスポンスコードと、そのLocation
ヘッダの内容を検証するようになりました。
コアとなるコードの解説
src/pkg/net/http/server.go
の変更
// 変更前
// mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(pattern, StatusMovedPermanently)}
// 変更後
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently)}
このコードスニペットは、ServeMux
の Handle
メソッド内で、暗黙的なリダイレクトハンドラを登録する部分です。
n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit
: この条件は、登録しようとしているpattern
が末尾にスラッシュを持ち(例:/foo/
やexample.com/bar/
)、かつ、そのスラッシュなしのパターン(例:/foo
やexample.com/bar
)が明示的に登録されていない場合にtrue
となります。この場合、ServeMux
は末尾にスラッシュがないパスへのリクエストを、スラッシュ付きのパスへリダイレクトするための暗黙的なハンドラを登録する必要があります。path := pattern
: まず、path
変数に元のpattern
をコピーします。if pattern[0] != '/'
: ここが重要な変更点です。登録されるpattern
の最初の文字が/
でない場合、そのパターンはホスト名を含んでいると判断されます(例:example.com/path/
)。path = pattern[strings.Index(pattern, "/"):]
:pattern
がホスト名を含む場合、strings.Index(pattern, "/")
を使って、pattern
内で最初に出現する/
のインデックスを探します。そして、そのインデックスから文字列の最後までをスライスすることで、ホスト名を除いたパス部分(例:/path/
)を抽出します。この抽出されたパスがpath
変数に代入されます。mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently)}
: 最終的に、抽出されたpath
(ホスト名が含まれない、または元々含まれていなかったパス)がhttp.RedirectHandler
に渡され、暗黙的なリダイレクトハンドラとしてServeMux
に登録されます。これにより、リダイレクト先のLocation
ヘッダが正しく設定されるようになります。
src/pkg/net/http/serve_test.go
の変更
// vtests に追加されたテストケース
// redirections for trees
{"http://localhost/someDir", "/someDir/"},
{"http://someHost.com/someDir", "/someDir/"},
// テスト検証ロジックの変更
// 変更前
// s := r.Header.Get("Result")
// if s != vt.expected {
// t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected)
// }
// 変更後
switch r.StatusCode {
case StatusOK:
s := r.Header.Get("Result")
if s != vt.expected {
t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected)
}
case StatusMovedPermanently: // 301リダイレクトの場合の検証を追加
s := r.Header.Get("Location") // Locationヘッダをチェック
if s != vt.expected {
t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected)
}
default:
t.Errorf("Get(%q) unhandled status code %d", vt.url, r.StatusCode)
}
-
新しいテストケース:
{"http://localhost/someDir", "/someDir/"}
:localhost
を含むURLで、末尾にスラッシュがない場合に、パス部分のみ(/someDir/
)にリダイレクトされることを期待します。{"http://someHost.com/someDir", "/someDir/"}
: 任意のホスト名を含むURLで、同様にパス部分のみにリダイレクトされることを期待します。 これらのテストケースは、ホスト名を含むパターンに対する暗黙的なリダイレクトの修正が正しく機能していることを確認するために不可欠です。
-
テスト検証ロジックの変更:
- 以前のテストは、主に
StatusOK
(200 OK) のレスポンスと、カスタムのResult
ヘッダの内容を検証していました。 - 今回の変更では、
switch r.StatusCode
を導入し、レスポンスのHTTPステータスコードに基づいて異なる検証を行うようになりました。 case StatusMovedPermanently:
のブロックが追加され、リダイレクトが発生した場合(ステータスコードが301の場合)に、レスポンスのLocation
ヘッダの内容をvt.expected
と比較して検証します。これにより、リダイレクト先のURLが期待通りであるかを確認できます。default:
ケースも追加され、予期しないステータスコードが返された場合にエラーを報告するようになりました。
- 以前のテストは、主に
これらのテストの追加と変更により、ServeMux
の暗黙的なリダイレクト、特にホスト名を含むパターンに対する挙動が、より厳密に検証されるようになりました。
関連リンク
- Go Change List: https://golang.org/cl/6329045
参考にした情報源リンク
- Go
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - HTTPステータスコード 301 Moved Permanently: https://developer.mozilla.org/ja/docs/Web/HTTP/Status/301
strings.Index
関数: https://pkg.go.dev/strings#Index