🐕

GoのHTTPとmiddlewareの合成

2023/01/07に公開

Golangの標準ライブラリを用いたHTTPサーバ実装では、middlewareと呼ばれる実装パターンが採用されることが多いようです。middlewareは要するに引数http.Handlerをとってhttp.Handlerを返す関数で、ハンドラの前後に何かしらの処理を挟み込むことを意図しています。

例えばWeb APIの個々のハンドラに共通する処理(ロギングや認証等)をmiddlewareに書いて整理する用途がありえそうです。middlewareについては既に様々な解説記事があります。

https://tutuz-tech.hatenablog.com/entry/2020/03/23/220326

https://journal.lampetty.net/entry/implementing-middleware-with-http-package-in-go

middlewareのネスト問題

middlewareをたくさん噛ませていくようになると、m1(m2(m3(m4(handler))))といった具合に、どんどんネストが深くなっていき可読性が下がります。この問題へはmiddlewareを合成してくれる何かを追加することで対応でき、そのための有名なライブラリにAliceがあります。

Aliceは非常にシンプルかつ軽量なのでこれ使えばいいじゃんという感じなのですが、例えばもし依存ライブラリを増やしたくない場合、エッセンス部分だけ独自実装するのはそれほど難しくありません。

もし独自実装するなら

「いくつかのmiddlewareを合成し、その結果を新たなmiddlewareとして返す」関数を実装すればよいです。

func composeMiddleware(middrewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler {
	return func(h http.Handler) http.Handler {
		for i := range middrewares {
			h = middrewares[len(middrewares)-i-1](h)
		}
		return h
	}
}

これを使うとm1(m2(m3(m4(handler))))composeMiddleware(m1, m2, m3, m4)(handler)と書き換えられます。

スライスを末尾から走査して組み立てていく形はちょっと面白いですね(もともとが関数のネストだったので内側の評価が先である事に対応しています)。

上記の実装はAliceとおそらく同等ですが、Aliceはさらにいくつかの機能を提供しているので、その点でAliceの方が便利かもしれません。

Discussion