发布于:,更新于:

09.Mygin之错误恢复Recover中间件

本篇是mygin这个系列的最后一篇。如果想自己动手实现一个类似Gin的Web框架,建议从 mgin第一篇开始,
总代码行数有效行数只有600多行
wc.png

github源码 mygin

目的

  • 实现错误处理机制

panic简介

在实现错误处理机制之前,我们知道在Go 中,错误的处理方式依靠return返回,由调用者处理。如果是不可恢复的错误,可以手动抛出错误,当然在实际运行中,也会遇到不可处理的错误,比如说除数为0的时候painc 也会触发。终止当前的程序。

手动触发错误

1
2
3
4
5
6
//main.go
func main() {
fmt.Println("before ....")
panic("some err message")
fmt.Println("after ....")
}
  • shell
    1
    2
    3
    4
    5
    6
    7
    8
    ~  go run cmd/main.go
    before ....
    panic: some err message

    goroutine 1 [running]:
    main.main()
    /var/www/gophp/cmd/main.go:12 +0x5f
    exit status 2

程序发生错误

1
2
3
4
5
func main() {
i := []int{0}
j := 2
fmt.Println(j / i[0])
}
  • shell
    1
    2
    3
    4
    5
    6
    7
    go run cmd/main.go
    panic: runtime error: integer divide by zero

    goroutine 1 [running]:
    main.main()
    /var/www/gophp/cmd/main.go:13 +0x194
    exit status 2
    painc 的介绍到此为止,也就是发生了painc 错误,对程序来说就是比较严重的,就会中断,但是这个时候需要捕获到这个painc 错误,Go中没有try…Catch…又想捕获错误,这个时候就该defer 出场了。

defer简介

panic会导致程序被中止,但是在程序终止前,会先把当前协程上已经defer的任务,执行完成后再终止。效果类似于其他语言的try…catch

示例

1
2
3
4
5
6
7
8
9
10
//cmd/main.go

func main() {
defer func() {
fmt.Println("defer....")
}()
i := []int{0}
j := 2
fmt.Println(j / i[0])
}
  • shell
    1
    2
    3
    4
    5
    6
    7
    8
    ~ go run cmd/main.go
    defer....
    panic: runtime error: integer divide by zero

    goroutine 1 [running]:
    main.main()
    /var/www/gophp/cmd/main.go:16 +0x1d3
    exit status 2
    可以看到,在程序退出之前先执行了defer 中的函数。如果能在defer 中捕获painc 错误,那么就能实现其他语言的try…catch

recover函数

介绍之前看官方文档怎么定义的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
翻译后...
recover 内置函数允许程序管理恐慌的 goroutine 的行为。在延迟函数(但不是它调用的任何函数)
中执行恢复调用会通过恢复正常执行来停止 panic 序列,并检索传递给 panic 调用的错误值。如果
在延迟函数之外调用 recover,则不会停止紧急序列。在这种情况下,或者当 goroutine 没有
panic 时,或者如果提供给 panic 的参数为 nil,则 recover 返回 nil。因此,recover 的
返回值报告 goroutine 是否处于恐慌状态。

mygin应用

  • cmd/main.go
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func main() {
    defer func() {
    fmt.Println("defer....")
    if err := recover(); err != nil {
    fmt.Printf("recover 到错误信息:%s\n", err.(error).Error())
    fmt.Println("recover success")
    }
    }()
    i := []int{0}
    j := 2
    fmt.Println(j / i[0])
    fmt.Println("after panic")
  • shell
    1
    2
    3
    4
    ~ go run cmd/main.go
    defer....
    recover 到错误信息:runtime error: integer divide by zero
    recover success
    可以看到已经捕获到错误信息了,程序正常结束。 after panic 没有打印,这是正确的,当panic 被触发时,程序的执行栈就到定义的defer 函数。就像在try代码块中发生了异常,执行栈来到 catch,接下来执行 catch 代码块中的代码。而在 main() 中打印了 recover success,说明程序已经恢复正常,继续往下执行直到结束。

mygin的错误处理

对一个 Web 框架而言,错误处理机制是必要的。如果发生了painc错误,应当返回错误信息或告诉对方失败了,而不至于什么都不返回,对调用方十分不友好。
例如我有如下的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"github.com/scott-pb/mygin"
"net/http"
"strconv"
)

func main() {

r := mygin.Default()
group := r.Group("/api")
group.GET("/recovery/:index", func(c *mygin.Context) {
index := c.Params.ByName("index")
i, _ := strconv.ParseInt(index, 10, 10)
s := []int{1, 3, 5, 7, 9}

c.String(http.StatusOK, fmt.Sprintf("index:%d result:%d success!\n", i, s[i]))
})

err := r.Run(":8088")
if err != nil {
fmt.Println(err)
}
}

根据调用方传递的index下标函数数组中的值,当传递的index超过数组下标时,就会发生painc错误,如果执行/api/recovery/10 时,就会发生错误。调用方什么返回都没有。
这个时候,就需要添加一个错误的处理机制,当错误发生时,向调用方返回Internal Server Error,且打印必要的错误信息,方便进行错误定位。

实现中间件 Recovery

新增文件 mygin/recovery.go,在这个文件中实现中间件 Recovery

  • mygin/recovery.go
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package mygin

    import (
    "fmt"
    "net/http"
    )

    // Recovery 发生错误时,恢复函数,且返回相应错误信息。
    func Recovery() HandlerFunc {
    return func(c *Context) {
    // 使用defer延迟执行,以便在函数退出时进行recover
    defer func() {
    if err := recover(); err != nil {
    // 如果发生panic,打印错误信息并返回500 Internal Server Error响应
    fmt.Println(err.(error).Error())
    c.Writer.Write([]byte("Internal Server Error\n"))
    c.status = http.StatusInternalServerError
    c.Abort() // 终止后续中间件的执行
    }
    }()

    c.Next() // 调用下一个中间件或处理函数
    }
    }
    Recovery 方法很简单,使用defer挂载上错误恢复的函数,在这个函数中调用 recover方法,捕获panic,打印错误信息,并且向调用方返回 Internal Server Error。
  • mygin/engine.go
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Default 返回一个默认的引擎实例
    func Default() *Engine {
    engine := New()

    //Logger Recovery 中间件
    engine.Use(Logger(), Recovery())

    // Group 保存 engine 的指针
    engine.RouterGroup.engine = engine

    return engine
    }

接下来就是测试了

测试

  • shell
    1
    2
    3
    4
    ~ curl http://127.0.0.1:8088/api/recovery/1
    index:1 result:3 success!
    ~ curl http://127.0.0.1:8088/api/recovery/10
    Internal Server Error
  • 控制台输出
    mygin09.png
    可以看到第一次请求返回200成功,第二次请求,先打印了错误信息,然后对应的返回500错误,且调用方接收到了Internal Server Error。



本站由 [PengBin](/) 使用 [Stellar 1.26.3](https://github.com/xaoxuu/hexo-theme-stellar/tree/1.26.3) 主题创建。 本博客所有文章除特别声明外,均采用 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 许可协议,转载请注明出处。