<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go Web 框架 Gin 路由的學(xué)習(xí)

          共 6133字,需瀏覽 13分鐘

           ·

          2021-07-13 22:20

          Gin 是目前應(yīng)用比較廣泛的Golang web 框架。目前,Github Star 數(shù)已經(jīng)達(dá)到了4.9w+. 框架的實(shí)現(xiàn)非常簡(jiǎn)單,可定制性非常強(qiáng),性能也比較好,深受golang開發(fā)者的喜愛(ài)。Gin 提供了web開發(fā)的一些基本功能。如路由,中間件,日志,參數(shù)獲取等,本文主要從源碼的角度分析Gin的路由實(shí)現(xiàn)。

          Gin 的路由功能是基于 https://github.com/julienschmidt/httprouter 這個(gè)項(xiàng)目實(shí)現(xiàn)的。目前也有很多其他Web框架也基于該路由框架做了二次開發(fā)。

          http 路由的接口

          在 Gin 中,為了兼容不同路由的引擎,定義了 IRoutes 和 IRouter 接口,便于替換其他的路由實(shí)現(xiàn)。(目前默認(rèn)是httprouter)

          下面是一個(gè)路由的接口定義:

          type IRoutes interface {
             Use(...HandlerFunc) IRoutes

            Handle(stringstring, ...HandlerFunc) IRoutes
            Any(string, ...HandlerFunc) IRoutes
            GET(string, ...HandlerFunc) IRoutes
            POST(string, ...HandlerFunc) IRoutes
            DELETE(string, ...HandlerFunc) IRoutes
            PATCH(string, ...HandlerFunc) IRoutes
            PUT(string, ...HandlerFunc) IRoutes
            OPTIONS(string, ...HandlerFunc) IRoutes
            HEAD(string, ...HandlerFunc) IRoutes

            StaticFile(stringstring) IRoutes
            Static(stringstring) IRoutes
            StaticFS(string, http.FileSystem) IRoutes
          }

          type HandlerFunc func(*Context)

          HandlerFunc 是一個(gè)方法類型的定義,我們定義的路由其實(shí)就是一個(gè)路徑與HandlerFunc 的映射關(guān)系。從上面的定義可以看出,IRoutes 主要定義了一些基于http方法、靜態(tài)方法的路徑和一組方法的映射。Use 方法是針對(duì)此路由的所有路徑映射一組方法,在使用上是為了給這些路由添加中間件。

          除了上面的定義外,Gin 還有路由組的抽象。

          type IRouter interface {
            IRoutes
            Group(string, ...HandlerFunc) *RouterGroup
          }

          路由組是在IRoutes 的基礎(chǔ)上,有了組的概念,組下面還可以掛在不同的組。組的概念可以很好的管理一組路由,路由組可以自己定義一套Handler方法(即一組中間件)。

          個(gè)人認(rèn)為IRouter的定義Group 應(yīng)該返回 IRouter,這樣可以把路由組更加抽象,也不會(huì)改變現(xiàn)有服務(wù)的使用。期待看下Gin源碼什么時(shí)候會(huì)按照這種定義方法修改過(guò)來(lái)。

          在Gin框架中,路由由 RouterGroup 實(shí)現(xiàn)。我們從構(gòu)造和路由查找兩個(gè)方面分析路由的實(shí)現(xiàn)。

          路由實(shí)現(xiàn)

          路由的本質(zhì)就是在給定 路徑與Handler映射關(guān)系 的前提下,當(dāng)提供新的url時(shí),給出對(duì)應(yīng)func 的過(guò)程。其中可能需要從url中提取參數(shù),或者按照 * 匹配 url 的情況。

          首先,我們看下Gin中路由結(jié)構(gòu)的定義。

          // gin engine
          type Engine struct {
            RouterGroup
            // ... 其他字段
            trees            methodTrees
          }

          // 每個(gè) http 方法定義一個(gè)森林
          type methodTrees []methodTree

          type methodTree struct {
            method string
            root   *node
          }

          // 路由組的定義
          type RouterGroup struct {
            Handlers HandlersChain
            basePath string
            engine   *Engine
            root     bool
          }

          從定義中可以看出,其實(shí)Gin 的 Engine 是復(fù)用了 RouterGroup。對(duì)于不同的 http method,都通過(guò)一個(gè)森林來(lái)存儲(chǔ)路由數(shù)據(jù)。下面是森林上每個(gè)節(jié)點(diǎn)的定義:

          type node struct {
            path      string  // 當(dāng)前路徑
            indices   string  // 對(duì)應(yīng)children 的前綴
            wildChild bool   // 可能是帶參數(shù)的,或者是 * 的,所以是野節(jié)點(diǎn)
            nType     nodeType  // 參數(shù)節(jié)點(diǎn),靜態(tài)節(jié)點(diǎn)
            priority  uint32  // 優(yōu)先級(jí) ,優(yōu)先級(jí)高的放在children 放在前面。
            children  []*node  // 子節(jié)點(diǎn)
            handlers  HandlersChain // 調(diào)用鏈
            fullPath  string  // 全路徑
          }

          從代碼實(shí)現(xiàn)上得知,這個(gè)森林其實(shí)是一個(gè)壓縮版本的Trie樹,每個(gè)節(jié)點(diǎn)會(huì)存儲(chǔ)前綴相同的路徑數(shù)據(jù)。下面,我們通過(guò)代碼來(lái)學(xué)習(xí)下路由的添加和刪除。

          路由的添加

          路由的添加,就是將path路徑添加到定義的Trie樹種,將handlers 添加到對(duì)應(yīng)的node 節(jié)點(diǎn)。

          func (n *node) addRoute(path string, handlers HandlersChain) {
            // 初始化和維護(hù)優(yōu)先級(jí)

            for {
              // 查找前綴
              i := longestCommonPrefix(path, n.path)

           // 原有路徑長(zhǎng)的情況下
           // 節(jié)點(diǎn)n 的 path 變?yōu)榱斯睬熬Y
           // 原有n 的path 路徑變?yōu)榱爽F(xiàn)有n 的子節(jié)點(diǎn)

           // 當(dāng)添加的path長(zhǎng)的情況
           // 需要分情況討論:
           // 1. 如果是一個(gè)帶參數(shù)的路徑,校驗(yàn)是否后續(xù)路徑不同,如果不同則繼續(xù)掃描下一段路徑
           // 2. 如果是帶 * 的路徑, 則直接報(bào)錯(cuò)
           // 3. 如果已經(jīng)有對(duì)應(yīng)的首字母,修改當(dāng)前node節(jié)點(diǎn),并繼續(xù)掃描,并掃描下一段路徑
           // 4. 如果非參數(shù)或者 * 匹配的方法,則插入一個(gè)子節(jié)點(diǎn)路徑,并完成掃描

           // 最后注冊(cè)handlers,添加fullPath
              n.handlers = handlers
              n.fullPath = fullPath
              return
            }
          }

          從上面的代碼注釋可以看出,路由的添加,主要是通過(guò)不斷對(duì)比當(dāng)前節(jié)點(diǎn)的path和添加的path,做添加節(jié)點(diǎn)或者節(jié)點(diǎn)變更的操作,達(dá)到添加path的目的。

          路徑查找

          在服務(wù)請(qǐng)求時(shí),路由的責(zé)任就是給定一個(gè)url請(qǐng)求,拿到節(jié)點(diǎn)保存的handlers,以及url中包含的參數(shù)值。下面是對(duì)一個(gè)url 的解析實(shí)現(xiàn)。

          type nodeValue struct {
           handlers HandlersChain
           params   *Params
           tsr      bool
           fullPath string
          }

          func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
          walk: // Outer loop for walking the tree
            for {
              prefix := n.path

          // 如果比當(dāng)前節(jié)點(diǎn)路徑要長(zhǎng):
          //  - 非參數(shù)類型或模糊匹配的URL,如果和當(dāng)前節(jié)點(diǎn)前綴匹配,直接查看 node 的子節(jié)點(diǎn)
          //  - 參數(shù)化的node, 按照 / 分割提取參數(shù),如果未結(jié)束,則繼續(xù)匹配剩下的路徑,否則返回結(jié)果。
          //  - * 匹配的node,將剩余的路徑添加到 param 中直接返回。
          // 如果和當(dāng)前節(jié)點(diǎn)相等,那就直接返回即可。
          // 這里還做了非本方法的路徑匹配,用戶返回http 方法錯(cuò)誤的異常報(bào)告。
            }
          }

          一個(gè)例子

          下面通過(guò)一個(gè)例子,方便我們快速理解router的實(shí)現(xiàn)。

          加入下面的一個(gè)路徑:

          /search/ 

          /support/ 

          /blog/:post/ 

          /about-us/team/ 

          /contact/


          在樹中,我們看到的樣子如下:

            Path
            \
            ├s
            |├earch\
            |└upport\
            ├blog\
            |    └:post
            |         └\
            ├about-us\
            |        └team\
            └contact\

          在做路由查找時(shí),通過(guò)路徑不斷匹配,找到對(duì)應(yīng)的子節(jié)點(diǎn)。拿到對(duì)應(yīng)子節(jié)點(diǎn)下的handler。完成路由的匹配。

          總結(jié)

          1. httprouter 沒(méi)有實(shí)現(xiàn)了routergroup功能,只是實(shí)現(xiàn)了router 的功能,在gin中做了實(shí)現(xiàn)
          2. 通過(guò)Trie樹實(shí)現(xiàn)路由是比較基礎(chǔ)的一種實(shí)現(xiàn)方法,除了這種方法外,還可以考慮通過(guò)正則的方式提取路由。
          3. Gin http 服務(wù)是基于 Go 的 net/http 庫(kù)的, net/http 庫(kù)中handler 的實(shí)現(xiàn)是針對(duì)不同的 http method 的,所以需要在engine 中針對(duì)不同的method 提供不同的trie 樹。
          4. 在添加路由時(shí),如果使用了 any 方法,則在每個(gè)http method 下都會(huì)添加一樣的路徑。
          5. middleware 本質(zhì)上只是一個(gè) HandlerFunc.


          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。


          瀏覽 28
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99视频+国产日韩欧美 | 亚洲欧美视频免费观看 | 日韩一区二区视频在线观看 | 国产精品精品国产婷婷这里Av | 日韩视频在线观看一区二区三区 |