【Go】gorilla/muxでミドルウェア管理してみる

オリジナルのThe Go gopher(Gopherくん)は、Renée Frenchによってデザインされました。

こんにちは!eurekaのAPIチームでエンジニアをやっているrikiiです。

今回はGoのWebアプリケーションのミドルウェア管理について紹介したいと思います。

そもそもミドルウェアとは?

https://mattstauffer.com/blog/laravel-5.0-middleware-filter-style/#what-is-middleware

ミドルウェアは様々な意味があると思いますが、ここで紹介しているのは、Webアプリケーションフレームワークなどに搭載されているような、アプリケーションの処理の前後でなんらかの処理をする機構のことです。例えばSession管理や認証処理などです。

GoではginなどのWebアプリケーションフレームワークや、negroniのようなミドルウェア専用のライブラリなどミドルウェアを管理するものはたくさんありますが、ルーティングにはgorilla/muxを使う方も多いと思うので、ここではgorillaで簡単にミドルウェア管理する方法を紹介します。

gorilla/csrfを使ってCSRF対策をしてみる

例えば、WebAPIでCSRF対策をしたい場合があるかと思います。対策方法は様々ありますが、ここでは一般的なCSRFトークンを利用して対策してみようと思います。

gorilla/csrfパッケージを使用すると割と簡単に対策が可能だったのでこちらを紹介します。例では gorrila/mux を使っていますが、他にも大体のフレームワークやnegroni等でも使えます。

例: Get localhost:3001/user のAPIが叩かれた場合

func main() {
r := mux.NewRouter()
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
r.Use(csrfMiddleware)
c := &controllers.UserController{}
r.HandleFunc("/", c.Get).Methods("GET")
http.ListenAndServe(":3001", r)
}
type UserController struct {}
func (c *UserController) Get(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-CSRF-Token", csrf.Token(r))
w.WriteHeader(200)
}

これだけで、レスポンスのヘッダーにCSRFトークンが入ります。

あとはクライアント側で、受け取ったリクエストヘッダーの X-CSRF-Token をレスポンスヘッダーの X-CSRF-Token にセットして送ってもらえば、トークン認証もしてくれます。

また、 Useメソッドは下記のような実装になっており、ミドルウェアを複数渡すことも可能です。

type MiddlewareFunc func(http.Handler) http.Handler
func (r *Router) Use(mwf ...MiddlewareFunc) {
for _, fn := range mwf {
r.middlewares = append(r.middlewares, fn)
}
}

使いたいミドルウェアを処理したい順番にr.Use() の引数に渡すだけで順次実行されます。

このように簡単にミドルウェアを管理することができます。

次は自作ミドルウェアの例を紹介します。

自作ミドルウェアを作ってみる

type MiddlewareFunc func(http.Handler) http.Handler

先程の Useメソッドを見返すと MiddlewareFunc型の関数を用意すればいいことがわかるかと思います。

例えば、簡単なCORSに対応した、Access-Control-Allow-Origin ヘッダーに適当なURLを入れるだけの簡単なミドルウェアを作ってみたいと思います。

func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := "localhost:3001"
w.Header().Set("Access-Control-Allow-Origin", origin)
next.ServeHTTP(w, r)
})
}
func main() {
r := mux.NewRouter()
r.Use(CORSMiddleware)
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
r.Use(csrfMiddleware)
c := &controllers.UserController{}
r.HandleFunc("/", c.Get).Methods("GET")
http.ListenAndServe(":3001", r)
}

これでレスポンスヘッダーに Access-Control-Allow-Origin に値を入れることができます。

渡した順番に実行されるので、↓の順番で実行されます。

1. CORSMiddleware
2. csrfMiddleware

処理される順番が結構重要になってくるので少し実装を見てみたいと思います。

func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := "localhost:3001"
w.Header().Set("Access-Control-Allow-Origin", origin)
next.ServeHTTP(w, r)
})
}

引数の next http.Handlerは次に実行予定のミドルウェアの関数が入ってきます。

r.Use(CORSMiddleware)
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
r.Use(csrfMiddleware)

↑の順番で渡したので、今回の例でいうとcsrfMiddleware が入ります。

next.ServeHTTP(w, r)

実際には↑の処理で、csrfMiddlewareのServeHTTPが実行されています。

少しわかりにくいですが、middleware1(middleware2(middleware3(...))) のように入れ子のような形で引数の next.ServeHTTP メソッドが実行されて、順次レスポンスに書き込む処理が実行されています。

なので next.ServeHTTP(w,r) を呼ばないと次以降のミドルウェアが実行されなくなってしまうので、必ず忘れずに呼ぶ必要があります。

まとめ

外部パッケージ(gorilla/csrf)と自作のミドルウェアを管理する方法を見てきました。

どちらも簡単に管理できたのは、 gorilla/muxのミドルウェア管理(他のフレームワークもですが)が全てhttp.Handler interface依存になっており汎用的に使える設計になっているからです。

今回はGoでのミドルウェア管理を少しみてきましたが、他の言語のWebアプリケーションフレームワークやライブラリでもほぼ同じ仕組みで動いているので知っておくとよいかと思います。