1. 程式人生 > 其它 >gin原始碼解讀2-揭開gin的神祕面紗

gin原始碼解讀2-揭開gin的神祕面紗

資料如何在gin中流轉

func main() {
	gin.SetMode(gin.DebugMode)  // 設定為開發模式
	router := gin.Default()
	_ = router.SetTrustedProxies([]string{"172.19.10.13"})  // 設定信任代理
	router.GET("/ping", func(context *gin.Context) {
		context.JSON(http.StatusOK, gin.H{"message": "pong"})
		fmt.Println(context.ClientIP())
	})
	_ = router.Run(":8000")
}

這段程式碼的大概流程:
1.r := gin.Default()初始化了相關的引數 2./ping將路由及處理handler註冊到路由樹中 3.啟動服務
r.Run()其實呼叫的是err = http.ListenAndServe(address, engine), 結合上一篇文章可以看出來, gin其實利用了net/http的處理過程

ServeHTTP的作用

上一篇文章有提到DefaultServeMux, 其實DefaultServeMux實現了ServeHTTP(ResponseWriter, *Request), 在request執行到server.go的serverHandler{c.server}.ServeHTTP(w, w.req)這一行的時候, 從DefaultServeMux取到了相關路由的處理handler.

因此, gin框架的Engine最重要的函式就是func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request). Engine實現了Handler(server.go#L84-86), 讓net/http請求資料最終流回到gin中, 從gin的route tree中取到相關的中介軟體及handler, 來處理客戶端的request

Engine

在整個gin框架中最重要的一個struct就是Engine, 它包含路由, 中介軟體, 相關配置資訊等. Engine的程式碼主要就在gin.go中
Engine中比較重要的幾個屬性, 其他的屬性暫時全部省略掉

type Engine struct {
    RouterGroup // 路由
    pool             sync.Pool  // context pool
    trees            methodTrees // 路由樹
    // html template及其他相關屬性先暫時忽略
}

Engine有幾個比較主要的函式:

New() Default()

func New() *Engine {
    // ...
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        // ...
        trees: make(methodTrees, 0, 9),
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

New()主要乾的事情:

1.初始化了Engine 2.將RouterGroup的Handlers(陣列)設定成nil, basePath設定成/ 3.為了使用方便, RouteGroup裡面也有一個Engine指標, 這裡將剛剛初始化的engine賦值給了RouterGroup的engine指標 4.為了防止頻繁的context GC造成效率的降低, 在Engine裡使用了sync.Pool, 專門儲存gin的Context

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

Default()跟New()幾乎一模一樣, 就是呼叫了gin內建的Logger(), Recovery()中介軟體.

Use()

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

Use()就是gin的引入中介軟體的入口了. 仔細分析這個函式, 不難發現Use()其實是在給RouteGroup引入中介軟體的. 具體是如何讓中介軟體在RouteGroup上起到作用的, 等說到RouteGroup再具體說.

engine.rebuild404Handlers()
engine.rebuild405Handlers()

這兩句函式其實在這裡沒有任何用處. 我感覺這裡是給gin的測試程式碼用的. 我們在使用gin的時候, 要想在404, 405新增處理過程, 可以通過NoRoute(), NoMethod()來處理.

addRoute()

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    ...
    root := engine.trees.get(method)
    if root == nil {
        root = new(node)
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
}

這段程式碼就是利用method, path, 將handlers註冊到engine的trees中. 注意這裡為什麼是HandlersChain呢, 可以簡單說一下, 就是將中介軟體和處理函式都註冊到method, path的tree中了.

Run()系列函式

Run, RunTLS, RunUnix, RunFd 這些函式其實都是最終在呼叫net/http的http服務.

ServeHTTP

這個函式相當重要了, 主要有這個函式的存在, 才能將請求轉到gin中, 使用gin的相關函式處理request請求.

...

t := engine.trees

for i, tl := 0, len(t); i < tl; i++ {
    if t[i].method != httpMethod {
        continue
    }
    root := t[i].root

    handlers, params, tsr := root.getValue(path, c.Params, unescape)
    if handlers != nil {
        c.handlers = handlers
        c.Params = params
        c.Next()
        c.writermem.WriteHeaderNow()
        return
    }
    ...
}

利用request中的path, 從Engine的trees中獲取已經註冊的handler

func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

在Next()執行handler的操作. 其實也就是下面的函式

func(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "pong",
    })
}

如果在trees中沒有找到對應的路由, 則會執行serveError函式, 也就是404相關的.

參考連結