在gin框架中,要显示自定义的异常页面,首先需要通过gin路由对象中的LoadHTMLFiles或者LoadHTMLGlob方法加载自定义的错误页面模板文件, 然后定义符合 gin.HandlerFunc 类型的路由处理函数/方法 ,即只有一个参数(c *ginx.XContext)的函数或者方法。 404异常使用路由对象的.NoRoute方法绑定路由处理函数, 其他类型的异常采用在中间件中采用defer + recover()函数捕获异常,然后根据不同类型的异常显示不同的模板页面信息。
gin框架中自定义404错误页面
gin框架中自定义404错误页面, 实际上就是没有路由匹配的页面,使用gin内置的路由对象中的方法 r.NoRoute(Xxx), 这里的r是r := gin.Default() 的对象。 Xxx 就是一个 HandlerFunc 函数类型定义,即函数/方法的参数定义是 func(*Context) ,如: func ErrHandler404(c *ginx.XContext) { }
自定义404错误页面示例:
html模板页面 templates/errors/404.html
{{define "errors/404.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.title}}</title>
</head>
<body>
<h1>{{.title}}</h1>
</body>
</html>
{{end}}
404异常页面HandlerFunc处理函数
// 自定义404异常页面处理器函数
func ErrHandler404(c *gin.Context) {
c.HTML(200, "errors/404.html", ginx.H{"title": "404 Error - Page not found"})
}
404异常模板文件加载和路由绑定
func main() {
r := gin.Default()
// 模式匹配方式加载html模板文件
r.LoadHTMLGlob("templates/**/*.html")
// 指定模板文件加载
//r.LoadHTMLFiles("templates/errors/404.html", "templates/errors/500.html")
// 自定义404异常页面处理路由绑定
r.NoRoute(ErrHandler404)
// 路由绑定
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"title": "Hello world!",
})
})
r.Run(":8080")
}
启动服务, 访问 http://localhost:8080/abc 这时就会显示我们自定义的404异常页面
gin框架根据不同的异常类型显示不同的异常页面的方法
gin框架根据不同类型的异常显示不同的异常页面采用在中间件中采用defer + recover()函数捕获异常,然后根据不同类型的异常显示不同的模板页面信息。
这个也是需要先加载模板文件的,方法和上面的一样
html异常模板文件定义
templates/errors/500.html 其他模板文件定义省略, 注意这里的模板文件名称定义 ,即 {{define "模板文件名"}}
{{define "errors/500.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.title}}</title>
</head>
<body>
<h1>{{.title}}</h1>
<h2>{{.msg}}</h2>
</body>
</html>
{{end}}
defer + recover()异常处理中间件定义
defer + recover()函数, 然后在使用 switch err := recErr.(type) { case xxx: } 的形式根据不同的异常类型显示不同的html模板和数据。
下面的switch case里面就是我们要捕获的异常类型, case的类型就是我们通过panic获取其他程序抛出的异类型, 如 panic("Demo Error ") 这个是一个string类型的异常,在case里面就会被string分支捕获;这个异常 panic(&net.OpError{Op: "read",Net: "tcp", Err: fmt.Errorf("demo for op error")}) 就会被 case *net.OpError: 分支捕获。 其他的以此类推.....
// 服务器异常处理中间件
func ErrHandlerMiddleware(c *gin.Context) {
// 注意这里通过defer语句+recover()函数来捕获异常
defer func() {
// 注意这里的recover()返回的异常类型是interface{}类型,可以通过类型断言来判断是何种类型的异常
if recErr := recover(); recErr != nil {
tpl := "errors/500.html" // 默认异常模板文件
data := make(map[string]interface{})
// switch + type类型推断
switch err := recErr.(type) {
case *net.OpError:
tpl = "errors/net_error.html"
data["title"] = "net.OpError exception has occurred"
data["msg"] = fmt.Sprintf("%v", err.Error())
case *net.AddrError:
tpl = "errors/net_error.html"
data["title"] = "net.AddrError exception has occurred"
data["msg"] = fmt.Sprintf("%v", err.Error())
case string:
data["title"] = err
data["msg"] = err
default:
// 模板数据
data["title"] = err
data["msg"] = err
}
global.Log.Errorf("error: %v", data)
//debug.PrintStack() //打印错误堆栈信息
// 显示自定义错误页面
c.HTML(200, tpl, data)
}
}()
// 继续后续的处理 这个是中间件和 路由处理控制器的重要区别
c.Next()
}
gin使用示例
这个方式使用的时候只需要在gin路由对象里面通过Use方法加载这个中间件即可,即:
r.Use(ErrHandlerMiddleware)
在控制器中如果有异常发生,就会被我们定义的中间件捕获, 然后我们就可以在中间件中根据不同的类型设置不同的异常模板页面。
控制器抛异常示例
var IndexCtr = cIndex{}
type cIndex struct{}
func (a *cIndex) DemoErr1(c *gin.Context) {
// 抛一个异常
panic("This is not implemented for Demo Error purpose.")
}
func (a *cIndex) DemoErr2(c *gin.Context) {
// 抛一个异常
panic(fmt.Errorf("fmt error demo2, gin Request: %v", c.Request))
}
func (a *cIndex) DemoErr3(c *gin.Context) {
// 抛一个异常 *net.AddrError
panic(&net.OpError{Op: "read",Net: "tcp", Err: fmt.Errorf("demo for op error")})
}
func (a *cIndex) DemoErr4(c *gin.Context) {
// 抛一个异常 *net.AddrError
panic(&net.AddrError{Err: "fmt error demo2", Addr: "localhost"})
}
gin路由 HandlerFunc处理函数/方法 和gin中间件的区别
在gin框架中, 路由的处理函数和中间件的参数定义都是一样的,都必须符合HandlerFunc的类型定义,即: type HandlerFunc func(*Context)
他们的区别在于gin中间件中 使用 c.Next() 方法来处理后续的网络请求。 在路由函数和中间件中如果要中断后续的请求,都需要调用 c.Abort() 方法来中断后续的调用链HandlersChain(这个其实就是一个HandlerFunc切片,他的定义是:type HandlersChain []HandlerFunc ) 的请求。 而如果要中断当前函数的请求,则都需要使用 return语句。