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

[インデックス 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.ServeMuxHandle メソッド内で暗黙的なリダイレクトを設定するロジックの変更にあります。

ServeMuxHandle メソッドは、パターンが末尾にスラッシュを持ち、かつそのスラッシュなしのパターンが明示的に登録されていない場合に、暗黙的なリダイレクトハンドラを登録します。このリダイレクトハンドラは、末尾にスラッシュがないURLへのリクエストを、スラッシュ付きのURLへリダイレクトするために使用されます。

変更前のコードでは、この暗黙的なリダイレクトハンドラを生成する際に、登録された pattern そのものを http.RedirectHandler に渡していました。

// 変更前
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(pattern, StatusMovedPermanently)}

ここで問題となるのは、patternexample.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つのファイルにあります。

  1. src/pkg/net/http/server.go: ServeMuxHandle メソッド内の暗黙的なリダイレクト設定ロジック。

    • pattern がホスト名を含む場合に、RedirectHandler に渡す文字列からホスト名を取り除き、パス部分のみを抽出するロジックが追加されました。
  2. 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)}

このコードスニペットは、ServeMuxHandle メソッド内で、暗黙的なリダイレクトハンドラを登録する部分です。

  • n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit: この条件は、登録しようとしている pattern が末尾にスラッシュを持ち(例: /foo/example.com/bar/)、かつ、そのスラッシュなしのパターン(例: /fooexample.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