今天是我golang框架閱讀系列第三篇文章,今天咱們主要看看gin的框架執行流程。關於golang框架生命週期源碼閱讀下面是個人計劃:node
計劃 | 狀態 |
---|---|
Go框架解析-beego | done |
Go框架解析-iris | done |
Go框架解析-gin | done |
Go框架解析-echo | doing |
Go框架解析-revel | doing |
Go框架解析-Martini | doing |
再完成各個golang框架生命週期的解析以後,我會計劃對這幾個框架的優略進行一個系列分析,因爲業內大多都是性能分析的比較多,我可能會更側重於如下維度:git
第一波咱們主要把重點放在框架設計上面。github
上次閱讀iris咱們使用的glide安裝的,今天咱們安裝gin嘗試下使用gomod,具體步驟以下。golang
使用go mod安裝:算法
// 初始化go.mod文件 go mod init gin-code-read // 安裝gin go get github.com/gin-gonic/gin // 複製依賴到vendor目錄 go mod vendor
啓動一個簡單的gin http服務:網絡
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() }
看上面的啓動代碼是否是很熟悉,和iris很像是吧,一樣的Default
方法。閉包
看完gin框架流程我有大體以下幾個感觸:app
總之,目前就一個感覺:框架
Gin是我認爲的一個GO框架應該有的樣子
下圖就是我對整個Gin框架生命週期的輸出,因爲圖片過大存在平臺壓縮的可能,建議你們直接查看原圖連接。tcp
訪問圖片源地址查看大圖 http://cdn.tigerb.cn/20190704...
原圖查看連接: http://cdn.tigerb.cn/20190704...
// 獲取一個gin框架實例 gin.Default() ⬇️ // 具體的Default方法 func Default() *Engine { // 調試模式日誌輸出 // 🌟很不錯的設計 debugPrintWARNINGDefault() // 建立一個gin框架實例 engine := New() // 是否是很眼熟 和iris裏註冊中間件的方式一致 // 不過比iris好的是支持多參數 iris則是得調用屢次 engine.Use(Logger(), Recovery()) return engine } ⬇️ // 建立一個gin框架實例 具體方法 func New() *Engine { // 調試模式日誌輸出 debugPrintWARNINGNew() // 先插入一個小話題,可能好多人都在想爲何叫gin呢? // 哈哈,這個框架實例的結構體實際命名的Engine, 很明顯gin就是一個很個性的簡稱了,是否是真相大白了。 // 初始化一個Engine實例 engine := &Engine{ // 路由組 // 給框架實例綁定上一個路由組 RouterGroup: RouterGroup{ // engine.Use 註冊的中間方法到這裏 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);", } // RouterGroup綁定engine自身的實例 // 不太明白爲什麼如此設計 // 職責分明麼? engine.RouterGroup.engine = engine // 綁定從實例池獲取上下文的閉包方法 engine.pool.New = func() interface{} { // 獲取一個Context實例 return engine.allocateContext() } // 返回框架實例 return engine } ⬇️ // 註冊日誌&goroutin panic捕獲中間件 engine.Use(Logger(), Recovery()) ⬇️ // 具體的註冊中間件的方法 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine } // 上面 是一個engine框架實例初始化的關鍵代碼 // 咱們基本看完了 // --------------router-------------- // 接下來 開始看路由註冊部分 // 註冊GET請求路由 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 { absolutePath := group.calculateAbsolutePath(relativePath) // 把中間件的handle和該路由的handle合併 handlers = group.combineHandlers(handlers) // 註冊一個GET集合的路由 group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() } ⬇️ 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) // 檢查有沒有對應method集合的路由 root := engine.trees.get(method) if root == nil { // 沒有 建立一個新的路由節點 root = new(node) // 添加該method的路由tree到當前的路由到路由樹裏 engine.trees = append(engine.trees, methodTree{method: method, root: root}) } // 添加路由 root.addRoute(path, handlers) } ⬇️ // 很關鍵 // 路由樹節點 type node struct { // 路由path path string indices string // 子路由節點 children []*node // 全部的handle 構成一個鏈 handlers HandlersChain priority uint32 nType nodeType maxParams uint8 wildChild bool } // 上面 // 咱們基本看完了 // --------------http server-------------- // 接下來 開始看gin如何啓動的http server func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) // 執行http包的ListenAndServe方法 啓動路由 // engine實現了http.Handler接口 因此在這裏做爲參數傳參進去 // 後面咱們再看engine.ServeHTTP的具體邏輯 err = http.ListenAndServe(address, engine) return } ⬇️ // engine自身就實現了Handler接口 type Handler interface { ServeHTTP(ResponseWriter, *Request) } ⬇️ // 下面就是網絡相關了 // 監聽IP+端口 ln, err := net.Listen("tcp", addr) ⬇️ // 上面執行完了監聽 // 接着就是Serve srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) ⬇️ // Accept請求 rw, e := l.Accept() ⬇️ // 使用goroutine去處理一個請求 // 最終就執行的是engine的ServeHTTP方法 go c.serve(ctx) // 上面服務已經啓動起來了 // --------------handle request-------------- // 接着咱們來看看engine的ServeHTTP方法的具體內容 // engine實現http.Handler接口ServeHTTP的具體方法 func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // 獲取一個上下文實例 // 從實例池獲取 性能高 c := engine.pool.Get().(*Context) // 重置獲取到的上下文實例的http.ResponseWriter c.writermem.reset(w) // 重置獲取到的上下文實例*http.Request c.Request = req // 重置獲取到的上下文實例的其餘屬性 c.reset() // 實際處理請求的地方 // 傳遞當前的上下文 engine.handleHTTPRequest(c) //歸還上下文實例 engine.pool.Put(c) } ⬇️ // 具體執行路由的方法 engine.handleHTTPRequest(c) ⬇️ t := engine.trees for i, tl := 0, len(t); i < tl; i++ { // 這裏尋找當前請求method的路由樹節點 // 我在想這裏爲啥不用map呢? // 雖然說也遍歷不了幾回 if t[i].method != httpMethod { continue } // 找到節點 root := t[i].root // 很關鍵的地方 // 尋找當前請求的路由 handlers, params, tsr := root.getValue(path, c.Params, unescape) if handlers != nil { // 把找到的handles賦值給上下文 c.handlers = handlers // 把找到的入參賦值給上下文 c.Params = params // 執行handle c.Next() // 處理響應內容 c.writermem.WriteHeaderNow() return } ... } // 方法樹結構體 type methodTree struct { // HTTP Method method string // 當前HTTP Method的路由節點 root *node } // 方法樹集合 type methodTrees []methodTree ⬇️ // 執行handle func (c *Context) Next() { // 上下文處理以後c.index被執爲-1 c.index++ for s := int8(len(c.handlers)); c.index < s; c.index++ { // 遍歷執行全部handle(其實就是中間件+路由handle) // 首先感受這裏的設計又是似曾相識 iris不是也是這樣麼 不懂了 哈哈 // 其次感受這裏設計的很通常 遍歷?多無聊,這裏多麼適合「責任鏈模式」 // 以後給你們帶來關於這個handle執行的「責任鏈模式」的設計 c.handlers[c.index](c) } } // Context的重置方法 func (c *Context) reset() { c.Writer = &c.writermem c.Params = c.Params[0:0] c.handlers = nil // 很關鍵 注意這裏是-1哦 c.index = -1 c.Keys = nil c.Errors = c.Errors[0:0] c.Accepted = nil }
最後咱們再簡單的回顧下上面的流程,從上圖看來,是否是相對於iris簡單了好多。
《Golang框架解析》系列文章連接以下: