gin的原始碼解讀4-gin的路由演算法
阿新 • • 發佈:2021-12-20
gin的路由演算法
gin的是路由演算法其實就是一個Trie樹(也就是字首樹). 有關資料結構的可以自己去網上找相關資料檢視.
註冊路由預處理
我們在使用gin時通過下面的程式碼註冊路由
普通註冊
router.GET("/ping", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{"message": "pong"})
})
使用中介軟體
router.Use(gin.Recovery())
使用Group
v1 := router.Group("v1") v1.GET("/ping", func(context *gin.Context) { context.JSON(http.StatusOK, gin.H{"message": "pong"}) })
具體實現
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函式
如果呼叫了中介軟體的話, 會呼叫下面函式
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
如果使用了Group的話, 會呼叫下面函式
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { return &RouterGroup{ Handlers: group.combineHandlers(handlers), basePath: group.calculateAbsolutePath(relativePath), engine: group.engine, } }
重點關注下面兩個函式:
// routergroup.go:L208-217
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
}
綜合來看, 在預處理階段
1.在呼叫中介軟體的時候, 是將某個路由的handler處理函式和中介軟體的處理函式都放在了Handlers的陣列中 2.在呼叫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)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
gin 路由最終的樣子大概是下面的樣子
type node struct {
path string
indices string
wildChild bool
nType nodeType
priority uint32
children []*node // child nodes, at most 1 :param style node at the end of the array
handlers HandlersChain
fullPath string
}
其實gin的實現不像一個真正的樹, children []*node所有的孩子都放在這個數組裡面, 利用indices, priority變相實現一棵樹
獲取路由handler
當服務端收到客戶端的請求時, 根據path去trees匹配到相關的路由, 拿到相關的處理handlers
...
// Find root of the tree for the given HTTP method
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
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers // 看這裡
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if value.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++
}
}