Go语言 如何处理Gin中间件中的错误

eyh26e7m  于 2023-02-27  发布在  Go
关注(0)|答案(2)|浏览(209)

我想获取每个路由上的所有http错误,而不是每次重写if 400,然后if 404,然后if 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");
      }
});

有类似的东西吗?

fnx2tebb

fnx2tebb1#

使用中间件比使用函数(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应该只在中间件内部使用。它在调用处理程序内部执行链中的挂起处理程序。[...]
使用中间件的好处是,你也可以传递参数给它,例如一个logger,你可能想稍后使用它作为错误处理的一部分,once,而不是每次直接调用utils.ErrorHandler时都传递它。在这个例子中,它看起来像这样(我使用Uber Zap loggers):

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...的调用都会将您的错误 Package 在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]

rjjhvcjd

rjjhvcjd2#

在中间件中处理错误的好处是集中处理错误,而不是在每个处理程序中显式处理异常。
为了减少响应Map错误,提出了一种相关的错误处理中间件gin-error

func Error(errM ...*ErrorMap) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()

        lastError := c.Errors.Last()
        if lastError == nil {
            return
        }

        for _, err := range errM {
            for _, e := range err.errors {
                if e == lastError.Err || errors.Is(e, lastError.Err) {
                    err.response(c)
                }
            }
        }
    }
}

使用示例:

  • 您可以将错误Map到一个状态代码
import (
    "github.com/richzw/gin-error"
    "github.com/gin-gonic/gin"
)
var BadRequestErr = fmt.Errorf("bad request error")

func main() {
    r := gin.Default()
    r.Use(err.Error(err.NewErrMap(BadRequestErr).StatusCode(http.StatusBadRequest)))

    r.GET("/test", func(c *gin.Context) {
        c.Error(BadRequestErr)
    })

    r.Run()
}
  • 或将错误Map到一个响应
import (
    "github.com/richzw/gin-error"
    "github.com/gin-gonic/gin"
)
var BadRequestErr = fmt.Errorf("bad request error")

func main() {
    r := gin.Default()
    r.Use(err.Error(
        err.NewErrMap(BadRequestErr).Response(func(c *gin.Context) {
            c.JSON(http.StatusBadRequest, gin.H{"error": BadRequestErr.Error()})
        })))

    r.GET("/test", func(c *gin.Context) {
        c.Error(BadRequestErr)
    })

    r.Run()
}

相关问题