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

[インデックス 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 のような重複するスラッシュを削除したり、... といったパスセグメントを解決したりするために行われます。この動作は、通常の GETPOST といったリソース取得のリクエストにおいては、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 は、デフォルトで以下のパス正規化とリダイレクトの動作を行います。

  1. 末尾のスラッシュの統一:
    • パターンがスラッシュで終わる場合(例: /foo/)で、リクエストパスがスラッシュで終わらない場合(例: /foo)、ServeMux は末尾にスラッシュを追加したパスにリダイレクトします。
    • パターンがスラッシュで終わらない場合(例: /bar)で、リクエストパスがスラッシュで終わる場合(例: /bar/)、ServeMux は末尾のスラッシュを削除したパスにリダイレクトします。
  2. 冗長なスラッシュの除去: /foo//bar のようなパスは /foo/bar に正規化されます。
  3. . および .. セグメントの解決: /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 となるため、これまで通りパスの正規化とリダイレクトのロジックが実行されます。これにより、GETPOST などの一般的なリクエストに対する ServeMux の望ましい動作(URLの正規化とSEOフレンドリーなリダイレクト)は維持されます。

この修正は、CONNECT メソッドの特殊な性質を考慮し、ServeMux の汎用的なパス処理ロジックから CONNECT リクエストを適切に除外することで、net/http パッケージの堅牢性と正確性を向上させています。

関連リンク

参考にした情報源リンク