• Mygin之错误恢复Recover中间件


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

    github源码 mygin

    目的

    • 实现错误处理机制

    panic简介

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

    手动触发错误

    //main.go
    func main() {
      fmt.Println("before ....")
      panic("some err message")
      fmt.Println("after ....")
    }
    
    • shell
    ~  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
    

    程序发生错误

    func main() {
      i := []int{0}
      j := 2
      fmt.Println(j / i[0])
    }
    
    • shell
    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

    示例

    //cmd/main.go
    
    func main() {
      defer func() {
      fmt.Println("defer....")
      }()
      i := []int{0}
      j := 2
      fmt.Println(j / i[0])
    }
    
    • shell
    ~ 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函数

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

    // 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 是否处于恐慌状态。
    

    应用

    • cmd/main.go
    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
    ~ 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错误,应当返回错误信息或告诉对方失败了,而不至于什么都不返回,对调用方十分不友好。
    例如我有如下的逻辑

    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

    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。

    // Default 返回一个默认的引擎实例
    func Default() *Engine {
    	engine := New()
    
        //Logger Recovery 中间件
    	engine.Use(Logger(), Recovery())
    
    	// Group 保存 engine 的指针
    	engine.RouterGroup.engine = engine
    
    	return engine
    }
    

    接下来就是测试了

    测试

    • shell
    ~ 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
    
    • 控制台输出
      控制台输出
      可以看到第一次请求返回200成功,第二次请求,先打印了错误信息,然后对应的返回500错误,且调用方接收到了Internal Server Error。
  • 相关阅读:
    如何搭建一套指标体系?
    Elasticsearch:检索增强生成 (Retrieval Augmented Generation -RAG)
    NX二次开发:保存时导出PDF并打开
    Unity坐标转换
    LabVIEW使用PID对激振器控制
    基于SSM+Vue的体育馆管理系统的设计与实现
    Java使用Function包&策略模式,优化大量if...else语句
    新型攻击方法分析
    qml语法介绍
    淘宝详情源数据接口解析
  • 原文地址:https://www.cnblogs.com/pengb/p/18003379