Gin中间件
Gin允许开发者在处理请求过程中,加入用户自己的钩子(Hook)函数,即中间件。中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作。
# 1)路由中间件
Gin中的中间件必须是一个gin.HandlerFunc类型,配置路由的时候可以传递多个func回调函数,最后一个func回调函数前面触发的方法都可以称为中间件。
// 路由中间件
func initMiddleware(c *gin.Context) {
fmt.Println("aaaaa")
}
func main() {
r := gin.Default()
//自定义模板函数,函数只能放在加载模板前
r.SetFuncMap(template.FuncMap{ //"html/template"包
"UnixToTime": UnixToTime,
})
//加载模板 放在配置路由上面
r.LoadHTMLGlob("templates/**/*")
//配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
r.Static("/static", "./static")
//前台路由
//routers.InitDefaultRouters(r)
//routers.InitApiRouters(r)
//routers.InitUserRouters(r)
r.GET("/", initMiddleware, func(c *gin.Context) {
c.String(http.StatusOK, "gin首页")
})
r.GET("/news", initMiddleware, func(c *gin.Context) {
c.String(http.StatusOK, "新闻首页")
})
r.GET("/login", initMiddleware, func(c *gin.Context) {
c.String(http.StatusOK, "login")
})
r.Run()
}
/* 输出结果 (以http://localhost:8080/为例)
终端:app | aaaaa
接口:gin首页
*/
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
28
29
30
31
32
33
# 2)Gin中间件
1、ctx.Next() :调用请求该请求的剩余处理程序。
func initMiddleware(c *gin.Context) {
start := time.Now().UnixNano()
fmt.Println("1-I'm initMiddleware")
//调用该请求的剩余处理程序
c.Next()
fmt.Println("2-I'm initMiddleware")
end := time.Now().UnixNano()
fmt.Println("3-time:", end-start)
}
func main() {
r := gin.Default()
//自定义模板函数,函数只能放在加载模板前
r.SetFuncMap(template.FuncMap{ //"html/template"包
"UnixToTime": UnixToTime,
})
//加载模板 放在配置路由上面
r.LoadHTMLGlob("templates/**/*")
//配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
r.Static("/static", "./static")
//前台路由
//routers.InitDefaultRouters(r)
//routers.InitApiRouters(r)
//routers.InitUserRouters(r)
r.GET("/", initMiddleware, func(c *gin.Context) {
fmt.Println("I'm index")
//time.Sleep(time.Second)
c.String(http.StatusOK, "gin首页")
})
r.Run()
}
/*输出结果
终端:3-time: 0
接口:gin首页
*/
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
28
29
30
31
32
33
34
35
多个中间件调用方式:
func initMiddlewareOne(c *gin.Context) {
fmt.Println("1-I'm initMiddlewareOne")
//调用该请求的剩余处理程序
c.Next()
fmt.Println("2-I'm initMiddlewareOne")
}
func initMiddlewareTwo(c *gin.Context) {
fmt.Println("1-I'm initMiddlewareTwo")
//调用该请求的剩余处理程序
c.Next()
fmt.Println("2-I'm initMiddlewareTwo")
}
func main() {
r := gin.Default()
//自定义模板函数,函数只能放在加载模板前
r.SetFuncMap(template.FuncMap{ //"html/template"包
"UnixToTime": UnixToTime,
})
//加载模板 放在配置路由上面
r.LoadHTMLGlob("templates/**/*")
//配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
r.Static("/static", "./static")
//前台路由
//routers.InitDefaultRouters(r)
//routers.InitApiRouters(r)
//routers.InitUserRouters(r)
r.GET("/admin", initMiddlewareOne, initMiddlewareTwo, func(c *gin.Context) {
fmt.Println("I‘m admin index")
c.String(http.StatusOK, "admin首页")
})
r.Run()
}
/*输出结果
终端:
1-I'm initMiddlewareOne
1-I'm initMiddlewareTwo
I‘m admin index
2-I'm initMiddlewareTwo
2-I'm initMiddlewareOne
接口:admin首页
*/
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2、ctx.Abort() :终止调用请求该请求的剩余处理程序。
func initMiddlewareAbort(c *gin.Context) {
start := time.Now().UnixNano()
fmt.Println("1-I'm initMiddleware")
//调用该请求的剩余处理程序
c.Abort()
fmt.Println("2-I'm initMiddleware")
end := time.Now().UnixNano()
fmt.Println("3-time:", end-start)
}
func main() {
r := gin.Default()
//自定义模板函数,函数只能放在加载模板前
r.SetFuncMap(template.FuncMap{ //"html/template"包
"UnixToTime": UnixToTime,
})
//加载模板 放在配置路由上面
r.LoadHTMLGlob("templates/**/*")
//配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
r.Static("/static", "./static")
//前台路由
//routers.InitDefaultRouters(r)
//routers.InitApiRouters(r)
//routers.InitUserRouters(r)
r.GET("/news", initMiddlewareAbort, func(c *gin.Context) {
c.String(http.StatusOK, "新闻首页")
})
r.Run()
}
/*输出结果
终端:3-time: 0
接口:
*/
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
28
29
30
31
32
33
34
# 3)全局中间件
func initMiddleware(c *gin.Context) {
start := time.Now().UnixNano()
fmt.Println("1-I'm initMiddleware")
//调用该请求的剩余处理程序
c.Next()
fmt.Println("2-I'm initMiddleware")
end := time.Now().UnixNano()
fmt.Println("3-time:", end-start)
}
func initMiddlewareOne(c *gin.Context) {
fmt.Println("1-I'm initMiddlewareOne")
//调用该请求的剩余处理程序
c.Next()
fmt.Println("2-I'm initMiddlewareOne")
}
func initMiddlewareTwo(c *gin.Context) {
fmt.Println("1-I'm initMiddlewareTwo")
//调用该请求的剩余处理程序
c.Next()
fmt.Println("2-I'm initMiddlewareTwo")
}
func main() {
r := gin.Default()
//自定义模板函数,函数只能放在加载模板前
r.SetFuncMap(template.FuncMap{ //"html/template"包
"UnixToTime": UnixToTime,
})
//加载模板 放在配置路由上面
r.LoadHTMLGlob("templates/**/*")
//配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
r.Static("/static", "./static")
//全局中间件
r.Use(initMiddleware, initMiddlewareOne, initMiddlewareTwo)
r.GET("/", func(c *gin.Context) {
fmt.Println("I'm index")
//time.Sleep(time.Second)
c.String(http.StatusOK, "gin首页")
})
r.Run()
}
/*输出结果
终端:
1-I'm initMiddleware
1-I'm initMiddlewareOne
1-I'm initMiddlewareTwo
I'm index
2-I'm initMiddlewareTwo
2-I'm initMiddlewareOne
2-I'm initMiddleware
接口:gin首页
*/
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 4)路由分组
func InitMiddleware(c *gin.Context) {
//判断用户是否登录
fmt.Println(time.Now())
fmt.Println(c.Request.URL)
}
func InitApiRouters(r *gin.Engine) {
apiRouters := r.Group("/api", middlewares.InitMiddleware)
{
apiRouters.GET("/", api.ApiController{}.Index)
apiRouters.GET("/add", api.ApiController{}.Add)
apiRouters.GET("/update", api.ApiController{}.Update)
apiRouters.GET("/delete", api.ApiController{}.Delete)
apiRouters.GET("/select", api.ApiController{}.Select)
}
}
/*输出结果
终端:
2025-06-09 19:01:33.0349342 +0800 CST m=+47.332205901
/api/
接口:api接口
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 5)中间件-控制器、中间件-中间件数据共享
func UserMiddleware(c *gin.Context) {
//判断用户是否登录
c.Set("username", "zhangsan")
}
func InitUserRouters(r *gin.Engine) {
userRouters := r.Group("/user", middlewares.UserMiddleware)
{
userRouters.GET("/", user.UserController{}.Index)
userRouters.GET("/add", user.UserController{}.Add)
userRouters.GET("/update", user.UserController{}.Update)
userRouters.GET("/delete", user.UserController{}.Delete)
userRouters.GET("/select", user.UserController{}.Select)
}
}
func (con UserController) Index(c *gin.Context) {
//c.String(http.StatusOK, "user接口")
con.Success(c)
}
func (con UserController) Add(c *gin.Context) {
c.String(http.StatusOK, "user接口--增加 -- 继承")
}
func (con UserController) Update(c *gin.Context) {
c.String(http.StatusOK, "user接口--修改 -- 继承")
}
func (con UserController) Delete(c *gin.Context) {
c.String(http.StatusOK, "user接口--删除 -- 继承")
}
func (con UserController) Select(c *gin.Context) {
username, _ := c.Get("username") //空接口类型
fmt.Println(username)
v, ok := username.(string)
if ok == true {
c.String(http.StatusOK, "user接口--查询 -- 继承--"+v)
} else {
c.String(http.StatusBadRequest, "获取用户数据失败!")
}
}
/*输出结果
user接口--查询 -- 继承--zhangsan
*/
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 6)gin.Default()中的默认中间件
gin.Default()中含有默认中间件Logger()和Recovery()两个中间件。若不想使用的话可以修改为gin.New()默认不创建路由。
Logger()中间件将日志写入gin.DefaultWriter,即配置了GIN_MODE=release
Recovery()中间件回recover任何panic。如果有panic,会写入500响应码
注意:
当在gin中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())
原因:
1)c.Copy() 的作用
调用 c.Copy() 会创建一个 只读、安全的副本,其中包含:
请求的必要数据(如 URL、Header、Method、Path 等)。
与并发无关的数据(不包括响应写入器、Body、上下文变量等)。
副本不会被回收,可以安全地在 goroutine 中使用。
2)*gin.Context 是复用的
Gin 为了提升性能,会通过对象池(sync.Pool)来复用 *gin.Context 对象。也就是说,请求处理完毕后,这个 c 会被回收并复用用于下一次请求。
如果你在 goroutine 中异步地访问 c,而此时主请求已返回,c 可能已经被其他请求复用了,导致读取到的是错误或被修改的数据,甚至引发数据竞态(data race)。
3)goroutine 是异步执行的
当你在 handler 中启动一个 goroutine 时,该 goroutine 的执行时间是不确定的,可能晚于主 handler 的返回时间。
一旦主 handler 返回,c 可能就失效了。
此时 goroutine 中访问 c.Request.URL.Path 等字段,就有可能访问到已经被回收或复用的内存。