Gin
是go
語言的一款輕量級框架,風格簡單樸素,支持中間件,動態路由等功能。 gin項目github地址
路由是web框架的核心功能。在沒有讀過 gin
的代碼以前,在我眼裏的路由實現是這樣的:根據路由裏的 /
把路由切分紅多個字符串數組,而後按照相同的前子數組把路由構形成樹的結構;尋址時,先把請求的 url
按照 /
切分,而後遍歷樹進行尋址。node
好比:定義了兩個路由 /user/get
,/user/delete
,則會構造出擁有三個節點的路由樹,根節點是 user
,兩個子節點分別是 get
delete
。git
上述是一種實現路由樹的方式,且比較直觀,容易理解。對 url 進行切分、比較,時間複雜度是 O(2n)
。github
Gin的路由實現使用了相似前綴樹的數據結構,只需遍歷一遍字符串便可,時間複雜度爲O(n)
。web
固然,對於一次 http 請求來講,這點路由尋址優化能夠忽略不計。數組
Gin
的 Engine
結構體內嵌了 RouterGroup
結構體,定義了 GET
,POST
等路由註冊方法。數據結構
Engine
中的 trees
字段定義了路由邏輯。trees
是 methodTrees
類型(其實就是 []methodTree
),trees
是一個數組,不一樣請求方法的路由在不一樣的樹(methodTree
)中。框架
最後,methodTree
中的 root
字段(*node
類型)是路由樹的根節點。樹的構造與尋址都是在 *node
的方法中完成的。函數
UML 結構圖優化
trees
是個數組,數組裏會有不一樣請求方法的路由樹。ui
node 結構體定義以下
type node struct { path string // 當前節點相對路徑(與祖先節點的 path 拼接可獲得完整路徑) indices string // 因此孩子節點的path[0]組成的字符串 children []*node // 孩子節點 handlers HandlersChain // 當前節點的處理函數(包括中間件) priority uint32 // 當前節點及子孫節點的實際路由數量 nType nodeType // 節點類型 maxParams uint8 // 子孫節點的最大參數數量 wildChild bool // 孩子節點是否有通配符(wildcard) }
關於 path
和 indices
,實際上是使用了前綴樹的邏輯。
舉個栗子:
若是咱們有兩個路由,分別是 /index
,/inter
,則根節點爲 {path: "/in", indices: "dt"...}
,兩個子節點爲{path: "dex", indices: ""},{path: "ter", indices: ""}
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
就是[]
。
Gin 中定義了四種節點類型:
const ( static nodeType = iota // 普通節點,默認 root // 根節點 param // 參數路由,好比 /user/:id catchAll // 匹配全部內容的路由,好比 /article/*key )
param
與 catchAll
使用的區別就是 :
與 *
的區別。*
會把路由後面的全部內容賦值給參數 key
;但 :
能夠屢次使用。
好比:/user/:id/:no
是合法的,但 /user/*id/:no
是非法的,由於 *
後面全部內容會賦值給參數 id
。
若是孩子節點是通配符(*
或者:
),則該字段爲 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) {})
獲得的路由樹結構圖爲:
附一篇前綴樹的文章,前綴樹和後綴樹