自由帳

とりとめのない学習メモです。主に Web サービスのシステム基盤や運用に関することを書いています

Go の http パッケージに関するコードを何となく読んでみた時のメモ

Go の http パッケージを使って HTTP Server を作るに当たっていろいろな書き方があるけど、例えば以下のようなコードとしたとき、各関数等が何をしているのかあまりわかっていなかったので調べた。

package main

import (
    "fmt"
    "net/http"
)

// The hello function writes a response with a greeting to the client
type helloHandler struct{}

func (h helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, world!"))
}


func main() {

    // マルチプレクサを使う場合
    mux := http.NewServeMux()
    mux.Handle("/hello", helloHandler{})

    http.ListenAndServe(":8080", mux)

    // デフォルトマルチプレクサへ http.NewServeMux.HandleFunc() を使って登録する
    //http.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request) {
    // // The "/" pattern matches everything, so we need to check
    // // that we're at the root here.
    // if req.URL.Path != "/" {
    //     http.NotFound(w, req)
    //     return
    // }
    // fmt.Fprintf(w, "Hello, world!")
    //})
    //http.ListenAndServe(":8080", nil)

    // デフォルトマルチプレクサへ http.NewServeMux.Handle() を使って登録する
    //http.Handle("/hello", helloHandler{})
    //http.ListenAndServe(":8080", nil)
}

環境

% go version
go version go1.14.2 darwin/amd64

ハンドラーの登録

どういったURLパスにリクエストが来た時、どういった処理を行うかを定義する。

DefaultServeMux

DefaultServeMux 自体は ServeMux 型の変数で、 そのポインタが DefaultServeMux へ格納される。デフォルトとなる http.ServeMux型 の変数。

これは登録されたhttp.HandleFunc関数で登録したハンドラ関数とURLのパスの対応関係が格納されるものでこれをベースにルーティングが行われるようである。

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

ServeMux

ServeMux は、HTTP リクエストマルチプレクサーの役割で、受け付けた HTTP リクエストの URL を, 登録されたパターンのリストと照合し、URL に最も近いパターンのハンドラーを呼び出す。

// ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that
// most closely matches the URL.
//
// Patterns name fixed, rooted paths, like "/favicon.ico",
// or rooted subtrees, like "/images/" (note the trailing slash).
// Longer patterns take precedence over shorter ones, so that
// if there are handlers registered for both "/images/"
// and "/images/thumbnails/", the latter handler will be
// called for paths beginning "/images/thumbnails/" and the
// former will receive requests for any other paths in the
// "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree,
// the pattern "/" matches all paths not matched by other registered
// patterns, not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the
// subtree root without its trailing slash, ServeMux redirects that
// request to the subtree root (adding the trailing slash). This behavior can
// be overridden with a separate registration for the path without
// the trailing slash. For example, registering "/images/" causes ServeMux
// to redirect a request for "/images" to "/images/", unless "/images" has
// been registered separately.
//
// Patterns may optionally begin with a host name, restricting matches to
// URLs on that host only. Host-specific patterns take precedence over
// general patterns, so that a handler might register for the two patterns
// "/codesearch" and "codesearch.google.com/" without also taking over
// requests for "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path and the Host
// header, stripping the port number and redirecting any request containing . or
// .. elements or repeated slashes to an equivalent, cleaner URL.
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
}

http.Handle()

http.Handle() は、DefaultServeMux 内へ、指定されたURLパターンのハンドラー関数を登録する。 (HTTP ハンドラーは個々のエンドポイントの要求を処理するためのもの)

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

Handler インタフェースは以下。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

http.HandleFunc()

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

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

メソッドを使う側としては記述の仕方が違うだけで、内部でやっていることは http.Handle() と同じであると思う。

http.ServeMux.HandleFunc()

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

http.HandlerFunc()

handler func(ResponseWriter, *Request) の部分は以下で HandlerFunc 型に置き換えられる。

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

muxhttp.ServeMux なので、mux.Handle の部分で http.ServeMux.Handle メソッドを呼ぶ

http.NewServeMux()

NewServeMux() は、ServeMux のコンストラクタを生成し、 ServeMux 型のポインタを返す

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

http.NewServeMux().Handle()

ServeMuxHandle() のメソッドが実装されている。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }
    if pattern[0] != '/' {
        mux.hosts = true
    }
}

muxEntryHandler と URL パターンを持った構造体。

type muxEntry struct {
    h       Handler
    pattern string
}

ServeMuxmuxEntry に map 形式でURLを登録しているように思えた。

ルーティング

(あまり読めてないので途中のメモ)

ListenAndServeで http サーバーを起動するときの第2引数をどうするかで変わる。

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

Server構造体を見てみるとhttp.ListenAndServeの第2引数がServeMux型の値が指定されている場合はHandlerが呼び出され、nilの場合ではDefaultServeMuxでルーティングが行われる

type Server struct {
    :
    Handler Handler // handler to invoke, http.DefaultServeMux if nil
    :
}

httpサーバーを実装するときに ServeMux を登録しているかどうかで ListenAndServe に指定する引数も変わると思う

http.ServeMux.ServeHTTP()

DefaultServeMuxを使用したルーティングは、ServeHTTPメソッドで行われる。 http.ServeMux型はServeHTTPメソッドを持つので、http.Handlerインターフェースを満たすため。

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

http.Handler()

上述の通り、ServeHTTPメソッドを持つインターフェース

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

http.ServeMux.Handler()

// Handler returns the handler to use for the given request,
// consulting r.Method, r.Host, and r.URL.Path. It always returns
// a non-nil handler. If the path is not in its canonical form, the
// handler will be an internally-generated handler that redirects
// to the canonical path. If the host contains a port, it is ignored
// when matching handlers.
//
// The path and host are used unchanged for CONNECT requests.
//
// Handler also returns the registered pattern that matches the
// request or, in the case of internally-generated redirects,
// the pattern that will match after following the redirect.
//
// If there is no registered handler that applies to the request,
// Handler returns a ``page not found'' handler and an empty pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    // CONNECT requests are not canonicalized.
    if r.Method == "CONNECT" {
        // If r.URL.Path is /tree and its handler is not registered,
        // the /tree -> /tree/ redirect applies to CONNECT requests
        // but the path canonicalization does not.
        if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }
        return mux.handler(r.Host, r.URL.Path)
    }
    // All other requests have any port stripped and path cleaned
    // before passing to mux.handler.
    host := stripHostPort(r.Host)
    path := cleanPath(r.URL.Path)
    // If the given path is /tree and its handler is not registered,
    // redirect for /tree/.
    if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
        return RedirectHandler(u.String(), StatusMovedPermanently), u.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)
}

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

http.ServeMux.match

// 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.Server.ListenAndServe
  • http.Server.Serve
  • http.conn.serve
  • http.serverHandler.ServerHTTP

まとめ

似たような名前のメソッド名やインタフェース、構造体があり最初は混乱したが、ハンドラーの登録とルーティングの処理があることを念頭におくと少し理解が深まった。

参考