[インデックス 13177] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http パッケージ内の server.go ファイルに対する変更です。server.go は、HTTPサーバーのコアロジック、特にリクエストのルーティングを司る ServeMux の実装を含んでいます。ServeMux は、HTTPリクエストのURLパスに基づいて適切なハンドラをディスパッチするマルチプレクサ(ルーター)として機能します。
コミット
net/http: speed up ServeMux when no patterns contain hostnames
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6248053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c238031b2de1a83e0441b4696dd7c2754e80688b
元コミット内容
net/http: speed up ServeMux when no patterns contain hostnames
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6248053
変更の背景
このコミットの主な目的は、net/http パッケージの ServeMux のパフォーマンスを向上させることです。具体的には、「登録されたパターンの中にホスト名を含むものが一つも存在しない場合」に、リクエストのルーティング処理を高速化することを目指しています。
ServeMux は、リクエストを処理する際に、まずホスト名を含むパターン(例: example.com/path)にマッチするかどうかを試み、次にホスト名を含まない一般的なパスパターン(例: /path)にマッチするかどうかを試みます。しかし、アプリケーションがホスト名を含むパターンを一切使用しない場合でも、ServeMux は常にホスト名を含むパターンでのマッチングを試みるため、その分のオーバーヘッドが発生していました。
この変更は、この不要なマッチング処理をスキップすることで、パフォーマンスの最適化を図るものです。これにより、一般的なWebアプリケーション(ホスト名ベースのルーティングをあまり使用しないもの)において、リクエスト処理のレイテンシが削減されることが期待されます。
前提知識の解説
Go言語の net/http パッケージ
net/http パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。Webアプリケーション開発において中心的な役割を果たします。
http.ServeMux
http.ServeMux は、GoのHTTPサーバーにおけるリクエストマルチプレクサ(ルーター)です。これは、受信したHTTPリクエストのURLパスに基づいて、適切な http.Handler を選択し、そのハンドラにリクエストの処理を委譲します。
ServeMux には、Handle メソッドや HandleFunc メソッドを使って、特定のURLパターンとそれに対応するハンドラを登録します。パターンには以下の2種類があります。
- パスパターン:
/path/to/resourceのように、URLのパス部分のみを指定するパターン。 - ホスト名を含むパターン:
example.com/pathのように、ホスト名とパスを組み合わせたパターン。これにより、同じパスでも異なるホスト名からのリクエストに対して異なるハンドラを割り当てることができます(バーチャルホストのような機能)。
ServeMux は、リクエストが来た際に、まずホスト名を含むパターンにマッチするかどうかを調べ、次に一般的なパスパターンにマッチするかどうかを調べます。
http.Handler インターフェース
http.Handler は、ServeHTTP(ResponseWriter, *Request) メソッドを持つインターフェースです。このインターフェースを実装する型は、HTTPリクエストを処理するハンドラとして機能します。
sync.RWMutex
sync.RWMutex は、Go言語の sync パッケージで提供される読み書きミューテックスです。これは、共有リソースへのアクセスを制御するために使用されます。
- RWMutex.RLock(): 読み取りロックを取得します。複数のゴルーチンが同時に読み取りロックを取得できます。
- RWMutex.RUnlock(): 読み取りロックを解放します。
- RWMutex.Lock(): 書き込みロックを取得します。書き込みロックが取得されている間は、他の読み取りロックも書き込みロックも取得できません。
- RWMutex.Unlock(): 書き込みロックを解放します。
ServeMux の内部マップ (m) は、複数のゴルーチンから同時にアクセスされる可能性があるため、sync.RWMutex を使用して並行アクセスを安全に制御しています。Handle メソッドでパターンを登録する際には書き込みロック (Lock) を、handler メソッドでパターンを検索する際には読み取りロック (RLock) を使用します。
技術的詳細
このコミットは、ServeMux の内部状態に hosts という新しいブーリアンフィールドを追加し、このフィールドを使ってルーティングロジックを最適化します。
-
ServeMux構造体へのhostsフィールドの追加:ServeMux構造体にhosts boolというフィールドが追加されました。このフィールドは、ServeMuxに登録されているパターンの中に、ホスト名を含むパターンが一つでも存在するかどうかを示すフラグとして機能します。初期値はfalseです。 -
Handleメソッドでのhostsフィールドの更新:ServeMux.Handleメソッドは、新しいパターンが登録されるたびに呼び出されます。このメソッド内で、登録されるpatternがスラッシュ (/) で始まらない場合(これはホスト名を含むパターンであることを意味します)、mux.hostsフィールドがtrueに設定されます。一度trueに設定されると、ServeMuxのライフサイクルを通じてtrueのまま維持されます。これは、ホスト名を含むパターンが一度でも登録されれば、以降のリクエスト処理でホスト名ベースのマッチングが必要になるためです。 -
handlerメソッドでの条件付きマッチング:ServeMux.handlerメソッドは、受信したHTTPリクエストに対して適切なハンドラを見つけるための内部メソッドです。このメソッド内で、mux.hostsフィールドの値がチェックされます。- もし
mux.hostsがtrueであれば、これまで通りr.Host + r.URL.Pathを使ってホスト名を含むパターンでのマッチングを試みます。 - もし
mux.hostsがfalseであれば、ホスト名を含むパターンでのマッチング(mux.match(r.Host + r.URL.Path))は完全にスキップされ、直接r.URL.Pathを使った一般的なパスパターンでのマッチングに移行します。
- もし
この最適化により、ホスト名を含むパターンが一つも登録されていない ServeMux の場合、リクエストごとに r.Host + r.URL.Path を構築し、そのパターンでマップを検索するという不要な処理が完全に回避され、パフォーマンスが向上します。特に、大量のリクエストを処理するサーバーにおいて、この小さな最適化が累積的な効果をもたらす可能性があります。
コアとなるコードの変更箇所
src/pkg/net/http/server.go ファイルにおいて、以下の変更が行われました。
-
ServeMux構造体へのフィールド追加:--- a/src/pkg/net/http/server.go +++ b/src/pkg/net/http/server.go @@ -840,8 +840,9 @@ func RedirectHandler(url string, code int) Handler { // redirecting any request containing . or .. elements to an // equivalent .- and ..-free URL. type ServeMux struct { - mu sync.RWMutex - m map[string]muxEntry + mu sync.RWMutex + m map[string]muxEntry + hosts bool // whether any patterns contain hostnames } -
handlerメソッドの変更:--- a/src/pkg/net/http/server.go +++ b/src/pkg/net/http/server.go @@ -903,12 +904,14 @@ func (mux *ServeMux) match(path string) Handler {\n }\n \n // handler returns the handler to use for the request r.\n-func (mux *ServeMux) handler(r *Request) Handler {\n+func (mux *ServeMux) handler(r *Request) (h Handler) {\n mux.mu.RLock()\n defer mux.mu.RUnlock()\n \n // Host-specific pattern takes precedence over generic ones\n-\th := mux.match(r.Host + r.URL.Path)\n+\tif mux.hosts {\n+\t\th = mux.match(r.Host + r.URL.Path)\n+\t}\n if h == nil {\n \th = mux.match(r.URL.Path)\n }\ -
Handleメソッドの変更:--- a/src/pkg/net/http/server.go +++ b/src/pkg/net/http/server.go @@ -950,6 +953,10 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) {\n \n mux.m[pattern] = muxEntry{explicit: true, h: handler}\n \n+\tif pattern[0] != \'/\' {\n+\t\tmux.hosts = true\n+\t}\n+\n // Helpful behavior:\n // If pattern is /tree/, insert an implicit permanent redirect for /tree.\n // It can be overridden by an explicit registration.\
コアとなるコードの解説
ServeMux 構造体への hosts フィールド追加
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
hosts フィールドは、ServeMux インスタンスがホスト名を含むパターン(例: example.com/path)を登録しているかどうかを示すブーリアンフラグです。このフラグは、ルーティング処理の最適化のために導入されました。デフォルト値は false です。
handler メソッドの変更
func (mux *ServeMux) handler(r *Request) (h Handler) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts { // <-- 追加された条件分岐
h = mux.match(r.Host + r.URL.Path)
}
if h == nil {
h = mux.match(r.URL.Path)
}
// ... (後続のコード)
}
handler メソッドは、受信したリクエスト r に基づいて適切な Handler を見つけ出す役割を担います。変更前は、常に mux.match(r.Host + r.URL.Path) を呼び出してホスト名を含むパターンでのマッチングを試みていました。
この変更により、if mux.hosts という条件が追加されました。
- もし
mux.hostsがtrueであれば、ホスト名を含むパターンが登録されている可能性があるため、r.Host + r.URL.Pathを使ったマッチングが実行されます。 - もし
mux.hostsがfalseであれば、ホスト名を含むパターンは一つも登録されていないことが保証されるため、この最初のマッチングステップは完全にスキップされます。これにより、不要な文字列結合 (r.Host + r.URL.Path) とマップ検索のオーバーヘッドが削減され、パフォーマンスが向上します。
Handle メソッドの変更
func (mux *ServeMux) Handle(pattern string, handler Handler) {
// ... (既存のコード)
mux.m[pattern] = muxEntry{explicit: true, h: handler}
if pattern[0] != '/' { // <-- 追加された条件
mux.hosts = true
}
// ... (後続のコード)
}
Handle メソッドは、新しいURLパターンとそれに対応するハンドラを ServeMux に登録するために使用されます。
追加された if pattern[0] != '/' という条件は、登録される pattern がスラッシュ (/) で始まらないかどうかをチェックしています。Goの net/http において、スラッシュで始まらないパターンはホスト名を含むパターン(例: example.com/path)として解釈されます。
- もし登録されるパターンがホスト名を含むパターンであれば、
mux.hostsフィールドがtrueに設定されます。 - 一度
mux.hostsがtrueに設定されると、それはServeMuxのインスタンスの寿命を通じてtrueのまま維持されます。これは、一度でもホスト名を含むパターンが登録されれば、以降のリクエスト処理でホスト名ベースのマッチングが必要になるためです。
この変更により、ServeMux は自身の内部状態(ホスト名を含むパターンが登録されているか否か)を追跡し、その情報に基づいてルーティングロジックを動的に最適化できるようになります。
関連リンク
- Go Change List 6248053: https://golang.org/cl/6248053
- Go言語
net/httpパッケージのドキュメント: https://pkg.go.dev/net/http
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語の
net/httpパッケージのソースコード syncパッケージのドキュメント- 一般的なHTTPルーティングとWebサーバーの概念に関する知識