公開日:
echoを使ってAPIを開発している際に、1リクエストに対してHTTPErrorHandlerがなぜか2回呼ばれる挙動に遭遇したので、その原因と対策について書き留めておく。
ハンドラがerrorを返したとき呼ぶメソッドを定義できる。
エラーをログに出力したり、Sentry等のエラートラッキングのシステムにエラーを送るコードを仕込めたりする。
参考
e.Use(xxx.HogeMiddleware())
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
return errors.New("エラーです")
}
})
func customHTTPErrorHandler(err error, c echo.Context) {
c.Logger().Error(err)
sentry.CaptureException(err)
}
e.HTTPErrorHandler = customHTTPErrorHandler
上記のコードでログが2回出力され、Sentryに2回エラーが送信されていた。
期待している動作はログに1回出力、Sentryに1回送信することなので、どこかで余分にHTTPErrorHandlerが呼ばれていることになる。
HogeMiddlewareの中でc.Error()メソッドを呼び出し、HTTPErrorHandlerが呼ばれていた。
echoのMiddlewareではハンドラ側でerrorが発生していたら、c.Errorを呼ぶ構成になっていることが多い。
func HogeMiddleware(opts ...Option) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if err := next(c); err != nil { // 次のMiddlewareもしくはハンドラの呼び出し
c.Error(err)
return err
}
// HogeMiddleware特有の処理
}
}
}
Errorメソッド はHTTPErrorHandlerを呼び出す。
副作用としてHTTPレスポンスをクライアントに送信(コミット)する。
// Error invokes the registered global HTTP error handler. Generally used by middleware.
// A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
// middlewares up in chain can not change Response status code or Response body anymore.
// Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
Error(err error)
Commit済みの場合は、Response().Committedの値がTrueに更新される。
Response().Committedの値を見て、Commit済みであれば早期Returnすることで重複呼び出しを防げる
func customHTTPErrorHandler(err error, c echo.Context) {
if context.Response().Committed {
return
}
c.Logger().Error(err)
sentry.CaptureException(err)
}
実はデフォルトのHTTPErrorHandler ではCommit済みであれば早期Returnする実装になっている
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
if c.Response().Committed {
return
}
}