Chapter 04

デフォルトでのルーティング処理の詳細

さき(H.Saki)
さき(H.Saki)
2021.09.07に更新

この章について

前章にて「サーバー起動時に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

出典:net/http/server.go

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
}

出典:net/http/server.go

役割

定義だけ見ても、DefaultServeMuxで何を実現しているのかわかりにくいと思います。

実はDefaultServeMuxの役割は、ServeMuxmフィールドが中心部分です。
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))
}

1. http.HandleFunc関数

それでは、http.HandleFunc関数の中身を見てみましょう。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

出典:net/http/server.go

内部では、DefaultServeMux(http.ServeMux型)のHandleFuncメソッドを呼び出しているだけです。

2. http.ServeMux.HandleFuncメソッド

それでは、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))
}

出典:net/http/server.go

内部で行っているのは主に2つです。

  1. func(ResponseWriter, *Request)型を、http.HandlerFunc型にキャスト
  2. ↑で作ったhttp.HandlerFunc型を引数にして、http.ServeMux.Handleメソッドを呼ぶ

3. http.ServeMux.Handleメソッド

それでは、http.ServeMux.Handleメソッドの中を今度は覗いてみましょう。

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	// (一部抜粋)
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
}

出典:net/http/server.go

ここで、DefaultServeMuxmフィールドに「URLパスー開発者がhttp.HandleFunc関数で登録したハンドラ関数」の対応関係を登録しています。

DefaultServeMuxによるルーティング

ここからはDefaultServeMuxから、先ほど内部に登録したハンドラを探し当てるまでの処理を辿ってみましょう。

1. http.ServeMuxServeHTTPメソッド

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)
}

出典:net/http/server.go

ここで行っているのは次の2つです。

  1. mux.Handlerメソッドで、リクエストにあったハンドラ(http.Handlerインターフェース)を取り出す
  2. ↑で取り出したハンドラのServeHTTPメソッドを呼び出す

1-1. http.ServeMuxHandlerメソッド

mux.Handlerメソッド内では、どのようにリクエストに沿ったハンドラを取り出しているのでしょうか。
それを知るために、http.ServeMux.Handlerの中身を見てみましょう。

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    // 一部抜粋
	return mux.handler(host, r.URL.Path)
}

出典:net/http/server.go

最終的に非公開メソッドhandlerメソッドが呼ばれています。

1-2. http.ServeMuxhandlerメソッド

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
}

出典:net/http/server.go

http.ServeMux.matchメソッドから得られるハンドラが返り値になっていることが確認できます。

1-3. http.ServeMuxmatchメソッド

そしてこのhttp.ServeMux.matchメソッドが、「URLパス→ハンドラ」の対応検索をDefaultServeMuxmフィールドを使って行っている部分です。

// 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, ""
}

出典:net/http/server.go

2. http.Handler.ServeHTTPメソッドの実行

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と、ユーザーが登録したハンドラ関数の対応関係は、以下のようにまとめられます。

次章予告

次章では、「ルーティングハンドラによって取り出されたユーザー登録ハンドラ内で、どのようにレスポンスを返す処理を行っているのか」について掘り下げていきます。