目录
1.gin中间件
中间件直接说源码优点枯燥,不妨从demo来以小窥大。
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.New()
server := http.Server{
Addr: ":8080",
// called handler, if nil default http.DefaultServeMux
Handler: engine,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 注册全局中间件
// recovery中间件, 记录日志中间件, 请求处理时间耗时中间件
engine.Use(gin.Recovery(), webLogMiddleware(), timeRecordMiddleware())
// 注册组中间件
// 除注册用户外,其余组内路由访问需要认证的中间件
// 方法1,组内加中间件
group1 := engine.Group("/app/v1", authRequiredMiddleware())
// 方法2,组内Use()加中间件
// 通过组路由Use()方法添加中间件
// group1.Use(authRequiredMiddleware())
{
group1.POST("/add", addUser)
group1.GET("/get", getUser)
group1.DELETE("/delete", deleteUser)
}
// 单个组路添加中间件
engine.GET("/ping", myMiddleware(), pong)
err := server.ListenAndServe()
if err != nil {
log.Fatalln("server start failed:", err)
}
}
func pong(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"msg": "pong",
})
}
func addUser(ctx *gin.Context) {
// 具体业务逻辑...
ctx.JSON(http.StatusOK, gin.H{
"msg": "add user success.",
})
}
func getUser(ctx *gin.Context) {
// 具体业务逻辑...
ctx.JSON(http.StatusOK, gin.H{
"msg": "get user info success.",
"username": "Alice",
"address": "Tianfu 1st",
})
}
func deleteUser(ctx *gin.Context) {
// 具体业务逻辑...
ctx.JSON(http.StatusOK, gin.H{
"msg": "delete user success.",
})
}
// 时间耗时中间件
func timeRecordMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
start := time.Now()
ctx.Next()
// 模拟请求处理耗时
time.Sleep(2 * time.Second)
cost := time.Since(start).Seconds()
log.Printf("request handle time cost: %f\n", cost)
}
}
// 记录日志中间件
func webLogMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
log.Println("before handler, extract response.")
ctx.Next()
log.Println("after handler, record request/response here.")
}
}
// 认证的中间件
func authRequiredMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
skipedPath := "/app/v1/add"
path := ctx.Request.URL.Path
// auth only exclude user add api
if path != skipedPath {
// 具体业务逻辑
}
ctx.Next()
}
}
// 中间件
func myMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
log.Println("balabala...")
ctx.Next()
}
}
上面的demo包含的中间件类型主要是三种:
- 1.全局中间件
- 2.组内中间件
- 3.单个路由中间件
接下来按此顺序顺着源码看看。
1.1 全局中间件
在创建了gin的engine后,我们就可以通过engine.Use()
方法添加中间件,以下是engine.Use()
方法源码。
// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
// 调用RouterGroup的Use()方法
engine.RouterGroup.Use(middleware...)
...
return engine
}
从源码看到,实际用engine.Use()
方法,其实也是调用RouterGroup.Use()
方法,从入参来看,传入的中间件其实也是可变的middleware,即可以一个一个middleware传入,也可以切片传入。
全局中间件注册以后,对全局路由都适用,通常我们会在全局上添加一些如请求耗时、日志记录、panic-recovery的中间件,方便记录与恢复整体service。
1.2 组内中间件
源码:
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
源码处理也不复杂,直接将添加的组内路由加到已有中间件的尾部,这里需要注意的地方是,如果是组内路由,在group的时候,其实会新建个group的,所以组内添加的中间件只对组内适用,来看看RouterGroup.Group()
方法源码。
// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
// 复制一份全局的中间件handlers,即处理后的handlers = 全局中间件+组内中间件
Handlers: group.combineHandlers(handlers),
// 计算根path
basePath: group.calculateAbsolutePath(relativePath),
// 引用同一个engine 实例
engine: group.engine,
}
}
上面方法是在Group()
方法中添加中间件,涉及的中间中间件处理就是复制一份全局中间件的handlers,加上添加的组内中间件handlers,在新建的RouterGroup实例中设置handlers = 全局中间件+组内中间件。
当然,在RouterGroup
中也提供`Use()`方法,我们来看看。
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
可以看到,其实就是在组内的现有的中间件handlers的基础上,再加上新添加的handlers,这个组内可以认为是全局的范围,也可以认为是确实就是组内的范围,取决于是否新建了组路由,默认下就是全局的,新建了组路由就是组内的,依然很简单不是。
1.3 单个路由中间件
单个路由中间件,仅适用于该路由,为什么这样说,且看源码。
还以demo为例,我们在添加"/ping"
路由方法为GET
,看源码:
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
// 调用handle()方法
return group.handle(http.MethodGet, relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
// 组内中间件handlers + 添加的handlers(最后一个为真正的业务逻辑处理的控制器handler)
handlers = group.combineHandlers(handlers)
// 添加路由信息
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
根据之前在路由注册中学习,我们知道,注册路由时,实际gin是将路由信息添加到方法树中。
做个小结:
全局中间件
gin的engine实例维护一个全局的handlers,即:engine.RouterGroup.Handlers
,添加路由时把这个加到路由的handlers,然后插入到路由存储数据结构方法树中。
组内中间件
每次新建组路由,都会新建个RouterGroup实例
,将engine维护的handlers(全局中间件)复制一份,然后加上组内路由,组建成一个新的handlers,即:engine.RouterGroup.Handlers + handlers(组路由上添加的中间件)
,待创建具体的路由时处理。
单个路由中间件
新建具体的路由时,在路由中添加中间件handlers,会加上group.Handlers,即该路由对应的handlers = group.Handlers + handlers(单个路由添加的handlers)
,每个路由的handlers都是如此处理,实际注册路由即加到方法树中。举个例子,group.Handlers = Handlers,路由1:path-1,添加的handlers为handlers_1,则插入到对应方法树中的路由信息应该是:path-1 和 Handlers + handlers_1
。
总体来说,gin对中间件的处理比较简单,不管是全局中间件、组内中间件、单个路由中间件,最终都是要插入到方法树中维护路由信息,engine在新建实例时,就创建了9棵methodTree
。为什么是9呢,因为http method就是get/post/put/patch/head/options/delete/connect/trace
,一共9个方法。