Goで始めるMiddleware その2

前回記事を書いたがもう少し詳しく書く。

takayukinakata.hatenablog.com

なぜGoでMiddlewareを書きたいのか

前回書き忘れていたところ。

おそらくだがフレームワークを使用している場合には、フレームワークがよろしくやってくれるので自分でMiddlewareをどうこうしたいというモチベーションはあまりない気がする。しかし、あくまで個人の主観だが、標準パッケージが利用しやすいGoでは、シンプルなAPIなどであればフレームワークを使用せずに標準のnet/httpを使用して書きたいケースなどは十分あり得るはずである。そして、標準のnet/httpを使用して書く場合には自分でMiddlewareまわりを実装する必要が発生するはずである(パッケージを使用して実装するのも含め)。このため、GoにおいてはMiddlewareを書きたくなるモチベーションが生まれることは十分あり得る。

前回のおさらい

GoでシンプルにMiddlewareを実装すると以下のようになる。

Middlewares in Go: Best practices and examples – Nicolas Merouze

package main

import (
    "fmt"
    "net/http"
)

// indexHandler ...
func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Hello, middleware!")
}

// aboutHandler ...
func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("This is midlleware test!!")
}

// middleware1 ...
func middleware1(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("[START] middleware1")
        next.ServeHTTP(w, r)
        fmt.Println("[END] middleware1")
    }
}

// middleware2 ...
// middleware3 ...

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", middleware1(middleware2(middleware3(indexHandler))))
    mux.HandleFunc("/about", middleware1(middleware2(middleware3(aboutHandler))))

    http.ListenAndServe(":8888", mux)
}

このようにMiddlewareを入れ子にして実装することで、例えば複数のMiddlewareを追加したい場合も容易に実装できる。

もっといい感じにMiddlewareを書くには

GoでMiddlewareを始めることができた。Middlewareの追加もできた。

しかし、追加するMiddlewareがさらに増えるとめんどくさそうなのは容易にわかると思う。

ではこの場合はどう実装したらいいだろうか。解の一つとしてMiddlewareスタックのようなものを作るというのがある。

package main

import (
    "fmt"
    "net/http"
)

// indexHandler ...
// aboutHandler ...

// middleware1 ...
func middleware1(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("[START] middleware1")
        next.ServeHTTP(w, r)
        fmt.Println("[END] middleware1")
    }
}

// middleware2 ...
// middleware3 ...

func main() {
    middlewares := newMws(middleware1, middleware2, middleware3)

    mux := http.NewServeMux()
    mux.HandleFunc("/", middlewares.then(indexHandler))
    mux.HandleFunc("/about", middlewares.then(aboutHandler))

    http.ListenAndServe(":8888", mux)
}

type middleware func(http.HandlerFunc) http.HandlerFunc

type mwStack struct {
    middlewares []middleware
}

func newMws(mws ...middleware) mwStack {
    return mwStack{append([]middleware(nil), mws...)}
}

func (m mwStack) then(h http.HandlerFunc) http.HandlerFunc {
    for i := range m.middlewares {
        h = m.middlewares[len(m.middlewares)-1-i](h)
    }
    return h
}

比較するとこうなる。Middlewareの入れ子を何回も書く必要がなくなる。

// 前
func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", middleware1(middleware2(middleware3(indexHandler))))
    mux.HandleFunc("/about", middleware1(middleware2(middleware3(aboutHandler))))

    http.ListenAndServe(":8888", mux)
}

// 後
func main() {
    middlewares := newMws(middleware1, middleware2, middleware3)

    mux := http.NewServeMux()
    mux.HandleFunc("/", middlewares.then(indexHandler))
    mux.HandleFunc("/about", middlewares.then(aboutHandler))

    http.ListenAndServe(":8888", mux)
}

パッケージを使うと

ではパッケージを使うとどうだろうか。ここではAliceとnegroniを紹介する。

Alice

github.com

Aliceを使用すると前述したMiddlewareスタックのようなものを容易に実装できる(というか元ネタ)。以下、Aliceの説明を引用する。

Alice provides a convenient way to chain your HTTP middleware functions and the app handler.

In short, it transforms

Middleware1(Middleware2(Middleware3(App)))

to

alice.New(Middleware1, Middleware2, Middleware3).Then(App)

コードを見ればわかるが考え方的には前述したMiddlewareスタックのようなものをalice.New(Middleware1, Middleware2, Middleware3)で作ってから.Then(App)でハンドラを返すようになっている。Aliceを使用すると実装は以下のようになる。

package main

import (
    "fmt"
    "net/http"

    "github.com/justinas/alice"
)

// indexHandler ...
// aboutHandler ...

// middleware1 ...
func middleware1(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("[START] middleware1")
        next.ServeHTTP(w, r)
        fmt.Println("[END] middleware1")
    })
}

// middleware2 ...
// middleware3 ...

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", indexHandler)
    mux.HandleFunc("/about", aboutHandler)

    chain := alice.New(middleware1, middleware2, middleware3)

    http.ListenAndServe(":8888", chain.Then(mux))
}

negroni

github.com

negroniも同様にMiddlewareスタックのようなものを扱うが、これまで紹介したものと違い実装する側がそのスタックを意識しなくてよい。negroni独自にHandlerインターフェースを用意しており、これに沿って実装すれば容易にMiddlewareの追加もできる。また、デフォルトでロガーやリカバリー処理(negroni.Classic())を用意してくれており、比較的リッチなパッケージとなっている。

https://github.com/urfave/negroni#negroniclassic

negroniを使用すると実装は以下のようになる。

package main

import (
    "fmt"
    "net/http"

    "github.com/urfave/negroni"
)

// indexHandler ...
// aboutHandler ...

// middleware1 ...
func middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    fmt.Println("[START] middleware1")
    next(w, r)
    fmt.Println("[END] middleware1")
}

// middleware2 ...
// middleware3 ...

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", indexHandler)
    mux.HandleFunc("/about", aboutHandler)

    n := negroni.New()
    n.Use(negroni.HandlerFunc(middleware1))
    n.Use(negroni.HandlerFunc(middleware2))
    n.Use(negroni.HandlerFunc(middleware3))
    n.UseHandler(mux)
    http.ListenAndServe(":8888", n)
}

感想

Goを書き始めてから初めてMiddlewareを意識した。最初はよくわからなかったが実際に書いてみるとそんなに怖くなかった。とはいえGoを書き始めて間もないので他によさげなパッケージがあれば教えてほしいです。

書いたコードはここに置きました。

github.com