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

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

このコミットは、Go言語の標準ライブラリ net/http パッケージにおける ServeMux の自動リダイレクト処理に関するバグ修正です。具体的には、ServeMux.Handler() メソッドが生成するリダイレクトにおいて、元のリクエストのクエリ文字列が失われてしまう問題を解決します。これにより、クエリ文字列を含むURLへのリクエストが ServeMux によって自動的にリダイレクトされる際に、そのクエリ文字列が正しく保持されるようになります。

コミット

commit 716a409b9044850d0edf11318ec0eca63de57a93
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Thu Aug 29 13:55:12 2013 -0700

    net/http: redirect handlers from mux.Handler() shouldn't clear the query string
    
    R=bradfitz, alberto.garcia.hierro, rsc, adg
    CC=golang-dev
    https://golang.org/cl/7099045

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

https://github.com/golang/go/commit/716a409b9044850d0edf11318ec0eca63de57a93

元コミット内容

net/http: redirect handlers from mux.Handler() shouldn't clear the query string

R=bradfitz, alberto.garcia.hierro, rsc, adg
CC=golang-dev
https://golang.org/cl/7099045

変更の背景

Goの net/http パッケージの ServeMux は、HTTPリクエストのパスを処理する際に、末尾のスラッシュの有無などによって自動的にリダイレクトを行う機能を持っています。例えば、/path へのリクエストが登録されており、ユーザーが /path/ とリクエストした場合、ServeMux は自動的に /path へとリダイレクト(HTTP 301 Moved Permanently)を返します。

しかし、この自動リダイレクト処理において、元のリクエストURLに含まれるクエリ文字列(例: /path?key=value?key=value の部分)が、リダイレクト先のURLから失われてしまうという問題がありました。これは、リダイレクト先のURLを生成する際に、リクエストのパス部分のみを考慮し、クエリ文字列を適切に引き継いでいなかったためです。

この問題は、特にクエリ文字列に重要な情報が含まれている場合(例: セッションID、トラッキング情報、検索クエリなど)に、アプリケーションの動作に影響を与える可能性がありました。このコミットは、このクエリ文字列の消失を防ぎ、ServeMux の自動リダイレクトがより期待通りに動作するようにするために行われました。

前提知識の解説

  • net/http パッケージ: Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。Webアプリケーション開発の基盤となります。
  • http.ServeMux: HTTPリクエストのURLパスに基づいて、適切なハンドラ(http.Handler インターフェースを実装する型)にリクエストをルーティングするマルチプレクサ(ルーター)です。Handle メソッドや HandleFunc メソッドを使って、特定のパスとハンドラを関連付けます。
  • http.Handler インターフェース: ServeHTTP(ResponseWriter, *Request) メソッドを持つインターフェースです。このメソッドがHTTPリクエストを処理し、レスポンスを書き込みます。
  • HTTPリダイレクト (HTTP Redirect): クライアント(ブラウザなど)に対して、リクエストされたリソースが別のURLに移動したことを伝えるHTTPレスポンスです。
    • StatusMovedPermanently (301): リクエストされたリソースが恒久的に新しいURLに移動したことを示します。クライアントは将来のリクエストで新しいURLを使用すべきです。
  • URLのクエリ文字列 (Query String): URLのパス部分の後に ? で区切られて続く部分で、キーと値のペア(例: key1=value1&key2=value2)で構成されます。サーバーに情報を渡すためによく使用されます。http.Request オブジェクトの URL.RawQuery フィールドでアクセスできます。
  • http.Request オブジェクト: 受信したHTTPリクエストに関するすべての情報(メソッド、URL、ヘッダー、ボディなど)を含む構造体です。
  • url.URL 構造体: URLの各コンポーネント(Scheme, Host, Path, RawQueryなど)を表現する構造体です。

技術的詳細

http.ServeMuxHandler メソッドは、受信した *http.Request オブジェクトに基づいて、そのリクエストを処理する適切な http.Handler と、そのハンドラが登録されているパターンを返します。このメソッドの内部では、リクエストの URL.Path が正規化(cleanPath 関数による処理)されます。例えば、/path/ のような末尾にスラッシュがあるパスや、//path のような余分なスラッシュがあるパスは、正規化されて /path のような形式になります。

もし正規化されたパスが元のリクエストパスと異なる場合、ServeMux は自動的に http.StatusMovedPermanently (301) リダイレクトを生成し、クライアントを正規化されたパスに誘導します。

このコミット以前は、この自動リダイレクトを生成する際に、RedirectHandler に渡されるURLが cleanPath によって得られた「パス部分のみ」でした。具体的には、RedirectHandler(p, StatusMovedPermanently) のように呼び出されていました。ここで p はクエリ文字列を含まないパスです。このため、元のリクエストが http://example.com/foo/?param=value であった場合、リダイレクト先は http://example.com/foo/ となり、?param=value が失われていました。

このコミットでは、この問題を解決するために、リダイレクト先のURLを生成する際に、元のリクエストの URL オブジェクトをコピーし、そのコピーの Path フィールドのみを正規化されたパスに更新するように変更されました。そして、この更新された url.URL オブジェクト全体を String() メソッドで文字列化して RedirectHandler に渡すことで、元のクエリ文字列が保持されるようになりました。

これにより、http://example.com/foo/?param=value のようなリクエストが ServeMux によって自動リダイレクトされる場合、リダイレクト先は http://example.com/foo?param=value となり、クエリ文字列が正しく引き継がれます。

この変更は、net/http/server.goServeMux.Handler メソッド内で行われました。また、この修正の正しさを検証するために、net/http/serve_test.go に新しいテストケース TestServeMuxHandlerRedirects が追加されています。このテストは、クエリ文字列を含むURLが自動リダイレクトされた際に、クエリ文字列が保持されていることを確認します。

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

src/pkg/net/http/server.goServeMux.Handler メソッド内の変更です。

--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -1448,7 +1448,9 @@ func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
  	if r.Method != "CONNECT" {
  		if p := cleanPath(r.URL.Path); p != r.URL.Path {
  			_, pattern = mux.handler(r.Host, p)
-			return RedirectHandler(p, StatusMovedPermanently), pattern
+			url := *r.URL
+			url.Path = p
+			return RedirectHandler(url.String(), StatusMovedPermanently), pattern
  		}
  	}

コアとなるコードの解説

変更されたコードブロックは、ServeMux.Handler メソッド内で、リクエストのパスが正規化され、元のパスと異なる場合に自動リダイレクトを生成する部分です。

  • 変更前:

    return RedirectHandler(p, StatusMovedPermanently), pattern
    

    ここでは、cleanPath 関数によって正規化されたパス p のみが RedirectHandler に渡されていました。p は単なるパス文字列であり、元のリクエストに含まれていたクエリ文字列は含まれていませんでした。このため、リダイレクト先のURLからクエリ文字列が失われていました。

  • 変更後:

    url := *r.URL
    url.Path = p
    return RedirectHandler(url.String(), StatusMovedPermanently), pattern
    
    1. url := *r.URL: これは、元のリクエスト rURL フィールド(*url.URL 型)をデリファレンスし、その値(url.URL 構造体)を新しい変数 url にコピーしています。これにより、元のURLに含まれる Scheme, Host, RawQuery (クエリ文字列) などの情報がすべて url に引き継がれます。
    2. url.Path = p: コピーした url オブジェクトの Path フィールドを、cleanPath によって正規化されたパス p で上書きします。これにより、URLのパス部分は正規化されますが、他のコンポーネント(特に RawQuery)は元のまま保持されます。
    3. return RedirectHandler(url.String(), StatusMovedPermanently), pattern: 更新された url オブジェクトの String() メソッドを呼び出しています。url.URL 構造体の String() メソッドは、その構造体が持つすべてのコンポーネント(Scheme, Host, Path, RawQueryなど)を結合して完全なURL文字列を生成します。この完全なURL文字列が RedirectHandler に渡されることで、クエリ文字列がリダイレクト先に正しく含まれるようになります。

この変更により、ServeMux の自動リダイレクト機能が、クエリ文字列を適切に保持するようになり、より堅牢なWebアプリケーションの構築に貢献します。

関連リンク

参考にした情報源リンク

  • Go言語の net/http パッケージのドキュメント
  • Go言語の url パッケージのドキュメント
  • HTTP/1.1 RFC 2616 (Status Code 301 Moved Permanently)
  • Go言語のソースコード (特に src/pkg/net/http/server.gosrc/pkg/net/http/serve_test.go)