[インデックス 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サーバーの概念に関する知識