My Favorite Life

Go there and write simply with golang. Such a person I want to be.

http Middleware in golang (Handler/HandlerFunc/Handle/HandleFunc)

業務では gRPC を使って開発しているのもあり、net/http パッケージを使用することは少ない。

時々、別プロジェクトで必要になったり、記事で見かける度に理解し直す部分を感じるので、自分用にメモ。

全ては Handler である

まずは結論とコードから。

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

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

HandlerFunc は ServeHTTP を実装しているので、Handler である。

func Handle(pattern string, handler Handler) {
    DefaultServeMux.Handle(pattern, handler)
}

DefaultServeMux は Serve によって使用される、デフォルトの ServeMux である。

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

ServeMux も ServeHTTP を実装しているので、Handler である。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

第二引数の func(ResponseWriter, *Request) は内部で HandlerFunc に変換される。
(HandlerFunc は Handler)

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

ListenAndServe(":8080", nil) のような形で handler = nil で call されると、DefaultServeMux が使用される。

この前提を持って、Middleware を見ていく。

Middleware

通常

http.Handle("/test", testHandler)

Middleware 使用

http.Handle("/test", Middleware()(testHandler))

func Middleware() func(http.Handler) http.Handler {
        return func(h http.Handler) http.Handler {
                return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                        log.Println("before")
                        defer log.Println("after")

                        h.ServeHTTP(w, r)
                })
        }
}
  • Middleware() は func(http.Handler) http.Handler を返す
    • つまり return func(h http.Handler) http.Handler {} の部分
  • Middleware()(handler) とは返ってきた func(http.Handler) http.Handler を実行する
    • func(testHandler) http.Handler となっている
  • testHandler の前後で処理を行い、testHandler.ServeHTTP(w, r) を実行し、 無名関数を http.HandlerFunc() へ変換し、新しいHandler をreturn する。

という流れ。
このつくりだと、1つの Middleware しか受け取れず使い勝手が悪いが、
func (h http.Handler, middlewares ...func(http.Handler) http.Handler)
このような形で複数受け取れる関数を用意し、range などで回すと好きなだけ実行することが可能である。
詳しくは参考リンクを参照されたい。

参考

Writing middleware in #golang and how Go makes it so much fun. | by Mat Ryer | Medium

http - The Go Programming Language