Zenn
📌

Goのmiddlewareとは?Handlerをラップして共通処理を組み込む仕組みを理解する

に公開

middlewareとは?

handlerの前後に共通処理を追加する仕組み

Goでは、handlerをラップすることで、共通処理(ミドルウェア)を柔軟に差し込めるようになります。例えば、次のような処理がmiddlewareに適してます。

  • contextに値を入れる
  • ログを出力する
  • 認証トークンを検証する

認証ミドルウェアの定義

package middleware

import(
 "context"
 "net/http"
 "myapp/contextkey"    
)

// AuthMiddlewareはトークンを確認し、ユーザーIDをcontextに格納する
func AuthMiddleware(next http.Handler) http.Handler{
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
 // リクエストのヘッダーから認証トークンを取得
 token := r.Header.Get("Authorization")
 if token == "" || token != "Bearer valid-token"{
    // レスポンスにerrorのstatuscodeと文言を書き込み
    http.Error(w, "Unauthorized", http.StatusUnauthorized)
    return
    }
    //例userID(実際はDBから取得する)
    userID := "user-123"
    // contextにuserIdを追加
    ctx := context.WithValue(r.Context(), contextkey.UserIDKey,userID)
    // 次のhandlerへ渡す
    next.ServeHTTP(w, r.WithContext(ctx))
 })
}

middlewareをラップして handlerに適用する

mux.Handle("/api/user/profile",
  middleware.LoggingMiddleware(
    middleware.AuthMiddleware(
      http.HandlerFunc(handler.GetUserProfile),
    ),
  ),
)

このようにhandlerをネストしていくことで処理を追加できますが、ネストが深くなると可読性が下がるという課題があります。

チェーン構成で読みやすくする

ChainMiddleware関数を定義して、middlewareを順番に適用できるようにすると、コードの見通しが良くなります。

// middlewares...func(http.Handler)http.Handlerは、複数のmiddleware引数を可変超引数として受け取り、各Middlewareはhttp.Handlerを受け取って、http.Handlerを返す関数である必要がありますという意味
func ChainMiddleware(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
  for i := len(middlewares) - 1; i >= 0; i-- {
    h = middlewares[i](h)
  }
  return h
}

使い方

mux.Handle("/api/user/profile", ChainMiddleware(
  http.HandlerFunc(handler.GetUserProfile),
  middleware.AuthMiddleware,
  middleware.LoggingMiddleware,
))

このように書くことで、処理の流れを上から自然に読めるようになります。

まとめ

Goのmiddlewareは共通処理を責務ごとに分離し、handlerに柔軟に組み込める仕組みです。

Discussion

ログインするとコメントできます