Gin源码分析 – 程序运行流程简介

介绍

gin 框架基于 httprouter 实现最重要的路由模块,采用Radix树数据结构来存储路由与handle方法的映射,也是框架高性能的原因。

  • Engine:容器对象,整个框架的基础
  • Engine.trees:负责存储路由和handle方法的映射,采用类似字典树的结构
  • Engine.RouterGroup:其中的Handlers存储着所有中间件
  • Context:上下文对象,负责处理请求和回应,其中的handlers是存储处理请求时中间件和处理方法的

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
		}
	}
}