Go の Gin のミドルウェア

Gin のミドルウェアは、リクエストの前後に処理を挟む仕組みだ。ロギング、認証、CORS、リカバリーなど様々な用途で使われる。

ミドルウェアの基本構造

Gin のミドルウェアは gin.HandlerFunc 型の関数だ。

func MyMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // リクエスト処理前
        fmt.Println("Before request")
        
        c.Next() // 次のハンドラを実行
        
        // リクエスト処理後
        fmt.Println("After request")
    }
}

c.Next() を呼ぶと、チェーン内の次のハンドラが実行される。Next() の後に書いたコードは、後続のハンドラがすべて完了した後に実行される。

グローバルミドルウェア

すべてのルートに適用するミドルウェアは Use で登録する。

r := gin.New() // ミドルウェアなしのエンジン

r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(MyMiddleware())

gin.Default() は gin.Logger() と gin.Recovery() が最初から設定されている。

グループへのミドルウェア適用

特定のルートグループにだけミドルウェアを適用できる。

// 認証が必要なルート
authorized := r.Group("/admin")
authorized.Use(AuthRequired())
{
    authorized.GET("/dashboard", dashboard)
    authorized.POST("/settings", updateSettings)
}

// 認証不要のルート
public := r.Group("/public")
{
    public.GET("/info", getInfo)
}

ロギングミドルウェアの実装

リクエストの処理時間を計測するミドルウェアを作ってみる。

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        
        c.Next()
        
        latency := time.Since(start)
        status := c.Writer.Status()
        
        log.Printf("[%d] %s %s - %v",
            status, c.Request.Method, path, latency)
    }
}

c.Next() の前後で時間を計測することで、ハンドラの処理時間がわかる。

認証ミドルウェアの例

func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        
        if token == "" {
            c.AbortWithStatusJSON(401, gin.H{
                "error": "Authorization header required",
            })
            return
        }
        
        user, err := validateToken(token)
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{
                "error": "Invalid token",
            })
            return
        }
        
        // 後続のハンドラで使えるように値をセット
        c.Set("user", user)
        c.Next()
    }
}

c.Abort() や c.AbortWithStatusJSON() を呼ぶと、後続のハンドラは実行されない。

コンテキストへの値の受け渡し

ミドルウェアで設定した値は、後続のハンドラで取得できる。

// ミドルウェアで設定
c.Set("userID", 123)

// ハンドラで取得
userID, exists := c.Get("userID")
if exists {
    id := userID.(int)
}

// 型安全に取得するヘルパー
func GetUserID(c *gin.Context) (int, bool) {
    if v, ok := c.Get("userID"); ok {
        if id, ok := v.(int); ok {
            return id, true
        }
    }
    return 0, false
}

ミドルウェアの実行順序

ミドルウェアは登録した順に実行される。

r.Use(A())
r.Use(B())
r.Use(C())

この場合、リクエスト時は A → B → C の順、レスポンス時は C → B → A の順(Next() 以降の処理)になる。

A の前処理

B の前処理

C の前処理

ハンドラ実行

C の後処理

B の後処理

A の後処理

組み込みミドルウェア

Gin には便利な組み込みミドルウェアがある。

gin.Logger()

リクエストのログを出力する。メソッド、パス、ステータス、レイテンシなどが記録される。

gin.Recovery()

panic が発生しても 500 エラーを返してサーバーを継続させる。本番環境では必須。

サードパーティ製のミドルウェアも豊富で、CORS、JWT 認証、レートリミットなど多くの機能がパッケージとして提供されている。