go-gin源碼分析

gin的http服務(wù)啟動(dòng)

test := gin.Default()
test.Run()

上面簡(jiǎn)簡(jiǎn)單單的兩行代碼,就能開啟一個(gè)基于gin框架的http服務(wù)
下面復(fù)雜的這么多行代碼也是在啟動(dòng)一個(gè)基于gin的http服務(wù)贮尖。
了解過(guò)GOhttp包的應(yīng)該很好理解下面的代碼

//返回一個(gè)gin的engine對(duì)象
func InitRouter() *gin.Engine{...}
//構(gòu)建一個(gè)server對(duì)象液茎,傳入engine作為handler
s := &http.Server{
        Addr:           fmt.Sprintf(":%d", setting.ServerSetting.HttpPort),
        Handler:        router,
        ReadTimeout:    setting.ServerSetting.ReadTimeout,
        WriteTimeout:   setting.ServerSetting.WriteTimeout,
        MaxHeaderBytes: 1 << 20,
    }
//將http作為協(xié)程開啟
    go func() {
        if err := s.ListenAndServe(); err != nil {
            log.Printf("Listen: %s\n", err)
        }
    }()
  //監(jiān)聽系統(tǒng)的信號(hào)(crtl+c)
    quit := make(chan os.Signal)
    signal.Notify(quit, os.Interrupt)
    <- quit

    log.Println("Shutdown Server ...")
//利用contex來(lái)關(guān)閉http服務(wù)(防止服務(wù)意外關(guān)閉所以留5s的掃尾時(shí)間)
    ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
    defer cancel()
    if err := s.Shutdown(ctx); err != nil {
        log.Fatal("Server Shutdown:", err)
    }

    log.Println("Server exiting")

Gin默認(rèn)的啟動(dòng)方式

我們先來(lái)看看兩行代碼的http服務(wù)是怎么開啟的

func Default() *Engine {
//檢驗(yàn)版本
    debugPrintWARNINGDefault()
//生成一個(gè)engine對(duì)象
    engine := New()
//默認(rèn)使用Logger票彪,Recovery中間件
    engine.Use(Logger(), Recovery())
    return engine
}
//參數(shù)就是返回一個(gè)Engine對(duì)象(如果一個(gè)函數(shù)太長(zhǎng)援所,先看參數(shù)和返回值了解大概是干什么的)
func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        //路由組
        // 給框架實(shí)例綁定上一個(gè)路由組
        RouterGroup: RouterGroup{
            // engine.Use 注冊(cè)的中間方法到這里
            Handlers: nil,
            basePath: "/",
            // 是否是路由根節(jié)點(diǎn)
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        // 路由樹
        // 我們的路由最終注冊(cè)到了這里
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJsonPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    // 綁定從實(shí)例池獲取上下文的閉包方法
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

還有Run方法

func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    // 執(zhí)行http包的ListenAndServe方法 啟動(dòng)路由
    // engine實(shí)現(xiàn)了http.Handler接口 所以在這里作為參數(shù)傳參進(jìn)去
    // 后面我們?cè)倏磂ngine.ServeHTTP的具體邏
    err = http.ListenAndServe(address, engine)
    return
}

了解了這個(gè)其實(shí)就很容易理解第二個(gè)是啟動(dòng)gin http服務(wù)的揭朝。
那么engine為什么能作為handler傳入server呢廊营,那肯定是實(shí)現(xiàn)了ServeHTTP方法即實(shí)現(xiàn)了handler接口

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // 獲取一個(gè)上下文實(shí)例
    // 從實(shí)例池獲取 性能高
    c := engine.pool.Get().(*Context)
    // 重置獲取到的上下文實(shí)例的http.ResponseWriter
    c.writermem.reset(w)
    // 重置獲取到的上下文實(shí)例*http.Request
    c.Request = req
    // 重置獲取到的上下文實(shí)例的其他屬性
    c.reset()
      //這里重置是應(yīng)為從對(duì)象池中拿出來(lái)的contex可能具有臟數(shù)據(jù)
      //(也就是可能是其他請(qǐng)求的contex,有興趣可以試著注釋掉這里的重置)
      //看看會(huì)發(fā)生什么
    // 實(shí)際處理請(qǐng)求的地方
    // 傳遞當(dāng)前的上下文
    engine.handleHTTPRequest(c)
    //歸還上下文實(shí)例
    engine.pool.Put(c)
}

果然 engine實(shí)現(xiàn)了ServeHTTP方法那么在處理請(qǐng)求時(shí),也是主要調(diào)用了這個(gè)方法萝勤。
接下來(lái)就好好分析engine以及engine的這個(gè)ServeHTTP方法做了什么事

engine

type Engine struct {
//這里的組合代表engine也實(shí)現(xiàn)了RouterGroup(就當(dāng)作繼承吧)
    RouterGroup  //路由組
       ....//去掉一些不太重要的結(jié)構(gòu)
    pool             sync.Pool//contex的對(duì)象池
    trees            methodTrees//handler樹
}

type methodTrees []methodTree

type methodTree struct {
    method string
    root   *node
}

type node struct {
    path      string
    indices   string
    children  []*node //子節(jié)點(diǎn)
    handlers  HandlersChain //handler鏈
    priority  uint32
    nType     nodeType
    maxParams uint8
    wildChild bool
}

type RouterGroup struct {
    Handlers HandlersChain
    basePath string
    engine   *Engine
    root     bool
}

type HandlerFunc func(*Context) //Contex也算是gin的核心了,稍后探索
type HandlersChain []HandlerFunc

這些結(jié)構(gòu)稍后再探索呐伞,先從一個(gè)gin的流程開始

gin注冊(cè)路由

路由其實(shí)分為兩個(gè)部分敌卓,一個(gè)是路由設(shè)置部分,一個(gè)是路由匹配部分伶氢。
路由其實(shí)并不僅僅是url趟径,還包括HTTP的請(qǐng)求方法,而實(shí)現(xiàn)一個(gè)REST風(fēng)格的http請(qǐng)求癣防,需要支持REST支持的方法蜗巧,比如GET,PUT蕾盯,POST幕屹,DELETE,OPTION等级遭。

r.GET("/auth", api.GetAuth)
r.Handle(http方法,path,handler)

而gin中注冊(cè)路由的方式就是調(diào)用對(duì)應(yīng)的http方法望拖,或是使用r.Handle注冊(cè)這里的r是engine

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    //向路由組注冊(cè)路由
    return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    //合并中間件得handle和路由的handle
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    if finalSize >= int(abortIndex) {
        panic("too many handlers")
    }
    mergedHandlers := make(HandlersChain, finalSize)
//注意這兩個(gè)copy,是RouterGroup的handler在用戶自定義handler前面挫鸽。
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    assert1(path[0] == '/', "path must begin with '/'")
    assert1(method != "", "HTTP method can not be empty")
    assert1(len(handlers) > 0, "there must be at least one handler")

    debugPrintRoute(method, path, handlers)
    // 檢查有沒有對(duì)應(yīng)method集合的路由
    //所以這里是每一個(gè)方法一個(gè)樹
    root := engine.trees.get(method)
    if root == nil {
        // 沒有 創(chuàng)建一個(gè)新的路由節(jié)點(diǎn)
        root = new(node)
        root.fullPath = "/"
        // 添加該method的路由tree到當(dāng)前的路由到路由樹里
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
}

可能這里有點(diǎn)暈说敏,但是只要記住handlers或者handlerschain都是func(*Contex)這個(gè)方法,而這個(gè)Contex里面有著GO原生http服務(wù)處理所有的的東西就好了丢郊,把它看成加強(qiáng)版的處理業(yè)務(wù)邏輯的對(duì)象

所以上述的這些函數(shù)就把路由與對(duì)應(yīng)的handlers設(shè)置好了盔沫。

那么來(lái)看看路由是如何匹配的

路由匹配

還記得前面engine實(shí)現(xiàn)的ServeHTTP方法嗎

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // 獲取一個(gè)上下文實(shí)例
    // 從實(shí)例池獲取 性能高
    c := engine.pool.Get().(*Context)
    // 重置獲取到的上下文實(shí)例的http.ResponseWriter
    c.writermem.reset(w)
    // 重置獲取到的上下文實(shí)例*http.Request
    c.Request = req
    // 重置獲取到的上下文實(shí)例的其他屬性
    c.reset()
      //這里重置是應(yīng)為從對(duì)象池中拿出來(lái)的contex可能具有臟數(shù)據(jù)
      //(也就是可能是其他請(qǐng)求的contex,有興趣可以試著注釋掉這里的重置)
      //看看會(huì)發(fā)生什么
    // 實(shí)際處理請(qǐng)求的地方
    // 傳遞當(dāng)前的上下文
    engine.handleHTTPRequest(c)
    //歸還上下文實(shí)例
    engine.pool.Put(c)
}

真正處理的就是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++ {
        // 這里尋找當(dāng)前請(qǐng)求method的路由樹節(jié)點(diǎn)
        if t[i].method != httpMethod {
            continue
        }
        // 找到節(jié)點(diǎn)
        root := t[i].root
        // Find route in tree
        // 尋找當(dāng)前請(qǐng)求的路由
        value := root.getValue(rPath, c.Params, unescape)
        if value.handlers != nil {
            // 把找到的handles賦值給上下文
            c.handlers = value.handlers
            // 把找到的入?yún)①x值給上下文
            c.Params = value.params
            c.fullPath = value.fullPath
            // 執(zhí)行handle
            c.Next()
            // 處理響應(yīng)內(nèi)容
            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)
}

這里就通過(guò)getValue找到了對(duì)應(yīng)的路由進(jìn)行處理

gin的中間件

我們看到tree中路由對(duì)應(yīng)的是HandlersChain医咨,實(shí)際就是[]HandlerFunc,所以一個(gè)路由架诞,實(shí)際上會(huì)對(duì)應(yīng)多個(gè)handlers拟淮。

所以是可以一個(gè)路由擁有多個(gè)handler的。這里的handler是怎么來(lái)的呢侈贷?

每個(gè)路由的handler有幾個(gè)來(lái)源惩歉,第一個(gè)來(lái)源是在engine.GET的時(shí)候調(diào)用增加的。第二個(gè)來(lái)源是RouterGroup.GET的時(shí)候增加的(而設(shè)置的方式上面已經(jīng)說(shuō)過(guò)了)俏蛮,而且group的handler高于自定義的handler(告訴我們中間件的回調(diào)要先于用戶定義的路徑處理函數(shù)撑蚌。),這里自定義的handler可以是多個(gè)。

router.GET("/before", MiddleWare(), func(c *gin.Context) {
  request := c.MustGet("request").(string)
  c.JSON(http.StatusOK, gin.H{
    "middile_request": request,
  })
})

func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}

第三種方法是使用Use增加中間件的方式:
apiv1.Use(jwt.JWT()),apiv1 := r.Group("/api/v1")這里的apiv1是一個(gè)路由組,gin一般使用一個(gè)use來(lái)載入一個(gè)中間件

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

這里會(huì)把這個(gè)中間件(實(shí)際上也是一個(gè)handler)存放到routerRroup上搏屑。所以中間件是屬于groupHandlers的争涌。
上文說(shuō)過(guò)handleHTTPRequest將對(duì)應(yīng)路由的handler賦給了當(dāng)前請(qǐng)求的上下文,并且調(diào)用next()函數(shù)執(zhí)行辣恋。

func (c *Context) Next() {
    // 上下文處理之后c.index被執(zhí)為-1
    c.index++
    for c.index < int8(len(c.handlers)) {
        // 遍歷執(zhí)行所有handle(其實(shí)就是中間件+路由handle)
        c.handlers[c.index](c)
        c.index++
    }
}

之前也提到過(guò)亮垫,context是從對(duì)象池里取出來(lái)的,所以可能帶有臟數(shù)據(jù)伟骨,因此要重置

func (c *Context) reset() {
    c.Writer = &c.writermem
    c.Params = c.Params[0:0]
    c.handlers = nil
    c.index = -1
    c.fullPath = ""
    c.Keys = nil
    c.Errors = c.Errors[0:0]
    c.Accepted = nil
    c.queryCache = nil
    c.formCache = nil
}

這里的Next方法沒有使用局部變量去遍歷計(jì)數(shù)handlers的饮潦,它使用了和Context的成員變量index。這樣就可以保證某些情況下Next()函數(shù)不會(huì)觸發(fā)任何handler的調(diào)用携狭。

這里還有個(gè)細(xì)節(jié)继蜡,既然這里是調(diào)用c.Next()來(lái)交出當(dāng)前Contex的控制權(quán),并執(zhí)行接下來(lái)的hanlder所以c.Next就可以視作是后面所有的handler都執(zhí)行了逛腿。那么整個(gè)handler執(zhí)行和返回的過(guò)程就可以看作一個(gè)棧稀并,先執(zhí)行的最后返回。

好了說(shuō)到這里单默,碘举,整個(gè)gin的流程應(yīng)該已經(jīng)明了了t

接下來(lái)探索一下前文提過(guò)多次的Context

Context

type Context struct {
    writermem responseWriter
    Request   *http.Request
    // 傳遞接口,使用各個(gè)處理函數(shù)搁廓,更加靈活引颈,降低耦合
    Writer    ResponseWriter

    Params   Params   // 路徑當(dāng)中的參數(shù)
    handlers HandlersChain  // 處理函數(shù)數(shù)組
    index    int8        // 目前在運(yùn)行著第幾個(gè)處理函數(shù)
    fullPath string

    engine *Engine

      // Keys is a key/value pair exclusively for the context of each request.
        //意味著這里可以在中間件之間傳遞參數(shù)
    Keys map[string]interface{}
      ......
}

可以看到Context里集成RequestResponseWriter,就是說(shuō)整個(gè)業(yè)務(wù)邏輯處理的參數(shù)數(shù)據(jù)都封裝在這個(gè)Context里了
里面實(shí)現(xiàn)了幾乎所有的http里處理的功能函數(shù)(bind(獲取參數(shù))枚抵,render輸出參數(shù))

附上一份gin的源碼(自己學(xué)習(xí)的時(shí)候?qū)懙南哂斜容^詳細(xì)的注釋)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市汽摹,隨后出現(xiàn)的幾起案子李丰,更是在濱河造成了極大的恐慌,老刑警劉巖逼泣,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趴泌,死亡現(xiàn)場(chǎng)離奇詭異舟舒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)嗜憔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門秃励,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吉捶,你說(shuō)我怎么就攤上這事夺鲜。” “怎么了呐舔?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵币励,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我珊拼,道長(zhǎng)食呻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任澎现,我火速辦了婚禮仅胞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剑辫。我一直安慰自己干旧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布妹蔽。 她就那樣靜靜地躺著莱革,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讹开。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天捐名,我揣著相機(jī)與錄音旦万,去河邊找鬼。 笑死镶蹋,一個(gè)胖子當(dāng)著我的面吹牛成艘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贺归,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼淆两,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拂酣?” 一聲冷哼從身側(cè)響起秋冰,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎婶熬,沒想到半個(gè)月后剑勾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埃撵,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年虽另,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暂刘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捂刺,死狀恐怖谣拣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情族展,我是刑警寧澤森缠,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站苛谷,受9級(jí)特大地震影響辅鲸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腹殿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一独悴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锣尉,春花似錦刻炒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拇厢,卻和暖如春爱谁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孝偎。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工访敌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衣盾。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓寺旺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親势决。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阻塑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容