Go框架解析-gin

前言

今天是我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服務:bash

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的生命週期

看完gin框架流程我有大體以下幾個感觸:閉包

  • gin是我目前看過的這三個go框架裏最簡潔的框架
  • gin和iris在框架設計存在風格一致的地方,例如註冊中間件、handle的執行

總之,目前就一個感覺:app

Gin是我認爲的一個GO框架應該有的樣子框架

下圖就是我對整個Gin框架生命週期的輸出,因爲圖片過大存在平臺壓縮的可能,建議你們直接查看原圖連接。

訪問圖片源地址查看大圖 user-gold-cdn.xitu.io/2019/7/7/16…

原圖查看連接: user-gold-cdn.xitu.io/2019/7/7/16…


關鍵代碼解析

// 獲取一個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框架解析》系列文章連接以下:

相關文章
相關標籤/搜索