んだ日記

ndaDayoの技術日記です

『実用 Go言語』を読んでみた。HTTPルーティング編

今日もこちらの読書録をまとめていきます。

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

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 のように、一つ一つの関数を紐解きながら理解していく感じが楽しかったです。

次号は、本書の読書録に戻りバリデーションやらをまとめていきたいと思います。

僕から以上。あったかくして寝ろよ