1. 程式人生 > 其它 >gin的原始碼解讀4-gin的路由演算法

gin的原始碼解讀4-gin的路由演算法

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++
	}
}

參考連結