Gin的路由注册是我们启动框架处理Http请求的关键之一,路由注册到什么样的数据结构关系到了在Http服务启动后处理请求的响应速率。因此这是Gin Web框架设计的核心点之一,下面我们就来分析一下Gin的路由注册代码。

​ 还是从最简单的demo实例出发:

package main

import (
   "github.com/gin-gonic/gin"
   "net/http/httputil"
)

func main() {
   r := gin.Default()
   r.GET("/ping", func(c *gin.Context) {
      bs,_:=httputil.DumpRequest(c.Request, true)
      c.JSON(200, gin.H{
         "message": string(bs),
      })
   })
   r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

​ 这里,r.GET函数将handler绑定到了/ping这个路径上。我们知道gin.Default函数的出参是Engine结构体,难道说Gin在设计上直接将GET、POST和一众路由注册函数都堆到了Engine这个结构体上了吗?显然不是,我们可以看到Engine中有个成员变量:

type Engine struct {
   RouterGroup
   ...
}

​ 这里很明显,Engine内嵌套了RouterGroup对象,因此他获得了RouterGroup的成员方法。回到demo代码,所以Engine指针可以直接使用路由注册方法注册Handler。

RouterGroup结构体

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
   Handlers HandlersChain
   basePath string
   engine   *Engine
   root     bool
}

​ RouterGroup分别有四个成员,其各自的含义如下表所示:

变量名 变量类型 释义
Handlers HandlersChain 该Path关联的Handlers
basePath string 该路由组的路径前缀,用于组合绝对路径
engine *Engine 该路由组归属的engine地址
root bool 是否为根路由

POST方法的注册逻辑

​ 可能大家会问:为什么这里单挑POST方法来说明?因为POST和GET等方法底层逻辑是类似的,所以这里就拿最典型的POST来说明。

// POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
   return group.handle(http.MethodPost, relativePath, handlers)
}

​ 可以明显看到,POST的底层是group.handle函数,这段不需要解释,让我们来看一下group.handle函数:

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
  	// 这里进去主要是将group结构体里的basePath和当前的相对路径组合成绝对路径
   absolutePath := group.calculateAbsolutePath(relativePath)
  // 将group所带的handler与该路径的handler组合得到该路径的《绝对handlers组》
   handlers = group.combineHandlers(handlers)
	// Mark1
   group.engine.addRoute(httpMethod, absolutePath, handlers)
   return group.returnObj()
}

​ 上面代码的Mark1处,是比较复杂的部分。这里可以思考一个问题:

为什么group不在自己这维护路由解析,而将它放到engine里呢?

​ 最浅显的一层其实是Engine维护了路由解析的前缀树。

前缀树与路由解析

​ 按照惯例还是先贴一下上文中addRoute方法的代码:

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
   ...
		// 这里我们不细究前缀树的实现,这放到后面的章节,这里我们就知道他在拿对应方法的前缀树的根
   root := engine.trees.get(method)
   if root == nil {
     // 如果拿不到则新生成对应方法的根路径
      root = new(node)
     // 根节点Path自然为/
      root.fullPath = "/"
     // 添加到树的根节点切片中
      engine.trees = append(engine.trees, methodTree{method: method, root: root})
   }
  // 将对应的处理函数注册到树中
   root.addRoute(path, handlers)

   // 计算路径参数的个数并更新maxParams
   if paramsCount := countParams(path); paramsCount > engine.maxParams {
      engine.maxParams = paramsCount
   }
  
   // 计算路径的长度更新maxSections
   if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
      engine.maxSections = sectionsCount
   }
}

​ 到这里基本将routerGroup这个代码文件分析的差不多了,剩余一些方法如StaticFS、Static将在后续的(有空的话)去解析。🐦🐦🐦


<
Previous Post
Gin的中间件探索之Recovery
>
Next Post
Gin探索之旅 Part-II 前缀树