Gin 框架的路由結構剖析
Gin
是go
語言的一款輕量級框架,風格簡單樸素,支援中介軟體,動態路由等功能。ofollow,noindex" target="_blank">gin專案github地址
路由是web框架的核心功能。在沒有讀過gin
的程式碼之前,在我眼裡的路由實現是這樣的:根據路由裡的/
把路由切分成多個字串陣列,然後按照相同的前子陣列把路由構造成樹的結構;定址時,先把請求的url
按照/
切分,然後遍歷樹進行定址。
比如:定義了兩個路由/user/get
,/user/delete
,則會構造出擁有三個節點的路由樹,根節點是user
,兩個子節點分別是get
delete
。
上述是一種實現路由樹的方式,且比較直觀,容易理解。對 url 進行切分、比較,時間複雜度是O(2n)
。
Gin的路由實現使用了類似字首樹的資料結構,只需遍歷一遍字串即可,時間複雜度為O(n)
。
當然,對於一次 http 請求來說,這點路由定址優化可以忽略不計。
Engine
Gin
的Engine
結構體內嵌了RouterGroup
結構體,定義了GET
,POST
等路由註冊方法。
Engine
中的trees
欄位定義了路由邏輯。trees
是methodTrees
型別(其實就是[]methodTree
),trees
是一個數組,不同請求方法的路由在不同的樹(methodTree
)中。
最後,methodTree
中的root
欄位(*node
型別)是路由樹的根節點。樹的構造與定址都是在*node
的方法中完成的。
UML 結構圖
trees
是個陣列,數組裡會有不同請求方法的路由樹。
node
node 結構體定義如下
type node struct { pathstring// 當前節點相對路徑(與祖先節點的 path 拼接可得到完整路徑) indicesstring// 所以孩子節點的path[0]組成的字串 children[]*node// 孩子節點 handlersHandlersChain// 當前節點的處理函式(包括中介軟體) priorityuint32// 當前節點及子孫節點的實際路由數量 nTypenodeType// 節點型別 maxParams uint8// 子孫節點的最大引數數量 wildChild bool// 孩子節點是否有萬用字元(wildcard) }
path 和 indices
關於path
和indices
,其實是使用了字首樹的邏輯。
舉個栗子:
如果我們有兩個路由,分別是/index
,/inter
,則根節點為{path: "/in", indices: "dt"...}
,兩個子節點為{path: "dex", indices: ""},{path: "ter", indices: ""}
handlers
handlers
裡儲存了該節點對應路由下的所有處理函式,處理業務邏輯時是這樣的:
func (c *Context) Next() { c.index++ for s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) } }
一般來說,除了最後一個函式,前面的函式被稱為中介軟體 。
如果某個節點的handlers
為空,則說明該節點對應的路由不存在。比如上面定義的根節點對應的路由/in
是不存在的,它的handlers
就是[]
。
nType
Gin 中定義了四種節點型別:
const ( static nodeType = iota // 普通節點,預設 root// 根節點 param// 引數路由,比如 /user/:id catchAll// 匹配所有內容的路由,比如 /article/*key )
param
與catchAll
使用的區別就是:
與*
的區別。*
會把路由後面的所有內容賦值給引數key
;但:
可以多次使用。
比如:/user/:id/:no
是合法的,但/user/*id/:no
是非法的,因為*
後面所有內容會賦值給引數id
。
wildChild
如果孩子節點是萬用字元(*
或者:
),則該欄位為true
。
一個路由樹的例子
定義路由如下:
r.GET("/", func(context *gin.Context) {}) r.GET("/index", func(context *gin.Context) {}) r.GET("/inter", func(context *gin.Context) {}) r.GET("/go", func(context *gin.Context) {}) r.GET("/game/:id/:k", func(context *gin.Context) {})
得到的路由樹結構圖為:
附一篇字首樹的文章,字首樹和字尾樹
以上。