gin框架是如何处理panic

发布时间 2023-12-19 15:18:28作者: 李若盛开

保护gin构建的web app不panic的方式,简单来说:

1)主程中的panic本身是会被gin拦截的
2)协程中的panic需要手动使用defer和recover进行保护

情景
在用gin构建项目,运行web app并上线了之后,或许有一些请求会经过业务,在特定的情况下出发会触发golang中的panic

按照golang的设定,一旦panic,如果不在函数调用栈中存在recover,那么是一定会使得整个程序终止的

但是线上的服务是不能够因为一两个的请求就直接终止,这样非常危险,所以需要手段来阻止web app在panic的情况下直接终止

解决方案
1)主程序中的panic
对于gin这个web框架来说,主程序中的panic是会被自动recover,还会打印出非常详细的日志信息,比如

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/test", func(ctx *gin.Context) {
        panic("test panic")
    })
    r.GET("/hello", func(ctx *gin.Context) {
        fmt.Println("test hello")
    })
    r.Run(":857")
}

原因是:在gin中,是通过使用该中间件来捕获panic,并保证服务不down机。 如果使用gin.Default()函数进行构建gin对象,那默认就注册了Recovery中间件。

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    //  注册了Recovery中间件
    engine.Use(Logger(), Recovery())
    return engine
}

2)协程中的panic
不过非常可惜的是,对于协程中的panic,gin并不能做到自动recover并打印日志信息,比如

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/test", func(ctx *gin.Context) {
        go func() {
            panic("test panic")
        }()
    })
    r.GET("/hello", func(ctx *gin.Context) {
        fmt.Println("test hello")
    })
    r.Run(":857")
}

协程解决方案

recover函数能够捕获Panic错误并恢复程序的正常运行。

所以,对于协程,要手动进行deferrecover来避免app的退出和打印日志信息,比如上面的代码应该修改为

    r.GET("/test", func(ctx *gin.Context) {
        go func() {
            defer func() {
                if err := recover(); err != nil {
                    fmt.Printf("error: %v\n", err)
                }
            }()
            panic("panic")
        }()
    })

可以看到app正常响应了请求,并且没有退出并打印了日志,想要更多定制操作可以修改defer的函数。