Go Gin源碼學習(一) 主流程

Gin的基本使用

Gin是一個比較輕量級的http框架,主要是提供了幾個便於使用的功能:node

  • 簡單的中間件註冊,能夠很方便的實現通用中間件的使用註冊
  • 提供了比較方便和全面的路由註冊,方便的實現RESTful接口的實現
  • 提供了便捷的獲取參數的方法,包括get、post兵能夠能夠把數據直接轉換成對象
  • 對路由的分組,Gin能夠對一組路由作統一的中間件註冊等操做
  • 能夠手機全部錯誤,統一在統一的地方寫日誌

性能方面:git

  • 是路由的基礎數據格式爲基數樹沒有使用反射,因此性能方面也是比較低消耗內存低
  • 上下文context使用了對象池,fasthttp中也一樣使用了sync.pool

使用方便也是比較簡單的,下面有一個很簡單的例子github

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "test/gin/middleware/model"
)

func main() {
    //建立router
    router := gin.Default()
    //建立組
    group := router.Group("/api")
    //爲組加中間件
    group.Use(func(context *gin.Context) {
        fmt.Println("api group url:", context.Request.URL.String())
    })
    //爲組加路由方法
    group.GET("/test", func(context *gin.Context) {
        context.JSON(200, model.Message{Message:"ok"})
    })
    //運行
    router.Run(":3333")
}

例子中是一個最簡單的Gin框架的應用。建立了一個engine,建立了組而且爲組添加了中間件以後在這個group下的路由方法都將使用這個中間件,方便對api最系統的管理對不一樣的api作不一樣的處理。
在Terminal中訪問能夠看到下面的結果json

curl http://localhost:3333/api/test
{"Message":"ok"}panleiMacBook-Pro:test

主要流程的源碼

首先咱們先看例子中的gin.Default() 返回的engine對象,這是Gin的主要對象。只對最主要的屬性加了註釋api

type Engine struct {
    //路由組
    RouterGroup
    RedirectTrailingSlash bool
    RedirectFixedPath bool
    HandleMethodNotAllowed bool
    ForwardedByClientIP    bool
    AppEngine bool
    UseRawPath bool
    UnescapePathValues bool
    MaxMultipartMemory int64
    delims           render.Delims
    secureJsonPrefix string
    HTMLRender       render.HTMLRender
    FuncMap          template.FuncMap
    allNoRoute       HandlersChain
    allNoMethod      HandlersChain
    noRoute          HandlersChain
    noMethod         HandlersChain
    // 對象池 用來建立上下文context
    pool             sync.Pool
    //記錄路由方法的 好比GET POST 都會是數組中的一個 每一個方法對應一個基數樹的一個root的node
    trees            methodTrees
}

而後咱們來看Default方法,其實很簡單就是建立一個engine對象而且添加默認的兩個中間件,一個是作log顯示,顯示每次請求能夠再console中看到。每次建立engine對象的時候回默認的添加一個routergroup地址爲默認的"/" 代碼以下:數組

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}
//new方法中默認的 添加了routergroup路由 「/」
func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJsonPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

在看中間件以前還有一個重要的對象context 上下文對象
這個對象中存放了 engine指針、請求的request對象、返回的responsewriter對象還有一些參數等對象,這個context將在請求一開始就被建立一直貫穿整個執行過程,包括中間件,路由等。最後的返回值能夠再responseWriter寫,最終就會返回給客戶端。app

type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain
    index    int8

    engine *Engine

    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}

    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs

    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string
}

接下來看得是添加中間件use方法框架

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    //調用routegroup的use方法
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    //爲group的handlers添加中間件 
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

添加爲中間件以後就是添加正常路由,Gin中提供了GET、POST、DELETE更各類方法,咱們就只看get方法其他的都是相同的處理方式。總結下來就是把group和傳入的handler合併,而且計算出路徑存入到tree中等客戶端的調用。curl

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    //調用get方法
    return group.handle("GET", relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    //計算路徑地址,好比group地址是 router.Group("/api")
    //結果爲/api/test/ 就是最終計算出來的結果 使用path.join 方法拼接 其中加了一些判斷
    absolutePath := group.calculateAbsolutePath(relativePath)
    //把group中的handler和傳入的handler合併 
    handlers = group.combineHandlers(handlers)
    //把方法 路徑 和處理方法做爲node 加入到基數樹種,基數樹在下次單獨學習分析
    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)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}

最後咱們須要看run方法,以前的都是準備工做或者說是設置路由中間件。等run方法執行的時候則是服務真正啓動起來。 代碼很簡單,幾乎不須要註釋是調用gohttp包的ListenAndServe方法把engine傳入而後http包中會有一個for邏輯不停的監聽這個端口號的全部請求。post

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

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

那麼客戶端最終請求以後會走到那些代碼,又是怎麼找到路由而且調用一個個中間件的呢?其實engine是繼承了Handler 這個接口(能夠看下面代碼),咱們知道若是不適用Gin框架直接使用http包咱們全部的路由就是直接繼承這個接口因此對這個接口咱們是很熟悉的。
下面的serveHTTP 就是Gin的方法 最主要流程就是從tree中獲取到路由,而後依次執行handler最終處理完成返回給客戶端。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    //從對象池中獲取context對象,這就是最初咱們看到的一種優化性能的一種方式
    c := engine.pool.Get().(*Context)
    //重置writer中的一些值
    c.writermem.reset(w)
    把request放到context中
    c.Request = req
    c.reset()
    //調用處理方法
    engine.handleHTTPRequest(c)
    //處理完成把context對象放回到對象池
    engine.pool.Put(c)
}

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

    //根據基數樹的特性尋找方法發 並調用next方法 依次執行handler
    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
        handlers, params, tsr := root.getValue(path, c.Params, unescape)
        if handlers != nil {
            c.handlers = handlers
            c.Params = params
            c.Next()
            c.writermem.WriteHeaderNow()
            return
        }
        if httpMethod != "CONNECT" && path != "/" {
            if 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 handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
                c.handlers = engine.allNoMethod
                serveError(c, http.StatusMethodNotAllowed, default405Body)
                return
            }
        }
    }
    c.handlers = engine.allNoRoute
    serveError(c, http.StatusNotFound, default404Body)
}

總結

從上面的流程咱們能夠看到,其實Gin框架就是對go http包的一次封裝,加入了group和tree。讓咱們能夠更簡單方便的使用http包。其實Gin還有不少其餘功能的源碼包括參數分析 json解析等等,此次學習的只是Gin的主要流程。Gin中tree是一個亮點,是的它在查找路由性能方面有很大的優點,下一篇文章會主要學習tree(基數樹)。

相關文章
相關標籤/搜索