今日もこちらの読書録をまとめていきます。
HTTPサーバー
本書では、以下の3つを押さえると見通しがよくなるとあります。
- Handlerインターフェース
- HandleFunc型
- ServeMux型
まずは、Handlerから見てみましょう。
Handler
https://pkg.go.dev/net/http#Handler
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
Handlerはインターフェースです。
ServeHTTP
メソッドを有していれば、Handlerというわけです。
が、ServeHTTP自体はサンプルコードではお見かけしませんね。
なぜでしょか??
HandleFunc
続いてHandleFuncですが、
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
となっております。
HandleFuncのコメントを読んでみましょう
HandleFunc registers the handler function for the given pattern in the DefaultServeMux. The documentation for ServeMux explains how patterns are matched.
HandleFuncは、DefaultServeMuxに、patternに対するハンドラ関数を登録するとありますね。 patternは、ルーティングに使用されるURLのパターンで、例えば、"/home"、"/users/{id}" などです。
まだよくわかりませんが、マルチプレクサと言っているくらいなので、patternを溜め込んで、ゴニョっているのかもなと想像できます。
では続いて、DefaultServeMux.HandleFunc
を見てみましょう。
DefaultServeMux.HandleFunc
// src/net/http/server.go; l=2116 var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux
DefaultServeMuxは、ServeMuxの型ですね。
ここだけみてもよくわからないので、ServeMuxをのぞいてみましょう。
ServeMux
src/net/http/server.go;l=2101 type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames } type muxEntry struct { explicit bool h Handler pattern string }
ServeMuxは、mapでmuxEntryを内部に持ち、muxEntryはフィールドにHandlerを持っていることがわかります。
マルチプレクサと名付けられてるワケが、なんとなく見えてきましたね。
DefaultServeMuxの正体がわかったところで、ServeMuxのHandleFuncをのぞいてみましょう。
ServeMux.HandleFunc
// go1.9.7:src/net/http/server.go;l=2300 func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) }
func(ResponseWriter, *Request)をHandlerFuncにキャストし、
// go1.9.7:src/net/http/server.go;l=1914 type HandlerFunc func(ResponseWriter, *Request)
ServeMuxのHandleの引数に渡してます。
次にServeMuxのHandleを見てみましょう。
ServeMux.Handle
func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() if pattern == "" { panic("http: invalid pattern " + pattern) } if handler == nil { panic("http: nil handler") } if mux.m[pattern].explicit { panic("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make(map[string]muxEntry) } mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} if pattern[0] != '/' { mux.hosts = true }
ありましたね!
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
で、ServeMuxのmにhandlerを渡しています。
はじめに戻りますが、
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
HandleFuncで、きちんとメソッドがregisterされていることがわかりました。
では、続いて登録されたハンドラがどのようにして起動するのか?をみていきましょう。
ServeMux.ServeHTTP
コメントからも分かる通り、ServeHTTPメソッドでリクエストを振り分けているようです。
// ServeHTTP dispatches the request to the handler whose // pattern most closely matches the request URL. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
やっていることは、RequestをServeMuxのHandlerメソッドにいれて、ServeMuxのServeHTTPを呼び出してますね。
まずは、ServeMuxのHandlerから読んでみましょう。
ServeMux.Handler
まずは、ServeMux.Handlerですが、内部的にはhandlerメソッドを読んでいるので、handlerメソッドを読んでみましょう。
// go1.9.7:src/net/http/server.go;l=2203 func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { // 略 path := cleanPath(r.URL.Path) if path != r.URL.Path { _, pattern = mux.handler(host, path) url := *r.URL url.Path = path return RedirectHandler(url.String(), StatusMovedPermanently), pattern } return mux.handler(host, r.URL.Path) }
ServeMux.handler
matchメソッドで、patternと合致しているハンドラを見つけにいってるくさいですね。
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() // Host-specific pattern takes precedence over generic ones if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return }
ServeMux.match
pathにマッチしているハンドラーを返していますね。 ServeMux構造体を一緒にみると、よりわかりやすいかと思います。ようやく、ルーティングの処理を追うことができました。
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames } 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 }
まとめ
気づけば、Goのルーティング処理を追っていました。 Writing complie in go のように、一つ一つの関数を紐解きながら理解していく感じが楽しかったです。
次号は、本書の読書録に戻りバリデーションやらをまとめていきたいと思います。
僕から以上。あったかくして寝ろよ