この章について
前章にて「サーバー起動時にhttp.ListenAndServe(":8080", nil)
とした場合、ルーティングハンドラはデフォルトでnet/http
パッケージ変数DefaultServeMux
が使われる」という話をしました。
ここでは、このDefaultServeMux
は何者なのかについて詳しく説明したいと思いいます。
DefaultServeMux
の定義・役割
定義
DefaultServeMux
は、net/http
パッケージ内に存在する公開グローバル変数です。
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
ServeMux
型の型定義は以下のようになっています。
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
役割
定義だけ見ても、DefaultServeMux
で何を実現しているのかわかりにくいと思います。
実はDefaultServeMux
の役割は、ServeMux
のm
フィールドが中心部分です。
m
フィールドのmap
には、「URLパスー開発者がhttp.HandleFunc
関数で登録したハンドラ関数」の対応関係が格納されています。
Goのhttpサーバーは、http.ListenAndServe
の第二引数nil
の場合ではDefaultServeMux
内に格納された情報を使って、ルーティングを行っているのです。
ハンドラの登録
まずは、DefaultServeMux
に開発者が書いたハンドラが登録されるまでの流れを追ってみましょう。
開発者が書いたfunc(w http.ResponseWriter, _ *http.Request)
という形のハンドラを登録するには、http.HandleFunc
関数に対応するURLパスと一緒に渡してやることになります。
func main() {
h1 := func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello from a HandleFunc #1!\n")
}
http.HandleFunc("/", h1) // パス"/"に、ハンドラh1が対応
log.Fatal(http.ListenAndServe(":8080", nil))
}
http.HandleFunc
関数
1. それでは、http.HandleFunc
関数の中身を見てみましょう。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
内部では、DefaultServeMux
(http.ServeMux
型)のHandleFunc
メソッドを呼び出しているだけです。
http.ServeMux.HandleFunc
メソッド
2. それでは、http.ServeMux.HandleFunc
メソッドの中身を見てみましょう。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
内部で行っているのは主に2つです。
-
func(ResponseWriter, *Request)
型を、http.HandlerFunc
型にキャスト - ↑で作った
http.HandlerFunc
型を引数にして、http.ServeMux.Handle
メソッドを呼ぶ
http.ServeMux.Handle
メソッド
3. それでは、http.ServeMux.Handle
メソッドの中を今度は覗いてみましょう。
func (mux *ServeMux) Handle(pattern string, handler Handler) {
// (一部抜粋)
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
}
ここで、DefaultServeMux
のm
フィールドに「URLパスー開発者がhttp.HandleFunc
関数で登録したハンドラ関数」の対応関係を登録しています。
DefaultServeMux
によるルーティング
ここからはDefaultServeMux
から、先ほど内部に登録したハンドラを探し当てるまでの処理を辿ってみましょう。
http.ServeMux
のServeHTTP
メソッド
1. DefaultServeMux
を使用したルーティング依頼は、ServeHTTP
メソッドで行われます。
このことは、前章の終わりがhttp.Handler
インターフェースのServeHTTP
メソッドだったことを思い出してもらえると、このことが理解できると思います。
http.ServeMux
型はServeHTTP
メソッドを持つので、http.Handler
インターフェースを満たします。
それでは、http.ServeMux.ServeHTTP
メソッドの中身を見てみましょう。
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// 一部抜粋
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
ここで行っているのは次の2つです。
-
mux.Handler
メソッドで、リクエストにあったハンドラ(http.Handler
インターフェース)を取り出す - ↑で取り出したハンドラの
ServeHTTP
メソッドを呼び出す
http.ServeMux
のHandler
メソッド
1-1. mux.Handler
メソッド内では、どのようにリクエストに沿ったハンドラを取り出しているのでしょうか。
それを知るために、http.ServeMux.Handler
の中身を見てみましょう。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// 一部抜粋
return mux.handler(host, r.URL.Path)
}
最終的に非公開メソッドhandler
メソッドが呼ばれています。
http.ServeMux
のhandler
メソッド
1-2. http.ServeMux.handler
の中身は、以下のようになっています。
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
// 一部抜粋
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
http.ServeMux.match
メソッドから得られるハンドラが返り値になっていることが確認できます。
http.ServeMux
のmatch
メソッド
1-3. そしてこのhttp.ServeMux.match
メソッドが、「URLパス→ハンドラ」の対応検索をDefaultServeMux
のm
フィールドを使って行っている部分です。
// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
http.Handler.ServeHTTP
メソッドの実行
2. http.ServeMux.match
関数から得られた、ユーザーが登録したハンドラ関数(http.Handler
インターフェース型)は、最終的には自身のServeHTTP
メソッドによって実行されることになります。
// 再掲
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// 一部抜粋
h, _ := mux.Handler(r) // mux.match関数によってハンドラを探す
h.ServeHTTP(w, r) // 実行
}
まとめ
ルーティングハンドラであるDefaultServeMux
と、ユーザーが登録したハンドラ関数の対応関係は、以下のようにまとめられます。
次章予告
次章では、「ルーティングハンドラによって取り出されたユーザー登録ハンドラ内で、どのようにレスポンスを返す処理を行っているのか」について掘り下げていきます。