一、Gin 路由的总体结构

Gin 的路由核心是基于 Radix Tree(压缩前缀树) 的高性能实现。
核心文件在源码中位于:

github.com/gin-gonic/gin/tree.go
github.com/gin-gonic/gin/routergroup.go
github.com/gin-gonic/gin/context.go

主要对象关系如下:

Engine
 ├── RouterGroup
 │    ├── 路由前缀
 │    ├── 中间件列表
 │    └── 子组 (Group)
 ├── trees (map[string]*node)
 │     ├── key: HTTP 方法 (GET, POST, ...)
 │     └── value: Radix Tree 的根节点
 └── ServeHTTP() 入口

二、路由注册流程

示例:

r := gin.Default()
r.GET("/user/:id", getUser)
r.POST("/user", createUser)

解析过程:

当执行 r.GET() 时,本质上会调用:

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}

handle() 会进一步调用:

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

最终走到核心函数:

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    root := engine.trees.get(method)
    if root == nil {
        root = new(node)
        engine.trees[method] = root
    }
    root.addRoute(path, handlers)
}

三、Radix Tree(压缩前缀树)的核心结构

每个 HTTP 方法都有一棵独立的路由树,例如:

trees["GET"]
trees["POST"]

节点结构(node):

type node struct {
    path      string        // 当前节点路径片段
    indices   string        // 子节点首字符索引,用于快速匹配
    children  []*node       // 子节点数组
    handlers  HandlersChain // 命中的处理函数链
    wildChild bool          // 是否含有通配符子节点
    nType     nodeType      // 静态、参数、通配符类型
    paramName string        // 参数名(例如 :id)
}

三种路径类型:

  1. 静态路径:如 /user/info

  2. 参数路径:如 /user/:id

  3. 通配路径:如 /static/*filepath


四、路由插入算法(addRoute()

Gin 的路由树采用 前缀压缩算法(Radix Tree)

  • 将公共前缀合并;

  • 动态参数和通配符会作为特殊节点存储;

  • 例如注册以下路径:

    /user/:id
    /user/list

    Gin 会构建出如下树形结构:

    └── user
        ├── :id
        └── list

addRoute() 中:

  1. 从根节点开始;

  2. 对比公共前缀;

  3. 拆分节点(必要时分裂);

  4. 遇到 :* 时,标记 wildChild = true

  5. 递归插入剩余路径;

  6. 最终叶子节点绑定 handlers


⚙️ 五、路由匹配过程

当有请求进来时,gin.Engine 实现了 http.Handler 接口:

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    engine.handleHTTPRequest(c)
}

handleHTTPRequest() 中:

func (engine *Engine) handleHTTPRequest(c *Context) {
    root := engine.trees.get(c.Request.Method)
    handlers, params, tsr := root.getValue(c.Request.URL.Path, c.Params)
    if handlers != nil {
        c.handlers = handlers
        c.Params = params
        c.Next() // 依次执行中间件链
        return
    }
    ...
}

匹配算法(getValue()):

  • 从根节点递归匹配路径;

  • 优先匹配静态节点;

  • 若失败则尝试参数节点(:);

  • 若还失败则尝试通配符节点(*);

  • 返回匹配的 handlers 和提取的 params


六、Handler 链的执行机制

Gin 的中间件和路由处理函数都存储为一个 HandlersChain

type HandlersChain []HandlerFunc

每个 HandlerFunc 的签名是:

type HandlerFunc func(*Context)

执行机制:

func (c *Context) Next() {
    c.index++
    for c.index < len(c.handlers) {
        c.handlers[c.index](c)
        c.index++
    }
}

这实现了一个典型的“洋葱模型”调用链:

middleware1(before)
  middleware2(before)
    finalHandler()
  middleware2(after)
middleware1(after)

七、路由查找优化策略

Gin 对路由查找进行了多层优化:

优化点说明
前缀压缩减少树节点数量,加速匹配
字符索引 indices快速找到下一个可能匹配的子节点
静态节点优先匹配绝大多数路由都是静态的
参数缓存在 Context 中缓存路径参数,避免多次解析
独立方法树不同 HTTP 方法不共用树,减少判断分支

八、总结图解

HTTP 请求 → Engine.ServeHTTP()
       ↓
根据 Method 找到对应 Radix Tree 根节点
       ↓
Radix Tree 递归匹配 path(静态 > 参数 > 通配)
       ↓
返回 handlers 和 params
       ↓
执行中间件链 c.Next()
       ↓
返回响应
模块职责
EngineGin 核心,持有所有路由树
RouterGroup用于分组、注册路由与中间件
nodeRadix Tree 节点结构
addRoute路由注册逻辑
getValue路由匹配逻辑
HandlersChain中间件与最终处理函数执行链