[インデックス 12965] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージ内の ServeMux
が、HTTP CONNECT
メソッドのリクエストに対して不適切にパスの正規化とリダイレクトを行っていた問題を修正するものです。
コミット
- コミットハッシュ:
61a8eb07f80b1db11a527060c9d861a20ab86d52
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Wed Apr 25 12:46:16 2012 -0700
- Subject:
net/http: ignore paths on CONNECT requests in ServeMux
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/61a8eb07f80b1db11a527060c9d861a20ab86d52
元コミット内容
net/http: ignore paths on CONNECT requests in ServeMux
Fixes #3538
R=golang-dev, adg, rsc
CC=golang-dev
https://golang.org/cl/6117058
変更の背景
net/http
パッケージの ServeMux
は、HTTPリクエストのURLパスを正規化し、必要に応じてリダイレクト(HTTP 301 Moved Permanently)を返す機能を持っています。これは、例えば /foo/
と /foo
のようなパスの末尾のスラッシュの有無を統一したり、/foo//bar
のような重複するスラッシュを削除したり、.
や ..
といったパスセグメントを解決したりするために行われます。この動作は、通常の GET
や POST
といったリソース取得のリクエストにおいては、SEOの観点やURLの一貫性を保つ上で望ましいものです。
しかし、HTTP CONNECT
メソッドは、通常のHTTPリクエストとは異なる特殊な目的で使用されます。CONNECT
メソッドは、主にプロキシサーバーを介してTCPトンネルを確立するために用いられ、特にHTTPS通信においてクライアントと最終的なサーバー間でエンドツーエンドの暗号化された接続を確立する際に不可欠です。CONNECT
リクエストのターゲットは Host:Port
形式であり、特定のパスを指すものではありません。
このコミット以前の ServeMux
は、CONNECT
リクエストに対しても通常のHTTPリクエストと同様にパスの正規化とリダイレクト処理を適用していました。これは CONNECT
メソッドの本来の意図に反する動作であり、プロキシ経由での通信において予期せぬリダイレクトが発生し、接続の確立に失敗するなどの問題を引き起こす可能性がありました。この問題は Go の Issue #3538 として報告されていました。
前提知識の解説
HTTP CONNECT
メソッド
HTTP CONNECT
メソッドは、HTTPプロキシサーバーに対して、指定された宛先ホストとポートへのTCP接続を確立するよう要求するために使用されます。プロキシがこの要求を受け入れると、クライアントと宛先サーバー間の生データのトンネルとして機能し、プロキシはトンネル内のデータを検査したり変更したりしません。
主な用途は以下の通りです。
- HTTPSトンネリング: クライアントがHTTPSサイトにアクセスする際、プロキシは暗号化されたトラフィックを直接検査できないため、
CONNECT
メソッドを使用してプロキシ経由で宛先サーバーへのTCP接続を確立します。これにより、クライアントと宛先サーバー間でTLSハンドシェイクが行われ、エンドツーエンドの暗号化通信が可能になります。 - その他のプロトコルのトンネリング: SSHやFTPなど、HTTP以外のプロトコルをHTTP(S)ポート経由でトンネリングするためにも使用されることがあります。
CONNECT
リクエストの形式は CONNECT host:port HTTP/1.1
のようになり、通常の GET /path HTTP/1.1
のようなパスは含まれません。
net/http.ServeMux
とパスの正規化・リダイレクト
net/http.ServeMux
は、Go言語の net/http
パッケージが提供するHTTPリクエストマルチプレクサ(ルーター)です。これは、受信したHTTPリクエストのURLパスを登録されたパターンと照合し、最も一致するハンドラにリクエストをディスパッチします。
ServeMux
は、デフォルトで以下のパス正規化とリダイレクトの動作を行います。
- 末尾のスラッシュの統一:
- パターンがスラッシュで終わる場合(例:
/foo/
)で、リクエストパスがスラッシュで終わらない場合(例:/foo
)、ServeMux
は末尾にスラッシュを追加したパスにリダイレクトします。 - パターンがスラッシュで終わらない場合(例:
/bar
)で、リクエストパスがスラッシュで終わる場合(例:/bar/
)、ServeMux
は末尾のスラッシュを削除したパスにリダイレクトします。
- パターンがスラッシュで終わる場合(例:
- 冗長なスラッシュの除去:
/foo//bar
のようなパスは/foo/bar
に正規化されます。 .
および..
セグメントの解決:/foo/./bar
は/foo/bar
に、/foo/../bar
は/bar
に解決されます。
これらの正規化処理は、通常、HTTP 301 (Moved Permanently) ステータスコードを伴うリダイレクトとしてクライアントに返されます。
技術的詳細
このコミットの技術的な核心は、net/http/server.go
内の ServeMux.ServeHTTP
メソッドにおけるパス正規化とリダイレクトのロジックに条件を追加した点です。
変更前は、ServeMux.ServeHTTP
メソッドの冒頭で、リクエストメソッドに関わらず一律に cleanPath
関数を呼び出し、パスの正規化とリダイレクト処理を行っていました。
// 変更前
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// Clean path to canonical form and redirect.
if p := cleanPath(r.URL.Path); p != r.URL.Path {
w.Header().Set("Location", p)
w.WriteHeader(StatusMovedPermanently)
return
}
mux.handler(r).ServeHTTP(w, r)
}
このコードは、CONNECT
リクエストが r.URL.Path
に意味のあるパス情報を持たないにもかかわらず、cleanPath
によって処理され、場合によってはリダイレクトが発行されてしまうという問題がありました。CONNECT
リクエストは、プロキシに対してトンネル確立を要求するものであり、パスの正規化やリダイレクトは不要であり、むしろ有害です。
このコミットでは、このパス正規化とリダイレクトのロジックを if r.Method != "CONNECT"
という条件ブロックで囲むことで、CONNECT
メソッドのリクエストに対してはこれらの処理がスキップされるように修正しました。
// 変更後
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.Method != "CONNECT" { // この条件が追加された
// Clean path to canonical form and redirect.
if p := cleanPath(r.URL.Path); p != r.URL.Path {
w.Header().Set("Location", p)
w.WriteHeader(StatusMovedPermanently)
return
}
}
mux.handler(r).ServeHTTP(w, r)
}
これにより、CONNECT
リクエストは ServeMux
のパス正規化ロジックの影響を受けなくなり、プロキシ経由でのHTTPS通信などが正しく機能するようになります。
コアとなるコードの変更箇所
--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -917,11 +917,13 @@ func (mux *ServeMux) handler(r *Request) Handler {
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
- // Clean path to canonical form and redirect.
- if p := cleanPath(r.URL.Path); p != r.URL.Path {
- w.Header().Set("Location", p)
- w.WriteHeader(StatusMovedPermanently)
- return
+ if r.Method != "CONNECT" {
+ // Clean path to canonical form and redirect.
+ if p := cleanPath(r.URL.Path); p != r.URL.Path {
+ w.Header().Set("Location", p)
+ w.WriteHeader(StatusMovedPermanently)
+ return
+ }
}
mux.handler(r).ServeHTTP(w, r)
}
コアとなるコードの解説
変更の中心は、ServeMux.ServeHTTP
メソッドの冒頭に追加された if r.Method != "CONNECT"
という条件文です。
r.Method
は、受信したHTTPリクエストのメソッド(例: "GET", "POST", "CONNECT" など)を表します。- この条件は、「もしリクエストメソッドが
CONNECT
でないならば」ということを意味します。
この条件が追加されたことにより、以下の動作が保証されます。
CONNECT
リクエストの場合:if r.Method != "CONNECT"
の条件がfalse
となるため、その内部のパス正規化とリダイレクトのロジックは実行されません。これにより、CONNECT
リクエストはServeMux
による不必要なパス処理を回避し、直接mux.handler(r).ServeHTTP(w, r)
に進むことができます。CONNECT
以外のリクエストの場合:if r.Method != "CONNECT"
の条件がtrue
となるため、これまで通りパスの正規化とリダイレクトのロジックが実行されます。これにより、GET
やPOST
などの一般的なリクエストに対するServeMux
の望ましい動作(URLの正規化とSEOフレンドリーなリダイレクト)は維持されます。
この修正は、CONNECT
メソッドの特殊な性質を考慮し、ServeMux
の汎用的なパス処理ロジックから CONNECT
リクエストを適切に除外することで、net/http
パッケージの堅牢性と正確性を向上させています。
関連リンク
- Go Issue #3538: https://github.com/golang/go/issues/3538
- Go CL 6117058: https://golang.org/cl/6117058
参考にした情報源リンク
- HTTP
CONNECT
Method: - Go
net/http.ServeMux
Path Cleaning and Redirect:- https://pkg.go.dev/net/http#ServeMux (公式ドキュメント)
- https://stackoverflow.com/questions/29402900/how-to-disable-automatic-redirect-in-go-http-server (Stack Overflow の関連議論)
- Go
net/http
package: