Golang 框架 gin 運行源碼分析

【導讀】gin 框架是 go 語言 web 框架中非常流行的框架之一,本文詳細解讀了 gin 框架的優點與其實現。

解析 gin 框架中數據流轉原理

分析流程

我們已經瞭解了 http 包中 server 的啓動方法, 現在我們分析 gin 處理流程

gin 框架號稱 路由提速了 40 倍, 所以,到底他是哪裏快呢?

我們還是看下他啓動服務時做哪些事情

package main

import "github.com/gin-gonic/gin"

func main() {
    #生成gin實例
    r := gin.Default()
    #註冊路由關係定義具體的路由處理方法
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    #啓動服務
    r.Run() // listen and serve on 0.0.0.0:8080
}

和我們剛纔看到的http_server非常像, 但是路由管理器這塊比較強大

較高級的語法糖, 可以直接指定 HTTP 方法名稱, 內部封裝 json 響應實現。

我們繼續從啓動方法r.Run()入手, 看下他的內部實現邏輯

#gin.go
func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()
    #解析地址
    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    #調用http包的ListenAndServe方法
    err = http.ListenAndServe(address, engine)
    return
}

我們可以發現, 這裏仍然使用的是 http 包的ListenAndServe方法, 和我們上一章調用類似,主要區別是其第二個參數, 這裏是 gin 框架生成 gin 實例。

並且我們知道, 第二個參數的主要意義是, 關聯路由管器對象, 即 gin 框架承接了 url 和處理函數的路由管理。其他 http 請求這一套, 繼續複用原生包的 http 接口

我們截取下 http 包的server.go的代碼

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    #獲取路由管理對象, 這裏即是我們調用`ListenAndServe`的第二個參數
    handler := sh.srv.Handler
    #如果沒有設置則使用默認的defaultServerMux
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    #進入路由管理對象的ServerHttp方法中進行路由分發
    handler.ServeHTTP(rw, req)
}

handler.ServerHTTP方法開始,gin 框架開始接管 http 請求

因此我們看下 gin.go 中的 ServerHTTP 方法

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    #爲減少gc重複回收 這裏使用sync.pool管理自定義Context對象
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    #分發處理請求
    engine.handleHTTPRequest(c)

    #將Context對象放回
    engine.pool.Put(c)
}

我們看下上面代碼, 主要做以下事情

我們着重看下 engine.handleHTTPRequest分發內部邏輯

func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method
    rPath := c.Request.URL.Path
    unescape := false
    if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
        rPath = c.Request.URL.RawPath
        unescape = engine.UnescapePathValues
    }
    #獲取請求路徑
    rPath = cleanPath(rPath)

    // 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, unescape)

        if value.handlers != nil {
            #更新Context對象屬性將路由地址管理的多個路由函數都交給Context管理
            c.handlers = value.handlers
            c.Params = value.params
            c.fullPath = value.fullPath
            #遞歸的執行關聯的handler方法
            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
    }

    if engine.HandleMethodNotAllowed {
        for _, tree := range engine.trees {
            if tree.method == httpMethod {
                continue
            }
            if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
                c.handlers = engine.allNoMethod
                serveError(c, http.StatusMethodNotAllowed, default405Body)
                return
            }
        }
    }
    c.handlers = engine.allNoRoute
    serveError(c, http.StatusNotFound, default404Body)
}

上述代碼完成完整路由,進行數據的最終響應, 我們把核心代碼摘出來。

#根據路徑請求參數找到對應的 路由處理函數 和我們上一章節實現類似
value := root.getValue(rPath, c.Params, unescape)

#遞歸的執行關聯的handler方法
c.Next()

我們先看 root.getValue方法, 其主要進行 路由操作,這裏是採用的基樹Radix_tree算法,實現較複雜,不再這裏詳細展開了。

我們再看下c.Next()方法,這個方法的核心,主要是方便接入中間件 (Middleware),使得代碼模塊化操作。

我們看下 Next 的具體實現

func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {
        #執行關聯的中間件方法或者 實際路由處理函數
        c.handlers[c.index](c)
        c.index++
    }
}

描述相對抽象, 我們新建一個 demo 例子

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "log"
    "time"
)

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()

        //請求處理前 Set example variable
        c.Set("example", "12345")

        //請求下一個中間件
        c.Next()

        //請求處理之後
        latency := time.Since(t)
        log.Print(latency)

        // access the status we are sending
        status := c.Writer.Status()
        log.Println(status)

    }
}

func main() {
    r := gin.New()

    //引入Logger()中間件
    r.Use(Logger())

    r.GET("/test", func(c *gin.Context) {
        example := c.MustGet("example").(string)
        //打印關聯的全部處理路由函數
        fmt.Println(c.HandlerNames())
        // it would print: "12345"
        log.Println(example)
    })

    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

上面例子中, 我們引入一個 Logger 中間件, 在路由函數中打印全部路由函數名稱,輸出如下

[main.Logger.func1 main.main.func1]

即能看到,這裏將我們新加入的 Logger 中間件,轉換成了句柄函數,即 / testURI 對應的路由函數有兩個, 這兩個會按照先後順序, 依次執行。

即先執行 main.Logger.func1, 後執行 main.main.func1, 結合我們上面的 Next 方法實現, 我們就能清楚的知道,其調用關係

實現了 Next 方法的僞代碼,加深理解:

    進入Next
        index++= 0
        調用handler方法即Logger方法(如果index<總handler數)
            進入Logger方法
            //logger業務邏輯
            進入Next方法
                //index++ = 1
                這裏還可以繼續加入其他中間件
                //進入fun1方法
                //從fun1方法離開
                //index++ = 2
            離開Next方法

            //logger業務邏輯處理
        離開Logger方法
        index++ = 3
    離開Next方法
邏輯處理完畢

這裏比較重要的概念是, 處理函數有先後執行關係, 並且處理函數可以通過調用 Abort 方法, 提前返回,不用遞歸調用到實際處理函數。

這些中間件,可以方便的使我們的業務代碼接入權限校驗 auth,日誌管理等其他功能模塊。

總結

核心亮點:路由管理對象的實現 + 中間件的實現原理

轉自:litonglitong

1905.github.io/golang%E5%BC%80%E5%8F%91/2019/08/14/golang-gin-webserver/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/D6Ls3fUZnmimg8EgBDuXcQ