Gin 源碼閱讀(一)

初始化 Engine 對象

從官方提供的 demo 代碼來逐行解析 gin 源碼架構

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

首先是 gin.Default(),如下

func Default() *Engine {
  // debug 信息
  debugPrintWARNINGDefault()
  // 返回了一個 Engine 結構體對象
  engine := New()
  // engine 使用兩個全局中間件
  // 第一個是 logger,核心功能是通過 fmt.Fprint(out, formatter(param)) 來輸出日誌
  // 第二個是 recovery,核心功能是通過 defer func() { if err := recover(); err != nil { ... } } 來處理 panic 錯誤
  // 並將 panic 類型的錯誤轉化爲 500 服務器錯誤拋出 c.AbortWithStatus(http.StatusInternalServerError)
  // go 的 recover() 函數可以捕獲拋出的 panic 類型錯誤
	engine.Use(Logger(), Recovery())
	return engine
}

// New 函數返回一個默認配置的 Engine 結構體對象
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,
		RemoveExtraSlash:       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
}

// 註冊全局中間件
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	// 註冊中間件
	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.Default 其實是返回了一個 gin 自定義的 Engine 結構體實例,並添加了兩個默認中間件 loggerrecovery 分別用於記錄日誌和捕獲 panic 錯誤,隨後的操作都是對這個 Engine 實例的操作。

添加路由

接下來逐行解析路由掛載,首先是 r.GET("/ping", ...gin.HandlerFunc)

// Engine.GET 直接訪問到 RouterGroup.GET 是因爲
/**
type Engine struct {
	RouterGroup

	...
}
*/
// RouterGroup 通過結構體語法直接掛載在了 Engine 結構體下,所以可以直接通過 Engine 訪問 RouterGroup 的方法
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	// http.MethodGet 是一個字符串常量 "GET"
	return group.handle(http.MethodGet, relativePath, handlers)
}

// group.handle
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	// 拼接絕對路徑
	absolutePath := group.calculateAbsolutePath(relativePath)
	// 將所有的 HandlerFunc 組合在一起(其中包括中間件和主函數)
	handlers = group.combineHandlers(handlers)
	// 爲當前方法和路由添加處理函數集合(handlers)
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	// 最後返回 Engine 對象,可進行鏈式操作
	return group.returnObj()
}

// 合併 handler
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	// 最大數量爲 63
	if finalSize >= int(abortIndex) {
		panic("too many handlers")
	}
	mergedHandlers := make(HandlersChain, finalSize)
	// 合併全局中間件(group.Handlers)和當前路由中間件和處理函數(handlers)
	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)
	if root == nil {
		// 新建節點
		root = new(node)
		// 處理所有路徑的函數
		root.fullPath = "/"
		// 在樹中添加該節點
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	// 爲 root 節點添加子節點 node
	root.addRoute(path, handlers)
}

// node 結構
type node struct {
	path      string
	indices   string
	// 每種方法(如 GET)是一個父節點
	// 每個路徑(如 /ping)都是一個子節點,通過 addRoute 添加到 children 中
	children  []*node
	handlers  HandlersChain
	priority  uint32
	nType     nodeType
	maxParams uint8
	wildChild bool
	fullPath  string
}

gin 對於路由定義的處理分爲兩步:

  1. 將全局中間件和單路由中間件及處理函數進行合併,得到一個 gin.HandlerFunc 數組;
  2. 將這個路由信息生成一個 node 節點,掛載在 Enginetrees 節點樹中,不同的方法(如 GET、PUT…)成爲父樹,而不同的路徑(如 /foo、/bar)則是子樹,將 handlers 及其他信息掛載在這個 node 節點中,以便在未來使用。

啓動應用

r.Run(":9999") 來進行解析,然後再回到 func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong"})}

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.ListenAndServe 的處理函數需要實現 ServeHTTP 方法,所以我們需要看 Engine 的 ServeHTTP 方法
	err = http.ListenAndServe(address, engine)
	return
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// 對 *gin.Context 進行初始化
	// *gin.Context 是 gin 的核心,太龐大了,這裏不做展開
	c := engine.pool.Get().(*Context)
	// 注入 http.ResponseWriter 和 *http.Request 到 *gin.Context 中
	c.writermem.reset(w)
	c.Request = req
	// 每一次網絡請求都會調用 c.reset() 對 *gin.Context 進行重置
	c.reset()

	// 處理網絡請求,進行響應(也是在這裏做最終的路由匹配)
	engine.handleHTTPRequest(c)

	// 在對象池進行緩存,減少創建開銷
	engine.pool.Put(c)
}

// 核心處理函數
func (engine *Engine) handleHTTPRequest(c *Context) {
	// 從注入的依賴中取出請求方法及請求路徑
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	
	//...

	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		// 判斷請求方法是否能匹配到節點
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// 匹配路由,到這裏就比較簡單了,這裏不做展開
		// value 中包含了 handlers
		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() 就是遍歷 handlers,按順序依次執行
			// 也就完成了所有中間件及最終響應函數的執行,該函數在下面有展開
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		//...
	}
	// 無匹配項,404 錯誤處理
	serveError(c, http.StatusNotFound, default404Body)
}

func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

// c.Next() 最終執行了 func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong"})}
// c.JSON
func (c *Context) JSON(code int, obj interface{}) {
	// 調用 render
	c.Render(code, render.JSON{Data: obj})
}

func (c *Context) Render(code int, r render.Render) {
	c.Status(code)
	// ...

	// 最終調用 render.JSON.Render 方法中的 WriteJSON 方法響應結果
	if err := r.Render(c.Writer); err != nil {
		panic(err)
	}
}

func WriteJSON(w http.ResponseWriter, obj interface{}) error {
	// 寫入對應的 header 頭部
	writeContentType(w, jsonContentType)
	// 在 io.Writer 中寫入對應的 JSON 內容
	// 這裏的 io.Writer 對應的就是 http.ResponseWriter
	encoder := json.NewEncoder(w)
	err := encoder.Encode(&obj)
	return err
}

我們最後來梳理一遍,r.Run(":9999") 啓動了一個 http 服務,監聽了指定端口,然後將端口的所有請求交給 Engine 處理。

Engine 之所以有處理請求的能力,是因爲實現了 http.Handler 接口,包含 ServeHTTP 方法,所有的請求就會交由 EngineServeHTTP 處理。

EngineServeHTTP 方法包裝了一個 *gin.Context 對象,將這個對象傳入每個 gin.HandlerFunc 中,然後調用所有的 handlers,完成對中間件及最終響應函數的調用。

至此,gin 的主流程已經梳理完畢,接下來的文章是對 gin 的一些 API 的深入梳理,歡迎關注。

原文地址,歡迎收錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章