gin和http包的使用

gin,beego等底層都用的是net/http模塊,上篇文章中對(duì)一個(gè)基本的http請(qǐng)求作了分析,這篇文章就gin怎么用的http模塊的流程進(jìn)行梳理。
gin的github地址
來(lái)看一個(gè)基本的gin項(xiàng)目代碼:

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 (for windows "localhost:8080")
}

可以看到一個(gè)基本的啟動(dòng)過(guò)程:
(1)首先生成一個(gè)gin.Default
(2)設(shè)置路由r.GET...
(3)啟動(dòng)listen and serve
例行先提出問(wèn)題:
(q1)gin是怎么使用到net/http的模塊的?
(q2)gin的路由處理流程?
通過(guò)每個(gè)流程的分析來(lái)解答:

一、gin.Default

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
    debugPrintWARNINGDefault()        // go版本的校驗(yàn)(>1.8.x),debugPrint,debug模式的時(shí)候會(huì)有相關(guān)打印
    engine := New()              // 生成一個(gè)Engine結(jié)構(gòu)體
    engine.Use(Logger(), Recovery())        // 用了Logger和Recovery兩個(gè)middleware,同時(shí)也可以自己定義middleware
    return engine        // 返回結(jié)構(gòu)體
}
1.1 debugPrintWARNINGDefault:

關(guān)于版本的校驗(yàn)等信息,debug模式下的啟動(dòng)日志打印

func debugPrintWARNINGDefault() {
    if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {                          //  版本的校驗(yàn),不能小于1.8
        debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.
`)}
    debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`)
}
func getMinVer(v string) (uint64, error) {
    first := strings.IndexByte(v, '.')
    last := strings.LastIndexByte(v, '.')
    fmt.Println(first)
    fmt.Println(last)
    if first == last {
        return strconv.ParseUint(v[first+1:], 10, 64)
    }
    return strconv.ParseUint(v[first+1:last], 10, 64)
}
1.2 engine := New()

New函數(shù)用于生成一個(gè)新的Engine結(jié)構(gòu)體,可以看到各種參數(shù)配置
首先我們來(lái)關(guān)注Engine結(jié)構(gòu)體的幾個(gè)參數(shù):
(1)RouterGroup:RouterGroup 描述的一個(gè)路由組,其中包含了這個(gè)路由組的相關(guān)公共屬性,比如后面可以通過(guò)(group *RouterGroup) Group方法修改basePath字段,用于加一些公共的路由前綴(eg:"api/v1"),。通過(guò)(group *RouterGroup) Use(middleware ...HandlerFunc)方法改變Handlers字段,用于添加公共的中間件。
貼一段項(xiàng)目代碼:

// InsecureRouterGroup returns router group without auth
func InsecureRouterGroup() *gin.RouterGroup {
    lock.Lock()
    r := gEngine.Group(constants.APIVERSION)
    r.Use(RequestID)
    lock.Unlock()
    return r
}

(2)各種bool參數(shù),用于對(duì)重定向(redirect),轉(zhuǎn)發(fā)(ForwardedByClientIP)等一些屬性的控制
(3)trees:類型為methodTrees,是每個(gè)不同方法的路由集合,原理參考go的http/router框架。大致就是一個(gè)路由查找樹,用到了radix tree(前綴樹)或者說(shuō)是壓縮檢索樹的數(shù)據(jù)結(jié)構(gòu),來(lái)進(jìn)行路由匹配,可以大大的提高路由查找的效率。

type methodTrees []methodTree
type methodTree struct {
    method string
    root   *node
}
type node struct {
    path      string              // 當(dāng)前節(jié)點(diǎn)的對(duì)應(yīng)的子查找路徑
    indices   string              // 子節(jié)點(diǎn)的首字母的組成的字符串
    children  []*node            // 子節(jié)點(diǎn)
    handlers  HandlersChain     // 這個(gè)node對(duì)應(yīng)要處理的Handlers
    priority  uint32
    nType     nodeType      // 當(dāng)前節(jié)點(diǎn)類型(eg: root(根節(jié)點(diǎn))/default(普通的)/param(參數(shù)類型)/catchAll(通配符))
    maxParams uint8
    wildChild bool        // 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)是否是參數(shù)節(jié)點(diǎn)
}

(4)pool:sync.Pool類型,New方法定義為return engine.allocateContext(),返回的結(jié)構(gòu)體是gin.Context結(jié)構(gòu)體,便于對(duì)結(jié)構(gòu)體的復(fù)用,減少gc。
拋出問(wèn)題
(qa6):gin.Context結(jié)構(gòu)體在哪里被用到的?具體的代碼處理邏輯?

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,        // 這個(gè)路由組的公共hanlers,后面會(huì)給其加上middleware
            basePath: "/",      // 路由地址為"/"
            root:     true,         // 表明這個(gè)為根路由
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),              // 給methodTrees定義一個(gè)methodTree的數(shù)組
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJsonPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

拋出問(wèn)題:
(q3)路由查找的時(shí)候RouterGroup和trees都是在哪里被用到了?

1.3 engine.Use(Logger(), Recovery())

這里用了gin的中間件Logger和Recovery

// 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...)        // use中間件,把加入的middleware加入到engine.RouterGroup.Handlers中,在后面加入具體的業(yè)務(wù)代碼的時(shí)候會(huì)combine這個(gè)Handlers和具體的業(yè)務(wù)handler,解釋了(qa3)的問(wèn)題。
    engine.rebuild404Handlers()          // 用于重寫noroute的handler,具體的方法實(shí)現(xiàn)在func (engine *Engine) NoRoute(handlers ...HandlerFunc)中,用于用戶自己定義
    engine.rebuild405Handlers()      // 同上,用于重寫nomethod的handler
    return engine
}
// 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()
}

至此對(duì)于gin.Default的代碼分析完畢

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

先貼源碼:

// 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)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath) // 合并相對(duì)路徑和routerGroup的basePath
    handlers = group.combineHandlers(handlers) // 把業(yè)務(wù)的handler和共用的handler(middleware)結(jié)合到一起,返回一個(gè)新的HandlersChain
    group.engine.addRoute(httpMethod, absolutePath, handlers) // 把path加入到相應(yīng)的method的檢索樹中去,關(guān)聯(lián)相應(yīng)的handlers
    return group.returnObj()
}
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
    return joinPaths(group.basePath, relativePath)
}
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
}
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)      // 查看engine.trees是否已經(jīng)有了此method,沒有的話創(chuàng)建出一個(gè)新的method的node
    if root == nil {
        root = new(node)
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)        // 把handlers加入到method的node中
}

總結(jié):r.GET方法工作(1)合并拿到絕對(duì)路徑 (2)將業(yè)務(wù)handler和middle handler合并為mergeHandler(3)將路徑,mergeHandler,method(httprouter的節(jié)點(diǎn)相關(guān)元素)加入到methodTrees中。
總的來(lái)說(shuō)處理過(guò)程符合httprouter那一套邏輯。

三、r.Run()

現(xiàn)在我們已經(jīng)有了一個(gè)類型為Engine的handler(實(shí)現(xiàn)了ServeHTTP的方法),以及一個(gè)Engine里的路由檢索樹已經(jīng)建立好了。
拋出問(wèn)題
(qa4):對(duì)于進(jìn)來(lái)的請(qǐng)求怎么進(jìn)行監(jiān)聽的?
(qa5):對(duì)于路由的匹配是在哪里進(jìn)行的?

// 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
}

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

可以看到這里的Run即為將engine這個(gè)handler放入到http.ListenAndServe中去,然后啟用監(jiān)聽。。。。,監(jiān)聽流程和net/http的處理流程一致。(解答了qa4)

對(duì)于qa5:
首選回顧一下,net/http中的路由查找是怎么執(zhí)行的嗎?看下面的代碼,其實(shí)發(fā)現(xiàn)net/http用的是ServeMux這個(gè)handler,也是實(shí)現(xiàn)了ServeHTTP這個(gè)方法,具體的路由匹配在mux.Handler(r) 中

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)                  //  handler的路由處理
    h.ServeHTTP(w, r)                       //  ServeHTTP的實(shí)現(xiàn),對(duì)應(yīng)到用戶的業(yè)務(wù)邏輯處理
}

而engine即類似于ServeMux,也實(shí)現(xiàn)了一個(gè)ServeHTTP方法,也是在方法里進(jìn)行了路由的匹配以及具體邏輯的處理,詳細(xì)見下面代碼。

// ServeHTTP conforms to the http.Handler interface。
// 這個(gè)方法的實(shí)現(xiàn)是在一個(gè)goroutine中實(shí)現(xiàn)的,就是說(shuō)每對(duì)一個(gè)新的HTTP請(qǐng)求,都會(huì)生成一個(gè)新的goroutine去處理(并發(fā)處理詳見net/http的處理過(guò)程)。
//對(duì)于這個(gè)HTTP請(qǐng)求,會(huì)先觸發(fā)ServeHTTP方法得到一個(gè)Context請(qǐng)求上下文,然后調(diào)用engine.handleHTTPRequest(C),處理這個(gè)請(qǐng)求,同時(shí)將處理的內(nèi)容寫入到http.ResponseWriter中。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)      // 從池子里拿出來(lái),要是沒有了的話自己new一個(gè)出來(lái)。有并發(fā)請(qǐng)求進(jìn)來(lái)的時(shí)候,會(huì)產(chǎn)生多個(gè)context
    c.writermem.reset(w)      // 對(duì)context的ResponseWriter賦為w
    c.Request = req            // 對(duì)context的Request賦為req
    c.reset()        // 對(duì)拿到的context進(jìn)行reset,類似于清空,然后才進(jìn)行使用
    engine.handleHTTPRequest(c)      // 這里就是最后的邏輯處理,包括(1)通過(guò)context .Request.path找到對(duì)應(yīng)的handler   (2)middleware的執(zhí)行 (3)業(yè)務(wù)代碼的執(zhí)行 (4)寫入結(jié)果到context. ResponseWriter中去
    engine.pool.Put(c)        // 用完了之后放回到engine.pool中去
}

注釋解釋了qa6。
對(duì)于engine.handleHTTPRequest(c)中的處理邏輯,涉及到了middleware的處理,下面拉出來(lái)分析一下:

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 {         // 遍歷methodTrees,找到當(dāng)前方法的methodTree   
            continue
        }
        root := t[i].root                // 找到methodTree的root
        // Find route in tree
        handlers, params, tsr := root.getValue(rPath, c.Params, unescape)        // 拿到當(dāng)前tree下的這個(gè)路徑下的handler和參數(shù)等
        if handlers != nil {
            c.handlers = handlers
            c.Params = params
            c.Next()              // 這里是真正的進(jìn)行handler的執(zhí)行,包括middleware和業(yè)務(wù)handle
            c.writermem.WriteHeaderNow()        // 寫入header
            return  
        }
        if httpMethod != "CONNECT" && rPath != "/" {          // 跨域等操作
            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(rPath, nil, unescape); handlers != nil {
                c.handlers = engine.allNoMethod
                serveError(c, http.StatusMethodNotAllowed, default405Body)
                return
            }
        }
    }
    c.handlers = engine.allNoRoute
    serveError(c, http.StatusNotFound, default404Body)
}

// 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)                        // 會(huì)進(jìn)行遞歸執(zhí)行middleware,直到執(zhí)行到業(yè)務(wù)代碼。一般的middleware會(huì)有個(gè)c.Next方法。像cors等沒有寫c.Next的,其實(shí)在for c.index < int8(len(c.handlers))遍歷中也會(huì)被執(zhí)行到。
        c.index++
    }
}

可以看到在handleHTTPRequest執(zhí)行過(guò)程中,都是通過(guò)context結(jié)構(gòu)體進(jìn)行上下文傳遞的,傳遞的內(nèi)容包括Request,Writer,handlers,Params等等。

總結(jié):

至此可以看到gin作為一個(gè)web框架,其實(shí)就是重寫了原生的ServeMux的實(shí)現(xiàn)。Engine就是一個(gè)類似于ServeMux的handler。
基本的web實(shí)現(xiàn)的時(shí)候,重新實(shí)現(xiàn)的邏輯主要有:
(1)對(duì)于路由的查找,用了http/router的路由框架,并在ServeHTTP方法中進(jìn)行了路由的查找。
(2)用了中間件的處理邏輯,并在路由添加的時(shí)候進(jìn)行了路由的合并等操作,并且在handleHTTPRequest的時(shí)候也會(huì)對(duì)每個(gè)中間件進(jìn)行處理。
后面有新的發(fā)現(xiàn)進(jìn)行補(bǔ)充。。。

最后編輯于
?著作權(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ù)。

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