go 进阶 gin底层原理相关: 四. gin中间件底层原理

一. gin 中间件基础

  1. 中间件是什么?: 是为了过滤路由而发明的一种机制,有点像责任链,当接收到请求时先经过中间件,再到具体的处理函数
  2. gin中执行业务时的先后流程分为: 全局中间件–>路由组中间件—>然后是执行实际业务逻辑的Handler,按照官方的定义处理实际业务的Handler称为main handler,剩下其它的称为middleware handler(也就是中间件), middleware handle 可以分为两部分,随着 request 的流动,左边是入,右边为出,而分割点就是 next, next 的作用就是明确在这个地方进入到下一个 handler ,如果没有指定,默认是当前 handler 结束后进入下一个 handler, 本质是一个匿名回调函数,和绑定到一个路径下的处理函数本质是一样的
  3. 中间件是实现流程
  1. 提供func(c *gin.Context)类型的函数,编写中间件业务
  2. 调用Engine下的Use()方法或RouterGroup下的Use()方法注册全局或路由组中间件
  3. 执行时,通过Context的index当前中间件下标获取到对应的中间件执行
  4. 通过Context.Next方法获取下一个中间件执行
  5. 最终执行到main Handler,当main Handler执行完毕后依次流出中间件
  6. 或通过Context.Abort() 将index移动末尾,也就是最后一个中间件,跳出中间件链

二. 中间件初始化流程

  1. 初始化中间件底层可以细分为两步
  1. 初始化中间件,将中间件保存到RouterGroup的HandlersChain数组中
  2. 新建切片,获取RouterGroup的HandlersChain数组中保存的中间件函数,插入新切片的头部,再把用户自定义处理某路径下请求的handler插入到尾部,构建前缀树

1. 初始化中间件保存到RouterGroup的HandlersChain数组中

  1. 在启动服务初始化容器时,会获取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
}
  1. 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                 // 是否为根路由组
}
  1. HandlersChain 实质就是一个方法数组,每个方法的方法签名为 func(*Context)
  2. 在执行gin.Use(func…)注册中间件, gin.Get(“”, func)注册路由, gin.Group(“”, func)创建分组等操作时最终函数会在gin初始化的时候按顺序,将这些func放到HandlersChain数组中,等待被依次调用
  3. 此处我们先只关注中间件,中间件会保存到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构建前缀树

  1. 在调用
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}
  1. 查看路由注册函数,以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()
}
  1. 在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
}
  1. 再然后执行addRoute()也就是路由注册的过程了,参考二. gin的路由注册原理

三. 中间件的获取执行

  1. 中间件的获取执行,实际也可以说成接收请求后的路由匹配,执行对应的路由,实现指定的业务
  2. 中间件的执行可以细分为两步
  1. 接收请求
  2. 接收到请求后的路由匹配与执行

监听接收连接请求

  1. 编写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
}
  1. net/http下的ListenAndServe(),该方法内部会调用Server下的ListenAndServe()
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	//调用Server下的ListenAndServe()
	return server.ListenAndServe()
}
  1. 查看Server下的ListenAndServe()方法内部会调用Server下的Serve()
  2. 在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)
	}
}
  1. 查看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)
		//……
}

接收到请求后的处理

  1. 上面了解到当监听接收到请求后,会执行ServeHTTP()
  2. 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)
}
  1. 在Engine实现的ServeHTTP()方法中会调用Engine.handleHTTPRequest(),该方法会找到当前请求方式对应methodTree然后找到路径对应的处理方法
  1. 在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
		}
	//……
  1. 查看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)
	}
}
  1. 具体参考前面做的笔记 三. gin接收请求匹配路由原理

四. 总结

  1. 中间件底层可以封中间件注册与路由匹配后中间件执行两部分看,要先了解gin中的Engine 与RouterGroup
  2. RouterGroup内部有:
  1. HandlersChain属性,内部存储了中间件函数执行链
  2. basePath属性: 当前路由组的基础路径(根节点是"/",在Group分组时对应的是basePath+分组路径)
  3. root 表示当前是否是跟路由的布尔属性
  4. Engine当前Gin引擎实例
  1. 在我们编写gin服务时首先要调用gin.New()或gin.Default()创建引擎实例, Engine 与 RouterGroup 实现了IRouter与IRouters 接口,实现了下注册中间件的Use()函数,通过Use()函数注册中间件
  1. Engine 隐式的继承了RouterGroup,在调用New()函数初始化时,会给这个默认的RouterGroup进行赋值
  2. 调用Engine 上的User()方法时,内部或获取这个默认的RouterGroup调用RouterGroup上的Use()注册中间件,会将中间件函数保存到RouterGroup的HandlersChain中间件执行链中
  3. 在调用RouterGroup的Group()进行分组时可以注册分组中间件,查看Group函数,内部会创建一个新的RouterGroup,调用RouterGroup上的combineHandlers()方法生成一个新的handler切片,把新的中间件函数的handler插入到头部,把前面注册的某路径下请求的handler中间件插入到尾部
  4. 最终中间件函数按照注册的先后顺序保存到了RouterGroup的HandlersChain中
  1. 接着看一下路由注册,RouterGroup 实现了IRouter与IRouters 接口,通过GET,POST等方法实现路由接口注册,以GET为例,内部会调用RouterGroup上的handle()方法,在该方法中:
  1. 也会执行calculateAbsolutePath()获取到当前接口上对应的中间件函数
  2. 调用Engine上的addRoute()方法进行路由注册,最终将所有的handler包括中间件与接口函数,接口路径等维护路由关系封装为node结构变量,保存到Engine的trees属性上
  1. 中间件执行也就是路由匹配执行的过程,参考前面做的笔记
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值