Gin的是路由算法其实就是一个Trie树(也就是前缀树)。
注册路由预处理
我们在使用gin时通过下面的代码注册路由
普通注册
router.POST("/somePost", func(context *gin.Context) { context.String(http.StatusOK, "some post") })
使用中间件
router.Use(Logger())
使用Group
v1 := router.Group("v1") { v1.POST("login", func(context *gin.Context) { context.String(http.StatusOK, "v1 login") }) }
这些操作, 最终都会在反应到gin的路由树上
具体实现
// routergroup.go 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() }
在调用POST
, GET
, HEAD
等路由HTTP相关函数时, 会调用handle
函数
重点关注下面两个函数:
// routergroup.go func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { return joinPaths(group.basePath, relativePath) } func joinPaths(absolutePath, relativePath string) string { if relativePath == "" { return absolutePath } finalPath := path.Join(absolutePath, relativePath) appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/' if appendSlash { return finalPath + "/" } return finalPath }
在joinPaths
函数里面有段代码, 很有意思, 我还以为是写错了. 主要是path.Join的用法.
finalPath := path.Join(absolutePath, relativePath) appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
在当路由是
/user/
这种情况就满足了lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
. 主要原因是path.Join(absolutePath, relativePath)
之后, finalPath是user
综合来看, 在预处理阶段
- 在调用中间件的时候, 是将某个路由的handler处理函数和中间件的处理函数都放在了
Handlers
的数组中 - 在调用Group的时候, 是将路由的path上面拼上Group的值. 也就是
/user/:name
, 会变成v1/user:name
真正注册
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() }
调用group.engine.addRoute(httpMethod, absolutePath, handlers)
将预处理阶段的结果注册到gin Engine
的trees上
gin路由树
gin的路由树算法是一棵前缀树. 不过并不是只有一颗树, 而是每种方法(POST, GET ...)都有自己的一颗树func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) root := engine.trees.get(method) // <-- 看这里 if root == nil { root = new(node) engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) }
Gin的路由树的大致样子
type node struct { path string indices string children []*node handlers HandlersChain priority uint32 nType nodeType maxParams uint8 wildChild bool }
其实gin的实现不像一个真正的树, children []*node
所有的孩子都放在这个数组里面, 利用indices, priority变相实现一棵树
获取路由handler
当服务端收到客户端的请求时, 根据path
去trees
匹配到相关的路由, 拿到相关的处理handlers
... t := engine.trees for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree handlers, params, tsr := root.getValue(path, c.Params, unescape) // 看这里 if handlers != nil { c.handlers = handlers c.Params = params c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && path != "/" { if tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } break } ...
主要在下面这个函数里面调用程序注册的路由处理函数
func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }