【问题标题】:How to handle errors in Gin middleware如何处理 Gin 中间件中的错误
【发布时间】:2021-12-25 03:33:26
【问题描述】:

我想抓取每条路由上的所有 http 错误,而不是每次都重写,如果 400 然后如果 404 然后如果 500 然后等等......所以我在每个路由处理程序中有一个 ErrorHandler() 函数:

func (h *Handler) List(c *gin.Context) {
    movies, err := h.service.ListService()

    if err != nil {
        utils.ErrorHandler(c, err)
        return
    }

    c.JSON(http.StatusOK, movies)
}

这个函数看起来像这样:

func ErrorHandler(c *gin.Context, err error) {
    if err == ErrNotFound {
        // 404
        c.JSON(http.StatusNotFound, gin.H{"error": ErrNotFound.Error()})
    } else if err == ErrInternalServerError {
        // 500
        c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError.Error()})
    } // etc...
}

ErrNotFoundErrInternalServerError 只是像这样初始化的全局变量:

var ErrNotFound = errors.New(http.StatusText(http.StatusNotFound))  // 404

我想知道我是否做得对,或者是否有更好的方法来做到这一点,比如抓住中间件中的错误并直接返回响应?

使用 node.js,我能够在中间件参数中发送 err 并像这样使用它:

app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
    if (err instanceof HttpError) {
        res.status(err.status).json({error: err.message});
      } else if (err instanceof Error) {
        res.status(500).json({error: err.message});
      } else {
        res.status(500).send("Internal Server Error");
      }
});

有类似的吗?

【问题讨论】:

    标签: go error-handling go-gin


    【解决方案1】:

    使用中间件比使用函数(utils 也被视为包名称)更惯用:

    func ErrorHandler(c *gin.Context) {
            c.Next()
    
            for _, err := range c.Errors {
                // log, handle, etc.
            }
        
            c.JSON(http.StatusInternalServerError, "")
    }
    
    
    func main() {
        router := gin.New()
        router.Use(middleware.ErrorHandler)
        // ... routes
    }
    

    值得注意的是,您在中间件 func 实际错误处理代码之前调用 c.Next(),因此您可以确保在调用处理程序链的其余部分之后发生错误处理。

    Next 只能在中间件内部使用。它执行调用处理程序内链中的待处理处理程序。 [...]

    使用中间件的好处是您还可以向它传递参数,例如一个记录器,您可能希望稍后将其用作错误处理的一部分,once,而不是每次直接调用 utils.ErrorHandler 时都传递它。在这种情况下,它看起来像这样(我使用 Uber Zap 记录器):

    func ErrorHandler(logger *zap.Logger) gin.HandlerFunc {
        return func(c *gin.Context) {
            c.Next()
            
            for _, ginErr := range c.Errors {
                logger.Error("whoops", ...)
            }
        }
    }
    
    func main() {
        router := gin.New()
        
        logger, _ := zap.NewDevelopment()
    
        router.Use(middleware.ErrorHandler(logger))
        // ... routes
    }
    

    然后,处理程序将中止链,而不是调用函数,这看起来更干净,更容易维护:

    func (h *Handler) List(c *gin.Context) {
        movies, err := h.service.ListService()
    
        if err != nil {
            c.AbortWithError(http.StatusInternalServerError, err)
            return
        }
    
        c.JSON(http.StatusOK, movies)
    }
    

    请务必注意,如果您在 c.AbortWithStatusc.AbortWithError 中设置 HTTP 状态,您可能希望在错误处理程序中覆盖它。在这种情况下,您可以使用-1 作为状态码调用c.JSON()

    func ErrorHandler(logger *zap.Logger) gin.HandlerFunc {
        return func(c *gin.Context) {
            c.Next()
            
            for _, ginErr := range c.Errors {
                logger.Error("whoops", ...)
            }
    
            // status -1 doesn't overwrite existing status code
            c.JSON(-1, /* error payload */)
        }
    }
    

    最后,使用中间件可以让您在处理程序中多次调用c.Error,例如当发生一系列非致命错误并且您想在实际中止请求之前捕获所有错误时。

    Error 将错误附加到当前上下文。错误被推送到错误列表中。为解决请求期间发生的每个错误调用 Error 是个好主意。中间件可用于收集所有错误并[处理它们]

    func (h *Handler) List(c *gin.Context) {
        err1 := /* non-fatal error */
        if err1 != nil {
            c.Error(err1)
        }
    
        err2 := /* another non-fatal error */
        if err2 != nil {
            c.Error(err2)
        }
    
        fatalErr := /* fatal error */
        if fatalErr != nil {
            c.AbortWithError(505, fatalErr)
            return
            // the error handler will have collected all 3 errors
        }
    
        c.JSON(http.StatusOK, movies)
    }
    

    至于中间件中的实际错误处理,它非常简单。请记住,所有对c.Errorc.AbortWith... 的调用都会将您的错误包含在gin.Error 中。因此,要检查原始值,您必须检查 err.Err 字段:

    func ErrorHandler(c *gin.Context) {
            c.Next()
    
            for _, err := range c.Errors {
                switch err.Err {
                    case ErrNotFound:
                      c.JSON(-1, gin.H{"error": ErrNotFound.Error()})  
                }
                // etc...
            }
    
            c.JSON(http.StatusInternalServerError, "")
    }
    

    遍历c.Errors 可能看起来很笨拙,因为现在您可能有N 个错误而不是一个,但是根据您打算如何使用中间件,您可以简单地检查len(c.Errors) > 0 并仅访问第一项c.Errors[0]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-08
      • 2017-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-01
      相关资源
      最近更新 更多