はじめに
ginでAPIなんかを書いていると、エラーを返すとき、
if err != nil {
log.Print(err)
c.AbortWithJSON(http.StatusInternalServerError, gin.H{
"Error": "Request failed"
})
}
のようなコードが(気をつけないと)あちこちに散らばってしまいます。
このままだと、例えばログの表示方法や保存方法が変わることになったときなどに一つひとつ手直ししないといけません。
そこで、ミドルウェアを使ってエラー時の処理をまとめたいと思います。
バージョン
Go : v1.12
gin : v1.5
ミドルウェアについて
ginではミドルウェアを設定でき、通常のハンドラーの前後に任意の処理を入れることができます。
例えば、
func main() {
r := gin.Default()
r.Use(someMiddleware()) // ミドルウェアを登録
r.GET("/", func(c *gin.Context) {
log.Println("2nd")
c.JSON(200, gin.H{"Message": "Hello"})
})
r.Run()
}
func someMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
log.Println("1st")
c.Next() // ハンドラーを実行
log.Println("3rd")
}
}
とすると、ログは、1st
, 2nd
, 3rd
の順番で出てきます。
見ての通り、c.Next
の前に書いたものはハンドラーの前に、後に書いたものはハンドラーの後に実行されます。
エラーハンドリング
エラーの投げ方
gin のコンテキストは、Error
メソッドを使ってエラーが設定できます。
さらにこのメソッドは、元のエラーとTypeやMetaなどの追加情報を含むgin.Error型を返します。
Typeには、gin.ErrorTypePublic
、ErrorTypePrivate
などがあり、デフォルトではErrorTypePrivate
です。
Metaの方はinterface{}型なので任意のデータを設定できます。
c.Error(err) // simple error
c.Error(err).SetType(gin.ErrorTypePublic)
c.Error(err).SetType(gin.ErrorTypePrivate).SetMeta("more data")
リクエストの処理中に何かエラーが起きたときは、AbortWithJSON
などとする代わりに、次のようにコンテキストにエラーを設定して一度ハンドラーから抜けます。
if err != nil {
c.Error(err).SetType(gin.ErrorTypePublic)
return
}
エラーミドルウェア
エラーがセットされたコンテキストをミドルウェアで処理するようにします。
次のようなミドルウェアを作成して、r.Use(errorMiddleware())
というように登録してください。
func errorMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
err := c.Errors.ByType(gin.ErrorTypePublic).Last()
if err != nil {
log.Print(err.Err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"Error": err.Error()
})
}
}
}
ここでは、コンテキストにセットされたエラーの中でも、TypeがPublicで、最後のものを取得しています。
エラーを取得した後には、ログの出力やSentryへの送信などといった任意の処理を入れることができます。
また、MetaにHTTPのステータスコードやレスポンスを入れたり、TypeのPrivateとPublicで処理を分けたりなどといったこともできます。
これで、エラーの処理をミドルウェアにまとめることができました!