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