Dawn's Blogs

分享技术 记录成长

0%

从零实现一个Web框架 (7) 错误恢复

在本节,最终的代码目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dain/
|--context.go
|--dain.go
|--logger.go
|--router.go
|--recovery.go
|--trie.go
|--go.mod
static/
|--css/
|--index.css
templates/
|--index.tmpl
main.go
go.mod

实现目标

在Web服务器中,因为服务器程序出现 panic 而导致服务端崩溃是无法接受的,所以需要错误恢复机制。

main.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
func main() {
// 默认使用 Logger 和 Recovery 中间件
e := dain.Default()

// 加载静态文件
e.Static("/static", "./static")

// 加载模板
e.LoadHTMLGlob("templates/*")

e.Get("/index", func(c *dain.Context) {
c.HTML(http.StatusOK, "index.tmpl", c.Path)
})

// 测试 Recovery 中间件
e.Get("/panic", func(c *dain.Context) {
array := []int{1, 2, 3}
c.JSON(http.StatusOK, dain.H{
"msg": array[100],
})
})

e.Run(":9000")
}

错误恢复

dain/recovery.go

需要预定义一个错误恢复的中间件,用于 recover 错误,以至于 panic 不会导致程序崩溃:

1
2
3
4
5
6
7
8
9
10
11
12
func Recovery() HandlerFunc {
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
message := fmt.Sprintf("%s", err)
log.Printf("%s\n\n", trace(message))
c.Fail(http.StatusInternalServerError, "Internal Server Error")
}
}()
c.Next()
}
}

其中,trace 函数用于追踪出错的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
func trace(message string) string {
var pcs [32]uintptr
n := runtime.Callers(3, pcs[:]) // skip first 3 caller

var str strings.Builder
str.WriteString(message + "\nTraceback:")
for _, pc := range pcs[:n] {
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
str.WriteString(fmt.Sprintf("\n\t%s:%d", file, line))
}
return str.String()
}