// recovery 机制,将协程中的函数异常进行捕获
func Recovery() framework.ControllerHandler {
// 使用函数回调
return func(c *framework.Context) error {
// 核心在增加这个 recover 机制,捕获 c.Next()出现的 panic
defer func() {
if err := recover(); err != nil {
c.Json(500, err)
}
}()
// 使用 next 执行具体的业务逻辑
c.Next()
return nil
}
}
这个中间件的作用是捕获协程中的函数异常。我们使用 defer、recover 函数,捕获了 c.Next 中抛出的异常,并且在 HTTP 请求中返回 500 内部错误的状态码。
乍看这段代码,是没有什么问题的。但是我们再仔细思考下是否有需要完善的细节?
首先是异常类型,我们原先认为,所有异常都可以通过状态码,直接将异常状态返回给调用方,但是这里是有问题的。这里的异常,除了业务逻辑的异常,是不是也有可能是底层连接的异常?
以底层连接中断异常为例,对于这种连接中断,我们是没有办法通过设置 HTTP 状态码来让浏览器感知的,并且一旦中断,后续的所有业务逻辑都没有什么作用了。同时,如果我们持续给已经中断的连接发送请求,会在底层持续显示网络连接错误(broken pipe)。
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
// 判断是否是底层连接异常,如果是的话,则标记 brokenPipe
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
...
if brokenPipe {
// 如果有标记位,我们不能写任何的状态码
c.Error(err.(error)) // nolint: errcheck
c.Abort()
} else {
handle(c, err)
}
}
}()
c.Next()
}
这段代码先判断了底层抛出的异常是否是网络异常(net.OpError),如果是的话,再根据异常内容是否包含“broken pipe”或者“connection reset by peer”,来判断这个异常是否是连接中断的异常。如果是,就设置标记位,并且直接使用 c.Abort() 来中断后续的处理逻辑。