post Image
GolangのWebフレームワークginのmiddlewareについての覚書

前置き

最近Golangを用いたWebAPIを開発しています。
Webフレームワークにはginを利用しているのですが、そのginにおけるmiddlewareについてなかなかまとまった資料などが見当たらなかったため、実際に動かしながら確認した動きについてまとめておこうと思います。

バージョンなど

  • Golang:1.10.1
  • gin:1.2

基本動作

サンプルソース

ginにおけるmiddlewareの挙動を理解するために以下のようなソースを用意します。
localhost:8080にアクセスした時にmessageを持つ構造を返しています。
またsampleMiddleware()をmiddlewareとして指定しています。

package main

import (
    "log"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.Use(sampleMiddleware())
    r.GET("/", func(c *gin.Context) {
        log.Println("main logic")
        c.JSON(200, gin.H{"message": "Hello!"})
    })
    r.Run()
}

func sampleMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        log.Println("before logic")
        c.Next()
        log.Println("after logic")
    }
}

挙動

上記ファイルを用意して、

go run main.go
curl localhost:8080

とコマンドを打つと、以下のようなレスポンスが返ってきます。

{"message":"Hello!"}

そしてこの時のginのログは以下のように出力されています。

before logic
main logic
after logic
[GIN] 2018/05/20 - 21:14:48 | 200 |      89.316µs |             ::1 | GET      /

説明

このログ出力から分かることは、middlewareとして指定したメソッドにおいて、

  • c.Next()の前に記述した処理はルーティング内の処理の前に実行される
  • c.Next()の後に記述した処理はルーティング内の処理の後に実行される

ということです。
これで共通の処理を前後にそれぞれ挟み込むことができるわけです。

middlewareの適用の仕方について

先の例では以下のようにmiddlewareを使用しました。

r.Use(sampleMiddleware())

全てのルーティングに共通して処理させたい場合は問題ないのですが、特定のルーティングの時だけmiddlewareを呼びたいこともあるでしょう。
そんな時は以下のようにもmiddlewareを使うこともできます。

サンプルソース

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        log.Println("main logic")
        c.JSON(200, gin.H{"message": "Hello!"})
    })
    r.GET("/a", sampleMiddleware(), func(c *gin.Context) {
        log.Println("main logic")
        c.JSON(200, gin.H{"message": "Welcome!"})
    })
    r.Run()
}
// 省略

 挙動

上記ソースを動かして、/と/aにアクセスするとginのログは以下のように出力されます。/aにアクセスした時だけmiddlewareの処理が実行されていることが分かると思います。

main logic
[GIN] 2018/05/20 - 21:26:10 | 200 |       300.4µs |             ::1 | GET      /
before logic
main logic
after logic
[GIN] 2018/05/20 - 21:26:20 | 200 |     251.709µs |             ::1 | GET      /a

 Groupを利用したmiddlewareの適用

特定のルーティングにおいてmiddlewareを実行したいという時、上で説明したやり方で可能ですが、その特定のルーティングが複数ある場合はGroupルーティングを使用した方法が適切です。ログイン済みユーザーのみがアクセスできるルートを用意するのに便利です。

func main() {
    r := gin.Default()
    sampleGroup := r.Group("/")
    sampleGroup.Use(sampleMiddleware())
    {
        sampleGroup.GET("/b", func(c *gin.Context) {
            c.JSON(200, gin.H{"message": "in group1"})
        })
        sampleGroup.GET("/c", func(c *gin.Context) {
            c.JSON(200, gin.H{"message": "in group2"})
        })
    }
    r.GET("/", func(c *gin.Context) {
        log.Println("main logic")
        c.JSON(200, gin.H{"message": "Hello!"})
    })
    r.Run()
}

このように記述することで、/bまたは/cにアクセスされた時にのみmiddlewareを実行することが可能になります。
ちなみにmiddlewareを適用するには以下のように書くことも可能です。

sampleGroup := r.Group("/", sampleMiddleware())
{
    sampleGroup.GET("/b", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "in group1"})
    })
    sampleGroup.GET("/c", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "in group2"})
    })
}

処理をmiddleware内で終了させたい

例えば認証処理などをmiddlewareにて行い、もし不適切なリクエストであった場合、エラーレスポンスを返したいとして以下のような記述をしたとします。

package main

import (
    "log"
    "strconv"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.Use(sampleMiddleware())
    r.GET("/:id", func(c *gin.Context) {
        log.Println("main logic")
        c.JSON(200, gin.H{"message": "Hello!"})
    })
    r.Run()
}

func sampleMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        id, _ := strconv.Atoi(c.Param("id"))
        if id == 0 {
            c.JSON(400, gin.H{"message": "invalid id"})
        }
    }
}

しかしこのような書き方だと、localhost:8080/0にアクセスした際のレスポンスは以下のようになります。

{"message":"invalid id"}{"message":"Hello!"}

要するにエラーコードとエラーメッセージを返しながらも以降の処理が継続されていることになります。
ログイン済みのユーザーにだけコンテンツを見せたい時などはこれでは困ります。
そういった場合はAbortを使うことで対処できます。

func sampleMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        id, _ := strconv.Atoi(c.Param("id"))
        if id == 0 {
            c.JSON(400, gin.H{"message": "invalid id"})
            c.Abort()
        }
    }
}

この場合、localhost:8080/0にアクセスすると、エラーメッセージのみが返ってきます。

{"message":"invalid id"}

Abortを使うことによって、middlewareにおいて以降の処理を通すことなくレスポンスを返すことができるようになります。

まとめ

お疲れ様でした。Golangはやはり日本語資料が少なく、基本的な機能の実装においても調査の時間が取られてしまうことが多いですね。頑張りましょう。
記事内に不適切・不十分な説明がございましたら、ご指摘いただけますと助かります。
閲覧いただきありがとうございました。


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。