It Made My Day

Acutally, my whole life is just one big yak shaving exercise.

Goのミドルウェアパターン

ミドルウェア is 何

ミドルウェア」という単語自体は幅広い意味を持つが、ミドルウェアパターンというのは、(どの言語を使用するかに関わらず) HTTPリクエストを処理する際にURLに関わらず行う共通の処理を設定すること。Webサーバーと実際のハンドラーの中間にあるため、「ミドルウェア」と呼ばれる。

f:id:turbofish:20220227233742p:plain

出所:drstearns.github.io

ミドルウェアは、下記のような用途を任せるレイヤとして使用されることが多い。

  • リクエストのログの記録
  • 認証処理
  • キャッシュのチェック
  • APIをクロスオリジンで呼び出し可能にする
  • その他レスポンスの圧縮とかなんらか共通の処理

Goでミドルウェアパターン

net/http 単体で実装する場合

ハンドラー(http.Handler)をラップしたミドルウェアハンドラーを定義するのが一般的なやり方っぽい。

雰囲気

mux := http.NewServeMux()  // マルチプレクサ
mux.HandleFunc("/", middleware1(middleware2(middleware3(indexHandler)))) // ハンドラー関数を登録

個人の感想だが見通しが悪くて読みづらいし、ミドルウェア用の関数がハンドラー関数のinterfaceを満たすようにするために若干冗長な実装になる。

まあ言うて若干の差ではあるし、サードパーティ製のMiddleware用ライブラリも色々あるのでそれらを組み合わせて使うという選択肢もあるけど、ginなどのWAFを使っていればより簡潔に書ける。個人的には、処理の流れをパッと見て理解しやすいと感じるからこっちが好き。

WAFを使って実装する場合

ginを使うとこんな感じ。

router := gin.Default()
router.Use(middleware1)
router.Use(middleware2)
router.Use(middleware3)
// こんな書き方もできる
// router.Use(middleware1, middleware2, middleware3)

router.GET("/", indexHandler)
router.Run()

それぞれの処理を跨いでデータを受け渡ししたい場合は、ginのコンテキストを用いて伝播させる。 ミドルウェア関数&ハンドラー関数は、router (gin.Engine)に適用した順番で実行される。ミドルウェア関数の中でc.Next()を呼ぶことで次の関数に移行する。

// middleware2の中で c.Next() が呼ばれるとここに来る
func middleware3() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("-------middleware3 Start------")
        // contextに値をセットすると、ハンドラ関数内でも使用できる
        c.Set("hoge", "fuga")
        // ハンドラ関数の処理に移行
        c.Next()
        // ハンドラ関数実行後にここに来る
        fmt.Println("-------middleware3 End------")
    }
}

ちなみに、ロガーとクラッシュからのリカバリgin.Default()で生成されるエンジンに標準搭載されている。

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

echoでも似たような書き方ができる。 ginにあったロガーとクラッシュリカバリ以外にも、BasicAuthやCORSなどのメジャーな用途については標準のミドルウェアが定義されているようだ。

e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.Logger())