defer+recover保护gin不panic

本文首发于我的Github博客

本文记录了作者保护gin构建的web app不panic的方式,简单来说:

  1. 主程中的panic本身是会被gin拦截的
  2. 协程中的panic需要使用deferrecover进行保护

情景

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

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

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

解决方案

主程序中的panic

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

package main

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

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

运行之后我们作如下HTTP请求

> curl localhost:8080/panic

会发现在gin的运行窗口出现如下信息

2020/06/04 18:42:12 [Recovery] 2020/06/04 - 18:42:12 panic recovered:
GET /panic HTTP/1.1
Host: localhost:8080
Accept: */*
User-Agent: curl/7.64.1


panic
/Users/admin/go/src/gin-test/main.go:10 (0x1581418)
        main.func1: panic("panic")
/Users/admin/go/src/github.com/gin-gonic/gin/context.go:165 (0x156baca)
        (*Context).Next: c.handlers[c.index](c)
/Users/admin/go/src/github.com/gin-gonic/gin/recovery.go:83 (0x157fb13)
        RecoveryWithWriter.func1: c.Next()
/Users/admin/go/src/github.com/gin-gonic/gin/context.go:165 (0x156baca)
        (*Context).Next: c.handlers[c.index](c)
/Users/admin/go/src/github.com/gin-gonic/gin/logger.go:241 (0x157ec40)
        LoggerWithConfig.func1: c.Next()
/Users/admin/go/src/github.com/gin-gonic/gin/context.go:165 (0x156baca)
        (*Context).Next: c.handlers[c.index](c)
/Users/admin/go/src/github.com/gin-gonic/gin/gin.go:420 (0x1575d20)
        (*Engine).handleHTTPRequest: c.Next()
/Users/admin/go/src/github.com/gin-gonic/gin/gin.go:376 (0x157548c)
        (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/Cellar/go/1.13.8/libexec/src/net/http/server.go:2802 (0x12cb6d3)
        serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/Cellar/go/1.13.8/libexec/src/net/http/server.go:1890 (0x12c6f74)
        (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.13.8/libexec/src/runtime/asm_amd64.s:1357 (0x105c030)
        goexit: BYTE    $0x90   // NOP

[GIN] 2020/06/04 - 18:42:12 | 500 |    1.238546ms |             ::1 | GET      "/panic"

并且整个app还在正常运行,没有终止,这非常好

协程中的panic

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

package main

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

func main() {
    r := gin.Default()
    r.GET("/go-panic", func(ctx *gin.Context) {
        go func() {
            panic("panic")
        }()
    })
    r.Run()
}

运行该app之后,我们作如下的HTTP请求

> curl localhost:8080/go-panic

会发现gin app退出了

panic: panic

goroutine 24 [running]:
main.main.func1.1()
        /Users/admin/go/src/gin-test/main.go:11 +0x39
created by main.main.func1
        /Users/admin/go/src/gin-test/main.go:10 +0x35
exit status 2
协程解决方案

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

package main

import (
    "fmt"

    "github.com/gin-gonic/gin"
)

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

而后我们像刚才一样进行HTTP请求

> curl localhost:8080/go-panic

会得到如下打印

error: panic
[GIN] 2020/06/04 - 18:50:20 | 200 |       2.951µs |             ::1 | GET      "/go-panic"

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值