liulei281
V2EX  ›  程序员

golang gin 框架分组路由的原理

By liulei281 at 2021 年 3 月 28 日 · 3060 次点击

gin 文档中的一个例子

func main() {
	router := gin.Default()

	// Simple group: v1
	v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}

	// Simple group: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}

	router.Run(":8080")
}

router.Group方法源码是这样的:

// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

从上面的例子看, v1 := router.Group("/v1")创建一个子分组v1, 并且v1.engine中存储了父级的信息, 这时候v1知道它的父级是router. 但是程序的入口是router, 但好像并没有把v1关联给router, 那么router怎么知道v1的存在?

(初学 golang, 请大佬解惑, 谢谢)

6 条回复  •  2021-03-28 20:26:46 +08:00
abersheeran
   1
abersheeran  
   2021 年 3 月 28 日
我纯 Golang 路人。但是 Gin 用的 Router 是基于 httprouter 的,httprouter 用的是 Radix Tree 。所以一定有一步是把节点加入树里。你再看看源码吧。
676529483
   2
676529483  
   2021 年 3 月 28 日
router 就是 Engine 对象,源码里面你看不是有 engine: group.engine 吗?
BrjGmj70f6CLT51u
   3
BrjGmj70f6CLT51u  
   2021 年 3 月 28 日
catror
   4
catror  
   2021 年 3 月 28 日
v1.POST() 和 v2.POST() 才是真正注册 route 的方法,group 只是方便理解和操作
cyjme
   5
cyjme  
   2021 年 3 月 28 日
那么 router 怎么知道 v1 的存在?


=======

// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}

源码中把 /v1 用于了 basePath 。

然后从 router.GET() 这个方法开始看,看什么时候用到 basePath (也就是 /v1 )

// GET is a shortcut for router.Handle("GET", path, 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) //进入
handlers = group.combineHandlers(handlers)
group.engine.addRoute( httpMethod, absolutePath, handlers)
return group.returnObj()
}

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath) //这里的 basePath 就是你的 "/v1",如果涉及到多重 group,可能是 /xxx/v1
}



=================
上面应该解答了你的疑问『怎么知道 v1 存在』,在每一次 GET/POST 执行的时候,都会根据之前的 basePath 进行计算。


继续深入,可以看 addRoute() ----> engine.trees 相关。然后就是 1 楼说的 httprouter tree 相关,如何快速查找匹配路由。
liulei281
   6
liulei281  
OP
   2021 年 3 月 28 日
@catror
@cyjme
谢谢, 明白怎么回事了.
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
© 2026 V2EX · 34ms · 3.9.8.5