gin源碼閱讀之二 -- 揭開gin的神秘面紗

上一篇簡(jiǎn)單介紹了gin, 以及net/http是如何數(shù)據(jù)流轉(zhuǎn)的, 本篇將詳細(xì)介紹gin

數(shù)據(jù)如何在gin中流轉(zhuǎn)

package main

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

func main() {
    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
}

這段代碼的大概流程:

  1. r := gin.Default()初始化了相關(guān)的參數(shù)
  2. /ping將路由及處理handler注冊(cè)到路由樹中
  3. 啟動(dòng)服務(wù)

r.Run()其實(shí)調(diào)用的是err = http.ListenAndServe(address, engine), 結(jié)合上一篇文章可以看出來(lái), gin其實(shí)利用了net/http的處理過(guò)程

ServeHTTP的作用

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

因此, gin框架的Engine最重要的函數(shù)就是func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request). Engine實(shí)現(xiàn)了Handler(server.go#L84-86), 讓net/http請(qǐng)求數(shù)據(jù)最終流回到gin中, 從gin的route tree中取到相關(guān)的中間件及handler, 來(lái)處理客戶端的request

Engine

在整個(gè)gin框架中最重要的一個(gè)struct就是Engine, 它包含路由, 中間件, 相關(guān)配置信息等. Engine的代碼主要就在gin.go中

Engine中比較重要的幾個(gè)屬性, 其他的屬性暫時(shí)全部省略掉

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

Engine有幾個(gè)比較主要的函數(shù):

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(數(shù)組)設(shè)置成nil, basePath設(shè)置成/
  3. 為了使用方便, RouteGroup里面也有一個(gè)Engine指針, 這里將剛剛初始化的engine賦值給了RouterGroup的engine指針
  4. 為了防止頻繁的context GC造成效率的降低, 在Engine里使用了sync.Pool, 專門存儲(chǔ)gin的Context
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

Default()New()幾乎一模一樣, 就是調(diào)用了gin內(nèi)置的Logger(), Recovery()中間件.

Use()

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

Use()就是gin的引入中間件的入口了. 仔細(xì)分析這個(gè)函數(shù), 不難發(fā)現(xiàn)Use()其實(shí)是在給RouteGroup引入中間件的. 具體是如何讓中間件在RouteGroup上起到作用的, 等說(shuō)到RouteGroup再具體說(shuō).

engine.rebuild404Handlers()
engine.rebuild405Handlers()

這兩句函數(shù)其實(shí)在這里沒有任何用處. 我感覺這里是給gin的測(cè)試代碼用的. 我們?cè)谑褂?code>gin的時(shí)候, 要想在404, 405添加處理過(guò)程, 可以通過(guò)NoRoute(), NoMethod()來(lái)處理.

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注冊(cè)到engine的trees中. 注意這里為什么是HandlersChain呢, 可以簡(jiǎn)單說(shuō)一下, 就是將中間件和處理函數(shù)都注冊(cè)到method, path的tree中了.

Run系列函數(shù)

Run, RunTLS, RunUnix, RunFd 這些函數(shù)其實(shí)都是最終在調(diào)用net/http的http服務(wù).

ServeHTTP

這個(gè)函數(shù)相當(dāng)重要了, 主要有這個(gè)函數(shù)的存在, 才能將請(qǐng)求轉(zhuǎn)到gin中, 使用gin的相關(guān)函數(shù)處理request請(qǐng)求.

...

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, 從Enginetrees中獲取已經(jīng)注冊(cè)的handler

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

Next()執(zhí)行handler的操作. 其實(shí)也就是下面的函數(shù)

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

如果在trees中沒有找到對(duì)應(yīng)的路由, 則會(huì)執(zhí)行serveError函數(shù), 也就是404相關(guān)的.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)發(fā)自:http://shanshanpt.github.io/2016/05/03/go-gin.html gi...
    dncmn閱讀 6,213評(píng)論 0 1
  • 最近將gin的源碼看了一遍, 會(huì)用幾篇文章將gin的流程及流程做一個(gè)梳理, 下面進(jìn)入正題. gin框架預(yù)覽 上圖大...
    HHFCodeRv閱讀 3,485評(píng)論 2 11
  • 1. 簡(jiǎn)介 這篇文章主要的目的是分析理解express的源碼,網(wǎng)絡(luò)上關(guān)于源碼的分析已經(jīng)數(shù)不勝數(shù),這篇文章準(zhǔn)備另辟蹊...
    沒事造輪子閱讀 1,482評(píng)論 0 8
  • 所謂框架 框架一直是敏捷開發(fā)中的利器,能讓開發(fā)者很快的上手并做出應(yīng)用,甚至有的時(shí)候,脫離了框架,一些開發(fā)者都不會(huì)寫...
    人世間閱讀 217,097評(píng)論 11 242
  • 引用:https://github.com/WangZhechao/expross 1.簡(jiǎn)介 這篇文章是分析exp...
    宮若石閱讀 3,248評(píng)論 1 8

友情鏈接更多精彩內(nèi)容