Gin源码分析

Posted by kzdgt on Wednesday, September 13, 2023

转载自:TerryPro - 知乎 (zhihu.com)

1 最小程序

首先通过对一个最小程序的分析,说明一下Gin程序的基本运行流程以及核心的组件。下面这个程序启动过后返回一个JSON字符串,{“message” : “pong”}。

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run()
}

下面我将深入代码,将整个程序的运行逻辑呈现在你的面前,由于本节仅仅是一个基础的介绍,因此主要部分结构和方法会进行删减,进保留主干。

2 创建引擎

r := gin.Default()

第1行语句创建一个gin运行引擎

func Default() *Engine {
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

该函数创建一个默认的Engine对象,Engine对象故名思义是 gin 的运行引擎,关键的字段如下所示:

type Engine struct {
	RouterGroup
	pool             sync.Pool
	trees            methodTrees
}

func New() *Engine {
	engine := &Engine{
                // 初始化RouterGroup
		RouterGroup: RouterGroup{ ... },
                // 创建方法输,由于有9种方法
		trees:                  make(methodTrees, 0, 9),
	}
        // 创建Context对象池
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}
  • RouterGroup:管理中间件,engine.User(Logger(), Recovery()) 就是将日志和恢复中间件添加到 RouterGroup中进行管理。
  • pool:在进行HTTP请求处理的过程中需要采用上下文Context进行管理,由于需要频繁的创建和销毁Context因此采用sync.Pool提高效率。
  • tree:每一个 HTTP 方法会有一颗方法树,方法树记录了路径和路径上的处理函数。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
        // 将中间件添加到RouterGroup中
	engine.RouterGroup.Use(middleware...)
	return engine
}

// 中间件,实际上是一个参数为Context的函数
type HandlerFunc func(*Context)

// 管理中间件的是一个切片
type HandlersChain []HandlerFunc

type RouterGroup struct {
	Handlers HandlersChain
}

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

上面的方面说明了注册中间件的过程,RouterGroup的核心字段是一个HandlerFunc的切片,所有的中间件按照顺序保存在这个切片中。

3 注册处理方法

r.GET("/ping", func(c *gin.Context) {
     c.JSON(200, gin.H{
          "message": "pong",
     })
})    

第2行语句,注册一个URL为/ping的处理方法,该方法返回一个JSON。

// GET方法
const (
	MethodGet     = "GET"
)

// 调用handle完成注册
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	// 获取完成的路径
        absolutePath := group.calculateAbsolutePath(relativePath)
        // 将中间件方法和本URL处理方法组合形成一个方法链,然后注册到tree中
	handlers = group.combineHandlers(handlers)
        // 整个方法注册的核心函数,完成方法的注册
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

engine.addRoute是方法注册的入口函数,简单来说就是将这个URL的方法链注册到相应的方法树中,为了提高URL的检索效率,这个方法树采用了Radix Tree的数据结构,在本小节中可以忽略,总值一句话通过这个方法树可以高效的检索的每个URL的对应处理方法链。

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
        // 获得这个方法的方法(GET方法)数据
	root := engine.trees.get(method)
        // 将该URL的处理方法链添加到方法树中
	root.addRoute(path, handlers)
}

// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
        ...
        // 创建相应的叶子结点或者找到相应的叶子节点
        n = child
        ...
        n.handlers = handlers
	n.fullPath = fullPath
	return
}

type node struct {
	handlers  HandlersChain
	fullPath  string
}

可以看到每个每个方法树中的节点包括 handlers和fullPath两个字段,分别保存相应的URL路径和处理方法链。

4 系统运行

r.Run()

第3行语句,调用Go的HTTP包,启动WEB服务。

func (engine *Engine) Run(addr ...string) (err error) {
        // 获取地址
	address := resolveAddress(addr)
        // http监听并且服务
	err = http.ListenAndServe(address, engine)
	return
}

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

Engine实现了ServeHTTP方法,该方法是HTTP的回调接口,当HTTP模块接收到一个HTTP请求后将调用Engine的ServeHTTP方法。

5 处理HTTP请求

简单来说就是从方法树中找到相应的方法进行处理,但是由于存在中间件,因此需要在中间件中通过Context保存上下文的数据,具体个过程在中间件一节在详细说。

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
        // 从对象池中获取一个Context
	c := engine.pool.Get().(*Context)
        // 对Context进行必要的初始化
	c.writermem.reset(w)
	c.Request = req
	c.reset()
        
        // 处理该HTTP请求
	engine.handleHTTPRequest(c)

        // 完成对HTTP的请求后,释放该Context
	engine.pool.Put(c)
}

func (engine *Engine) handleHTTPRequest(c *Context) {
	// 获得HTTP请求方法类型
        httpMethod := c.Request.Method
        // 获得URL路径
	rPath := c.Request.URL.Path

	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
                // 获得该HTTP请求的方法树
		if t[i].method != httpMethod {
			continue
		}

		root := t[i].root
		// 找到该URL的处理方法链
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)

		if value.handlers != nil {
                        // 通过上下文的Next方法实现中间件的调用以及方法的调用
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
	}
}

「真诚赞赏,手留余香」

kzdgt Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付