01Gin源碼解讀

簡介

Gin 源碼解讀, 基於 v1.5.0 版本.node

流程總覽

官方文檔上, 一個入門例子以下:git

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() // 監聽並在 0.0.0.0:8080 上啓動服務
}

看上去很是簡單, 首先進行初始化 gin.Default(), 接着定義了一個叫作 /ping 的路由, 最後直接啓動了 r.Run().github

初始化

首先, 深刻查看下 gin.Default() 的過程:web

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

配合註釋, 咱們就明白了 Default 的主要功能是初始化 Engine, 而後加載了兩個中間件, 用於日誌記錄和恢復.數組

Engine 其實是一個結構體, 也是 Gin 框架的核心, 看一下它的定義.服務器

// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()
type Engine struct {
    RouterGroup

    // Enables automatic redirection if the current route can't be matched but a
    // handler for the path with (without) the trailing slash exists.
    // For example if /foo/ is requested but a route only exists for /foo, the
    // client is redirected to /foo with http status code 301 for GET requests
    // and 307 for all other request methods.
    RedirectTrailingSlash bool

    // If enabled, the router tries to fix the current request path, if no
    // handle is registered for it.
    // First superfluous path elements like ../ or // are removed.
    // Afterwards the router does a case-insensitive lookup of the cleaned path.
    // If a handle can be found for this route, the router makes a redirection
    // to the corrected path with status code 301 for GET requests and 307 for
    // all other request methods.
    // For example /FOO and /..//Foo could be redirected to /foo.
    // RedirectTrailingSlash is independent of this option.
    RedirectFixedPath bool

    // If enabled, the router checks if another method is allowed for the
    // current route, if the current request can not be routed.
    // If this is the case, the request is answered with 'Method Not Allowed'
    // and HTTP status code 405.
    // If no other Method is allowed, the request is delegated to the NotFound
    // handler.
    HandleMethodNotAllowed bool
    ForwardedByClientIP    bool

    // #726 #755 If enabled, it will thrust some headers starting with
    // 'X-AppEngine...' for better integration with that PaaS.
    AppEngine bool

    // If enabled, the url.RawPath will be used to find parameters.
    UseRawPath bool

    // If true, the path value will be unescaped.
    // If UseRawPath is false (by default), the UnescapePathValues effectively is true,
    // as url.Path gonna be used, which is already unescaped.
    UnescapePathValues bool

    // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
    // method call.
    MaxMultipartMemory int64

    delims           render.Delims
    secureJsonPrefix string
    HTMLRender       render.HTMLRender
    FuncMap          template.FuncMap
    allNoRoute       HandlersChain
    allNoMethod      HandlersChain
    noRoute          HandlersChain
    noMethod         HandlersChain
    pool             sync.Pool
    trees            methodTrees
}

註釋裏寫到 Engine 的初始化有兩種方式, Default() 已經看過了, 看一下 New():app

// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true
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
}

再看一下, 添加中間件的過程.框架

// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

添加中間件, 其實是在 RouterGroup 上註冊, 那麼這 RouterGroup 又是什麼呢?less

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
    Handlers HandlersChain
    basePath string
    engine   *Engine
    root     bool
}

原來, RouterGroup 是用來配置路由的, 內部包含一個路由路徑 basePath 和中間件數組 Handlers.
因此, 添加中間件只是在 Handlers 中新加一個元素:ide

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

func (group *RouterGroup) returnObj() IRoutes {
    if group.root {
        return group.engine
    }
    return group
}

初始化的過程大致上就是如此.

註冊 handler

web 服務器最主要的固然是定義路由和處理函數了.

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

在前面, 咱們已經看過了 Engine 的定義, 注意看如下定義:

type Engine struct {
  RouterGroup

這顯示了 Engine 的內部使用了 RouterGroup, 因此其實上各類 HTTP 方法都是註冊在 RouterGroup 上的. 具體看一下 GET 方法:

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}

經過註釋和代碼, 咱們能夠知道, GET 只是一個快捷方式, 其實全部的 HTTP 方法註冊都是由 router.Handle 處理的.

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

handle 的核心語句是 group.engine.addRoute(httpMethod, absolutePath, handlers).

先看一下 combineHandlers, 能夠發現原來 handlers 是有限制的, 不能超過 63 個.
忽然以爲 Golang 中組合 slice 是有點蛋疼, 竟然要寫三行.

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
}

具體看一下 addRoute 有什麼操做.

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)
    root := engine.trees.get(method)
    if root == nil {
        root = new(node)
        root.fullPath = "/"
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
}

略過前面的判斷以後, 能夠看到核心是操做 engine.trees. 這用到了 httprouter.
root.addRoute(path, handlers) 的內容有點多, 就不展開了.

總之, 到這裏, 路由已經註冊好了.

運行

最後, 看一下 r.Run() 部分.

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
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
}

這是一個阻塞的方法, 除非發生錯誤. 內部使用 net/http 包的 ListenAndServe 函數.

接收請求

上面咱們已經看到運行是經過 http.ListenAndServe(address, engine) 實現的,
這是內置的 net/http 包的內容, 看一下具體的定義:

func ListenAndServe(addr string, handler Handler) error

第二個參數的類型是 Handler, 一猜就知道應該是接口類型, 看一下具體要實現什麼.

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

看一下 Engine 是如何實現 ServeHTTP 方法的.

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

主要看一下 engine.handleHTTPRequest(c), 這用於處理 HTTP 請求.

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 {
            c.handlers = value.handlers
            c.Params = value.params
            c.fullPath = value.fullPath
            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)
}

代碼有點長, 不過有兩句註釋能夠幫咱們快速理解邏輯, 主要根據 HTTP 方法和路徑從 engine.trees 找到
對應的 handlers, 當能找到時:

if value.handlers != nil {
  c.handlers = value.handlers
  c.Params = value.params
  c.fullPath = value.fullPath
  c.Next()
  c.writermem.WriteHeaderNow()
  return
}

重點關注下 c.Next():

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {
        c.handlers[c.index](c)
        c.index++
    }
}

由此, 就執行 Context 中的 handlers. 注意到, handlers 中包括了中間件和主處理函數,
所以就完成了路由的處理.

固然, 也有找不到對應的路由的時候, 這可能有多種緣由, 好比 HTTP 方法不存在, 或者是路徑不存在.

中間件原理

調用 Next() 的過程當中涉及到了中間件的原理, 下面具體講一講.

先看一下如何定義並添加中間件, 來自官方文檔:

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

        // Set example variable
        c.Set("example", "12345")

        // before request

        c.Next()

        // after request
        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()
    r.Use(Logger())

    r.GET("/test", func(c *gin.Context) {
        example := c.MustGet("example").(string)

        // it would print: "12345"
        log.Println(example)
    })

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

從上面的例子中能夠看出, 定義中間件和定義主處理函數沒什麼區別, 方法定義都是 gin.HandlerFunc:

type HandlerFunc func(*Context)

中間件中也調用了 c.Next(), 劃分了請求的過程, c.Next() 運行前是請求前(before request), 運行後是請求後(after request).

中間件流程.png

從上面的流程圖中假設註冊兩個處理函數, 第一個是 log 中間件, 用於日誌記錄, 另外一個是該路徑的主處理函數.
當接收到請求時, 進入不少個 Next 中, 這是由於中間件可能也會調用 c.Next(). 能夠將 c.Next()
理解爲控制流轉移, 每當運行 c.Next(), 其實是運行下一個 handler. 有點相似遞歸時的調用棧.

總結

Gin 的基本流程就是這樣的, 粗看代碼, 以爲仍是挺清晰的. 更多內容還有待挖掘.

相關文章
相關標籤/搜索