Gin Router注册与路由匹配原理
路由是Web框架的核心,Gin使用Radix树实现高效的路由匹配。
路由注册入口
基本注册方法
Go
// Engine嵌套RouterGroup
type Engine struct {
RouterGroup
trees methodTrees
// ...
}
// 注册路由
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 (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes
路由组注册
Go
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
// 使用示例
r := gin.New()
api := r.Group("/api/v1")
api.GET("/users", getUsers) // /api/v1/users
api.POST("/users", createUser) // /api/v1/users
路由存储结构
方法树
Go
type methodTrees []methodTree
type methodTree struct {
method string
root *node
}
// 每个HTTP方法一棵树
trees := methodTrees{
{method: "GET", root: &node{}},
{method: "POST", root: &node{}},
{method: "PUT", root: &node{}},
{method: "DELETE", root: &node{}},
// ...
}
Radix树节点
Go
type node struct {
path string
indices string
children []*node
handlers HandlersChain
priority uint32
nType uint8
maxParams uint8
wildChild bool
}
路由匹配流程
核心匹配方法
Go
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
// 查找对应方法树
for _, tree := range engine.trees {
if tree.method != httpMethod {
continue
}
// 在Radix树中查找
value := tree.root.getValue(rPath, c.Params, c.unescape)
if value.handlers != nil {
c.handlers = value.handlers
c.Params = value.params
c.Next()
return
}
break
}
// 404处理
c.handlers = engine.allNoRoute
c.Next()
}
路径匹配算法
Go
func (n *node) getValue(path string, po Params, unescape bool) (valuenodeValue) {
value := valueNode{}
// 逐字符匹配
Walk:
for {
prefix := n.path
if len(path) > len(prefix) {
if path[:len(prefix)] == prefix {
path = path[len(prefix):]
// 检查子节点
idxc := path[0]
for i, c := range []byte(n.indices) {
if c == idxc {
n = n.children[i]
continue Walk
}
}
// 通配符处理
if n.wildChild {
n = n.children[0]
// 参数提取
}
}
}
}
value.handlers = n.handlers
return value
}
路由类型匹配优先级
| 优先级 | 类型 | 示例 | 说明 |
|---|---|---|---|
| 1 | 静态路由 | /users/list | 精确匹配 |
| 2 | 参数路由 | /users/:id | 冒号参数 |
| 3 | 通配路由 | /files/*filepath | 星号通配 |
Go
// 路由冲突示例(会报错)
r.GET("/users/:id", handler1) // 冲突
r.GET("/users/:name", handler2) // 冲突:同类型参数
// 正确示例
r.GET("/users/:id", handler)
r.GET("/users/profile", handler) // 静态优先匹配
参数提取
Go
// 注册带参数路由
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id") // 获取路径参数
c.JSON(200, gin.H{"id": id})
})
r.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath") // 获取通配参数
c.JSON(200, gin.H{"filepath": filepath})
})
// Params结构
type Param struct {
Key string
Value string
}
type Params []Param
func (ps Params) ByName(name string) string {
for _, p := range ps {
if p.Key == name {
return p.Value
}
}
return ""
}
注意:静态路由优先于动态路由匹配,相同类型参数路由会冲突。
要点总结
- 每个HTTP方法独立一棵Radix树存储路由
- 路由注册时合并中间件链和计算绝对路径
- 匹配时逐字符遍历,支持静态、参数、通配三种类型
- 静态路由优先级最高,参数和通配次之
- 参数通过c.Param()获取,存储在Context.Params中
📝 发现内容有误?点击此处直接编辑