[インデックス 13716] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージの ServeMux
に Handler
メソッドを追加するものです。これにより、ServeMux
のリクエストディスパッチロジックが、リクエストに追加の制約を課すラッパー(ミドルウェアなど)から利用可能になります。
コミット
commit e29659b3c30a79cdbd61ac6b68d5cead57ef2de7
Author: Russ Cox <rsc@golang.org>
Date: Fri Aug 31 12:16:31 2012 -0400
net/http: add (*ServeMux).Handler method
The Handler method makes the ServeMux dispatch logic
available to wrappers that enforce additional constraints
on requests.
R=golang-dev, bradfitz, dsymonds
CC=golang-dev
https://golang.org/cl/6450165
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e29659b3c30a79cdbd61ac6b68d5cead57ef2de7
元コミット内容
net/http: add (*ServeMux).Handler method
Handler
メソッドは、ServeMux
のディスパッチロジックを、リクエストに追加の制約を課すラッパーで利用できるようにします。
変更の背景
Goの net/http
パッケージは、ウェブアプリケーションを構築するための基本的なHTTP機能を提供します。その中心的なコンポーネントの一つが ServeMux
であり、これはHTTPリクエストのURLパスに基づいて適切な Handler
を選択し、ディスパッチする役割を担います。
このコミットが導入される以前は、ServeMux
がリクエストをどのように処理し、どのハンドラにディスパッチするかという内部ロジックは、主に ServeHTTP
メソッド内にカプセル化されていました。これは通常の使用シナリオでは問題ありませんが、特定の高度なユースケース、例えばリクエストが実際にハンドラに到達する前に、追加のロジック(認証、ロギング、レート制限など)を適用したい場合、直接的な介入が困難でした。
このような「ラッパー」や「ミドルウェア」の概念を実装するためには、ServeMux
が最終的にどのハンドラを選択するのかを知る必要があります。しかし、これまでのAPIでは、ServeMux
が内部的に決定したハンドラを外部から取得する直接的な手段がありませんでした。このため、開発者は ServeMux
の内部ロジックを模倣するか、より複雑な方法でミドルウェアを実装する必要がありました。
このコミットは、ServeMux
のディスパッチロジックを外部に公開する Handler
メソッドを導入することで、この課題を解決します。これにより、開発者は ServeMux
が選択するハンドラと、そのハンドラが登録されたパターンを事前に取得し、その上で独自のラッパーロジックを適用できるようになります。これは、より柔軟で強力なHTTPリクエスト処理パイプラインを構築するための重要な改善です。
前提知識の解説
このコミットを理解するためには、以下のGo言語の net/http
パッケージに関する基本的な概念を理解しておく必要があります。
-
http.Handler
インターフェース: GoのHTTPハンドラは、http.Handler
インターフェースを実装する任意の型です。このインターフェースは、単一のメソッドServeHTTP(ResponseWriter, *Request)
を持ちます。このメソッドは、HTTPリクエストを処理し、HTTPレスポンスを生成する責任を負います。 -
http.ServeMux
:ServeMux
は、HTTPリクエストのURLパスに基づいて、登録されたhttp.Handler
をディスパッチするHTTPリクエストマルチプレクサ(ルーター)です。Handle
メソッドを使用して、特定のパターン(例:/users/
,/api/v1/
)とそれに対応するハンドラを登録します。ServeMux
はhttp.Handler
インターフェースも実装しているため、http.ListenAndServe
関数に直接渡すことができます。 -
リクエストの正規化とリダイレクト:
net/http
パッケージは、URLパスの正規化を自動的に行います。例えば、/dir
のようなパスが登録されている場合、/dir/
へのリクエストは自動的に/dir
にリダイレクトされることがあります。また、..
や.
を含むパスも正規化されます。これは、URLのセマンティクスを統一し、重複したコンテンツを避けるために重要です。 -
ミドルウェア (Middleware): ミドルウェアは、HTTPリクエストとレスポンスの処理パイプラインに介入し、追加の機能(認証、ロギング、エラーハンドリングなど)を提供するソフトウェアコンポーネントです。Goの
net/http
におけるミドルウェアは通常、http.Handler
を引数に取り、新しいhttp.Handler
を返す関数として実装されます。これにより、複数のミドルウェアをチェーンのように連結して適用することができます。func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("Request: %s %s", r.Method, r.URL.Path) next.ServeHTTP(w, r) // 次のハンドラを呼び出す }) }
このコミットが導入する
Handler
メソッドは、このようなミドルウェアがServeMux
のディスパッチロジックをより深く統合し、特定のパスにマッチするハンドラを事前に知ることを可能にします。
技術的詳細
このコミットの主要な変更点は、http.ServeMux
に Handler(r *Request) (h Handler, pattern string)
メソッドが追加されたことです。このメソッドは、与えられたHTTPリクエスト r
に対して ServeMux
が最終的にディスパッチする http.Handler
と、そのハンドラが登録されているパターン文字列を返します。
変更の具体的な内容は以下の通りです。
-
muxEntry
構造体の変更:ServeMux
の内部で使用されるmuxEntry
構造体にpattern
フィールドが追加されました。type muxEntry struct { explicit bool h Handler pattern string // 新しく追加されたフィールド }
これにより、
ServeMux
が内部で管理する各ハンドラエントリに、そのハンドラが登録された元のパターンを紐付けて保持できるようになります。 -
(*ServeMux).match
メソッドの変更:match
メソッドは、与えられたパスに最も特異的にマッチするハンドラを検索する内部ヘルパー関数です。このメソッドのシグネチャが変更され、マッチしたハンドラだけでなく、そのハンドラが登録されたパターンも返すようになりました。// 変更前: func (mux *ServeMux) match(path string) Handler // 変更後: func (mux *ServeMux) match(path string) (h Handler, pattern string)
これにより、
match
の呼び出し元は、ディスパッチされるハンドラと、それに紐づくパターン情報の両方を取得できます。 -
(*ServeMux).Handler
メソッドの追加: これがこのコミットの核心です。新しいHandler
メソッドは、外部からServeMux
のディスパッチロジックを利用するための公開APIです。func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { // ... }
このメソッドは以下のロジックを実行します。
- パスの正規化とリダイレクトの処理:
r.Method != "CONNECT"
の場合、r.URL.Path
をcleanPath
関数で正規化します。もし正規化されたパスが元のパスと異なる場合、RedirectHandler
を使用して永続的なリダイレクト(HTTP 301 Moved Permanently)を行うハンドラを返します。この際、リダイレクト先のパスにマッチするパターンも取得します。 - 実際のハンドラディスパッチ: 正規化されたパス(またはCONNECTメソッドの場合は元のパス)とリクエストのホストに基づいて、内部の
mux.handler
メソッドを呼び出し、最終的なハンドラとパターンを取得します。
- パスの正規化とリダイレクトの処理:
-
(*ServeMux).handler
メソッドの変更:handler
メソッドは、ServeMux.Handler
の内部実装であり、パスがすでに正規化されていることを前提としています。このメソッドもmatch
と同様に、ハンドラとパターンを返すようにシグネチャが変更されました。// 変更前: func (mux *ServeMux) handler(r *Request) (h Handler) // 変更後: func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
また、
NotFoundHandler()
を返す場合も、空のパターン文字列""
を返すように変更されました。 -
(*ServeMux).ServeHTTP
メソッドの変更:ServeMux
がhttp.Handler
インターフェースを実装するために提供するServeHTTP
メソッドは、新しいHandler
メソッドを利用するように変更されました。// 変更前: // if r.Method != "CONNECT" { // 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) // 変更後: // h, _ := mux.Handler(r) // h.ServeHTTP(w, r)
これにより、
ServeHTTP
の内部にあったパスの正規化とリダイレクトのロジックがHandler
メソッドに集約され、コードの重複が解消され、よりクリーンになりました。 -
(*ServeMux).Handle
メソッドの変更:Handle
メソッドは、パターンとハンドラをServeMux
に登録する際に、muxEntry
にパターン文字列も保存するように変更されました。これにより、match
メソッドがパターンを返すことができるようになります。
これらの変更により、ServeMux
のディスパッチロジックがよりモジュール化され、外部からその結果(どのハンドラが選択され、どのパターンにマッチしたか)を事前に取得できるようになりました。これは、ミドルウェアやカスタムルーティングロジックを実装する際に非常に有用です。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルは以下の2つです。
-
src/pkg/net/http/server.go
:muxEntry
構造体にpattern string
フィールドが追加。(*ServeMux).match
メソッドの戻り値にpattern string
が追加。(*ServeMux).Handler
メソッドが新規追加。(*ServeMux).handler
メソッドのシグネチャと実装が変更され、pattern string
を返すように。(*ServeMux).ServeHTTP
メソッドが、新しく追加されたHandler
メソッドを利用するように変更。(*ServeMux).Handle
メソッドが、muxEntry
にpattern
を保存するように変更。
-
src/pkg/net/http/server_test.go
:TestServeMuxHandler
という新しいテスト関数が追加され、(*ServeMux).Handler
メソッドの動作を検証。serveMuxRegister
とserveMuxTests
というテストデータが定義され、様々なリクエストパスと期待されるハンドラ、パターン、HTTPステータスコードの組み合わせをテスト。- テスト用のヘルパー関数
serve
とcodeSaver
が追加。
コアとなるコードの解説
src/pkg/net/http/server.go
の変更点
// muxEntry 構造体に pattern フィールドを追加
type muxEntry struct {
explicit bool
h Handler
pattern string // 新規追加
}
// match メソッドがハンドラとパターンを返すように変更
// 最も特異的な(最長の)パターンが優先される
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern // マッチしたエントリのパターンを返す
}
}
return
}
// 新しく追加された (*ServeMux).Handler メソッド
// リクエスト r に使用するハンドラを返す
// r.Method, r.Host, r.URL.Path を参照する
// 常に nil でないハンドラを返す
// パスが正規形でない場合、正規パスにリダイレクトする内部生成ハンドラを返す
// また、リクエストにマッチする登録済みパターン、または内部生成リダイレクトの場合はリダイレクト後にマッチするパターンを返す
// リクエストに適用される登録済みハンドラがない場合、"page not found" ハンドラと空のパターンを返す
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
}
}
// 正規化されたパス(またはCONNECTメソッドの場合は元のパス)で内部ハンドラを呼び出す
return mux.handler(r.Host, r.URL.Path)
}
// handler メソッドのシグネチャと実装が変更され、ハンドラとパターンを返すように
// パスは正規形であることが前提(CONNECTメソッドを除く)
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// ホスト固有のパターンが汎用パターンよりも優先される
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
// マッチするハンドラがない場合、NotFoundHandler と空のパターンを返す
h, pattern = NotFoundHandler(), ""
}
return
}
// ServeHTTP メソッドが新しい Handler メソッドを利用するように変更
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
h, _ := mux.Handler(r) // Handler メソッドを呼び出してハンドラを取得
h.ServeHTTP(w, r) // 取得したハンドラでリクエストを処理
}
// Handle メソッドが muxEntry にパターンを保存するように変更
func (mux *ServeMux) Handle(pattern string, handler Handler) {
// ...
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} // pattern を保存
// ...
// リダイレクトハンドラを登録する場合も pattern を保存
// mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern}
}
src/pkg/net/http/server_test.go
の新規追加
このファイルは、ServeMux.Handler
メソッドの動作を検証するためのテストケースを含んでいます。
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http
import (
"net/url"
"testing"
)
// テスト用のハンドラ登録データ
var serveMuxRegister = []struct {
pattern string
h Handler
}{
{"/dir/", serve(200)},
{"/search", serve(201)},
{"codesearch.google.com/search", serve(202)},
{"codesearch.google.com/", serve(203)},
}
// 指定されたHTTPステータスコードを返すハンドラ関数を生成
func serve(code int) HandlerFunc {
return func(w ResponseWriter, r *Request) {
w.WriteHeader(code)
}
}
// ServeMux.Handler メソッドのテストケース
var serveMuxTests = []struct {
method string
host string
path string
code int // 期待されるHTTPステータスコード
pattern string // 期待されるマッチパターン
}{
// 様々なリクエストパスと期待される結果の組み合わせ
{"GET", "google.com", "/", 404, ""},
{"GET", "google.com", "/dir", 301, "/dir/"}, // リダイレクトのテスト
{"GET", "google.com", "/dir/", 200, "/dir/"},
{"GET", "google.com", "/dir/file", 200, "/dir/"},
{"GET", "google.com", "/search", 201, "/search"},
// ... 他のテストケース ...
{"CONNECT", "google.com", "/dir", 301, "/dir/"}, // CONNECT メソッドのリダイレクトテスト
{"CONNECT", "google.com", "/../search", 404, ""}, // CONNECT メソッドのパス正規化テスト
// ...
}
// TestServeMuxHandler は ServeMux.Handler メソッドをテストする
func TestServeMuxHandler(t *testing.T) {
mux := NewServeMux()
for _, e := range serveMuxRegister {
mux.Handle(e.pattern, e.h) // ハンドラを登録
}
for _, tt := range serveMuxTests {
r := &Request{
Method: tt.method,
Host: tt.host,
URL: &url.URL{
Path: tt.path,
},
}
h, pattern := mux.Handler(r) // テスト対象の Handler メソッドを呼び出す
cs := &codeSaver{h: Header{}}
h.ServeHTTP(cs, r) // 取得したハンドラを実行し、ステータスコードを保存
if pattern != tt.pattern || cs.code != tt.code {
t.Errorf("%s %s %s = %d, %q, want %d, %q", tt.method, tt.host, tt.path, cs.code, pattern, tt.code, tt.pattern)
}
}
}
// codeSaver は WriteHeader に渡されたコードを保存する ResponseWriter
type codeSaver struct {
h Header
code int
}
func (cs *codeSaver) Header() Header { return cs.h }
func (cs *codeSaver) Write(p []byte) (int, error) { return len(p), nil }
func (cs *codeSaver) WriteHeader(code int) { cs.code = code } // コードを保存
このテストコードは、様々なHTTPメソッド、ホスト、パスの組み合わせに対して ServeMux.Handler
が期待通りにハンドラとパターンを返すか、そしてそのハンドラが正しいHTTPステータスコードを生成するかを網羅的に検証しています。特に、パスの正規化とリダイレクトの挙動、およびホスト名に基づくルーティングが正しく機能していることを確認しています。
関連リンク
- Go言語
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go言語のHTTPミドルウェアに関する一般的な議論: (Web検索で「Go http middleware」などで検索すると多くの記事が見つかります)
参考にした情報源リンク
- コミットハッシュ:
e29659b3c30a79cdbd61ac6b68d5cead57ef2de7
- GitHub上のコミットページ: https://github.com/golang/go/commit/e29659b3c30a79cdbd61ac6b68d5cead57ef2de7
- Go CL (Change List): https://golang.org/cl/6450165 (これはコミットメッセージに記載されているもので、Goのコードレビューシステムへのリンクです)
- Go言語の公式ドキュメントおよびソースコード。