一. gin 中间件基础
- 中间件是什么?: 是为了过滤路由而发明的一种机制,有点像责任链,当接收到请求时先经过中间件,再到具体的处理函数
- gin中执行业务时的先后流程分为: 全局中间件–>路由组中间件—>然后是执行实际业务逻辑的Handler,按照官方的定义处理实际业务的Handler称为main handler,剩下其它的称为middleware handler(也就是中间件), middleware handle 可以分为两部分,随着 request 的流动,左边是入,右边为出,而分割点就是 next, next 的作用就是明确在这个地方进入到下一个 handler ,如果没有指定,默认是当前 handler 结束后进入下一个 handler, 本质是一个匿名回调函数,和绑定到一个路径下的处理函数本质是一样的
- 中间件是实现流程
- 提供func(c *gin.Context)类型的函数,编写中间件业务
- 调用Engine下的Use()方法或RouterGroup下的Use()方法注册全局或路由组中间件
- 执行时,通过Context的index当前中间件下标获取到对应的中间件执行
- 通过Context.Next方法获取下一个中间件执行
- 最终执行到main Handler,当main Handler执行完毕后依次流出中间件
- 或通过Context.Abort() 将index移动末尾,也就是最后一个中间件,跳出中间件链
二. 中间件初始化流程
- 初始化中间件底层可以细分为两步
- 初始化中间件,将中间件保存到RouterGroup的HandlersChain数组中
- 新建切片,获取RouterGroup的HandlersChain数组中保存的中间件函数,插入新切片的头部,再把用户自定义处理某路径下请求的handler插入到尾部,构建前缀树
1. 初始化中间件保存到RouterGroup的HandlersChain数组中
- 在启动服务初始化容器时,会获取Engine实例,Engine中存在一个RouterGroup属性,在获取Engine实例时会先创建这个默认的RouterGroup
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
//实例化默认的RouterGroup,其中Handlers为中间件数组
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
//trees 是最重要的点!!!!负责存储路由和handle方法的映射,采用类似字典树的结构
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}`
engine.RouterGroup.engine = engine
//这里采用 sync/pool 实现context池,减少频繁context实例化带来的资源消耗
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
- Engine 与 RouterGroup 都实现了IRouter 接口,实现了IRouters下注册中间件的Use()函数,通过Use()函数注册中间件,将中间件函数保存到RouterGroup的HandlersChain数组中
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
//获取Engine中默认的RouterGroup执行注册中间件逻辑
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
// 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()
}
HandlersChain是什么
type RouterGroup struct {
Handlers HandlersChain // 中间件函数执行链
basePath string // 路由组的基础路径
engine *Engine // Gin引擎对象
root bool // 是否为根路由组
}
- HandlersChain 实质就是一个方法数组,每个方法的方法签名为 func(*Context)
- 在执行gin.Use(func…)注册中间件, gin.Get(“”, func)注册路由, gin.Group(“”, func)创建分组等操作时最终函数会在gin初始化的时候按顺序,将这些func放到HandlersChain数组中,等待被依次调用
- 此处我们先只关注中间件,中间件会保存到HandlersChain数组中
// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
2. 整合中间件函数与业务相关的mainHandler构建前缀树
- 在调用
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
- 查看路由注册函数,以GET为例,方法内会调用RouterGroup的handle()方法
router.GET("/hello", getting)
……
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
//1.通过相对路径获取绝对路径
absolutePath := group.calculateAbsolutePath(relativePath)
//2.合并前面Handlers(例如前面添加到中间件函数等)
handlers = group.combineHandlers(handlers)
//2.将对absolutePath路径Get请求对应的处理方法(handlers)加入到引擎的路由中
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
- 在RouterGroup.handle()中调用的combineHandlers()比较重要,生成一个新的handler切片,然后先把中间件的handler插入到头部,然后把用户自定义处理某路径下请求的handler插入到尾部。最后返回的是这个新生成的切片,而引擎中之前设置的中间件handlers(group.Handlers)并没改变。所以针对每个需要被路由的请求,之前注册的中间件对应的handler都会被调用
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
- 再然后执行addRoute()也就是路由注册的过程了,参考二. gin的路由注册原理
三. 中间件的获取执行
- 中间件的获取执行,实际也可以说成接收请求后的路由匹配,执行对应的路由,实现指定的业务
- 中间件的执行可以细分为两步
- 接收请求
- 接收到请求后的路由匹配与执行
监听接收连接请求
- 编写gin服务时,通过Engine下的Run()监听端口,启动服务,该方法内部最终会执行net/http下的ListenAndServe()
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return
}
- net/http下的ListenAndServe(),该方法内部会调用Server下的ListenAndServe()
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
//调用Server下的ListenAndServe()
return server.ListenAndServe()
}
- 查看Server下的ListenAndServe()方法内部会调用Server下的Serve()
- 在Server下的Serve()中,会获取连接,设置连接状态,基于协程启动监听连接请求
//1.
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
//调用Server下的Serve()
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
//2.该方法内部重会获取连接,设置连接状态,基于协程处理请求
func (srv *Server) Serve(l net.Listener) error {
//……
for {
rw, e := l.Accept()
//……
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
//基于协程
go c.serve(ctx)
}
}
- 查看conn下的serve(),当接收到连接请求后,会执行该方法,该方法内部会调用ServeHTTP()
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
//……
//通过 ServeHTTP(w, w.req) 处理请求
serverHandler{c.server}.ServeHTTP(w, w.req)
//……
}
接收到请求后的处理
- 上面了解到当监听接收到请求后,会执行ServeHTTP()
- Gin的底层使用了net/http包,初始化启动服务时要获取Engine实例,Engine实现了ServeHTTP()接口,该接口是索引请求的入口, 并且在获取该实例的过程中会创建一个用来处理请求的Context
在ServeHTTP中会基于Pool获取到Context,通过Context处理请求
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
//处理请求逻辑
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
- 在Engine实现的ServeHTTP()方法中会调用Engine.handleHTTPRequest(),该方法会找到当前请求方式对应methodTree然后找到路径对应的处理方法
- 在handleHTTPRequest方法中当获取到对应的树节点后,会获取到对应的Handlers列表,封装到Context中, 并且初始化Context的index属性,
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
path := c.Request.URL.Path
//……
//获取到当前请求对应的树
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
//1.获取RouterGroup上的handlers,获取请求参数等等
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
//将handlers设置到Context上
c.handlers = handlers
//设置请求参数
c.Params = params
//初始化index
c.Next()
c.writermem.WriteHeaderNow()
return
}
//……
- 查看Context下的Next(),就是累加Context的index属性,执行下一个Handler,这就是前面中间件的获取与执行
func (c *Context) Next() {
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
- 具体参考前面做的笔记 三. gin接收请求匹配路由原理
四. 总结
- 中间件底层可以封中间件注册与路由匹配后中间件执行两部分看,要先了解gin中的Engine 与RouterGroup
- RouterGroup内部有:
- HandlersChain属性,内部存储了中间件函数执行链
- basePath属性: 当前路由组的基础路径(根节点是"/",在Group分组时对应的是basePath+分组路径)
- root 表示当前是否是跟路由的布尔属性
- Engine当前Gin引擎实例
- 在我们编写gin服务时首先要调用gin.New()或gin.Default()创建引擎实例, Engine 与 RouterGroup 实现了IRouter与IRouters 接口,实现了下注册中间件的Use()函数,通过Use()函数注册中间件
- Engine 隐式的继承了RouterGroup,在调用New()函数初始化时,会给这个默认的RouterGroup进行赋值
- 调用Engine 上的User()方法时,内部或获取这个默认的RouterGroup调用RouterGroup上的Use()注册中间件,会将中间件函数保存到RouterGroup的HandlersChain中间件执行链中
- 在调用RouterGroup的Group()进行分组时可以注册分组中间件,查看Group函数,内部会创建一个新的RouterGroup,调用RouterGroup上的combineHandlers()方法生成一个新的handler切片,把新的中间件函数的handler插入到头部,把前面注册的某路径下请求的handler中间件插入到尾部
- 最终中间件函数按照注册的先后顺序保存到了RouterGroup的HandlersChain中
- 接着看一下路由注册,RouterGroup 实现了IRouter与IRouters 接口,通过GET,POST等方法实现路由接口注册,以GET为例,内部会调用RouterGroup上的handle()方法,在该方法中:
- 也会执行calculateAbsolutePath()获取到当前接口上对应的中间件函数
- 调用Engine上的addRoute()方法进行路由注册,最终将所有的handler包括中间件与接口函数,接口路径等维护路由关系封装为node结构变量,保存到Engine的trees属性上
- 中间件执行也就是路由匹配执行的过程,参考前面做的笔记