Golang之Gin框架源碼解讀——第二章

Gin是使用Go語言編寫的高性能的web服務框架,根據官方的測試,性能是httprouter的40倍左右。要使用好這套框架呢,首先我們就得對這個框架的基本結構有所瞭解,所以我將從以下幾個方面來對Gin的源碼進行解讀。

  • 第一章:Gin是如何儲存和映射URL路徑到相應的處理函數的
  • 第二章:Gin中間件的設計思想及其實現
  • 第三章:Gin是如何解析客戶端發送請求中的參數的
  • 第四章:Gin是如何將各類格式(JSON/XML/YAML等)數據解析

Gin Github官方地址

Gin中間件的設計思想及其實現

在第一章中我們談到Gin的中間件是基於RouterGroup數據結構實現的,這裏我們再來回顧一下這個數據結構:

type HandlerFunc func(*Context)

type HandlersChain []HandlerFunc

type RouterGroup struct {
    //中間件處理鏈
    Handlers HandlersChain
    //當前的路由基地址
    basePath string
    //Gin框架的核心引擎
    engine   *Engine
    //當前
	root     bool
}

下面,我們以官方的示例代碼來逐步研究其運行機制

func main() {
	//創建一個不包含任何中間件的Engine
	r := gin.New()
    //添加日誌中間件
	r.Use(gin.Logger())
    //添加錯誤回覆重定向中間件
    r.Use(gin.Recovery())
    //以上這三部可以使用r := gin.Default()一步實現
    //對/benchmark路由添加兩個處理函數
    r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
    //創建路由分組
    authorized := r.Group("/")
    //使用AuthRequired中間件
    authorized.Use(AuthRequired())
    //這裏的括號只是爲了看起來整齊
	{
        //這裏就是簡單的路由信息配置
		authorized.POST("/login", loginEndpoint)
		authorized.POST("/submit", submitEndpoint)
		authorized.POST("/read", readEndpoint)

        testing := authorized.Group("testing")
        //對/testing/analytics添加路由信息
		testing.GET("/analytics", analyticsEndpoint)
	}

	r.Run(":8080")
}

func AuthRequired() gin.HandlerFunc {
	return gin.BasicAuth(gin.Accounts{
		"foo":    "bar",
		"austin": "1234",
		"lena":   "hello2",
		"manu":   "4321",
	});
}

Endpoint結尾的均是HandlerFunc類型的處理函數
首先,我們來看添加中間件的函數

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

從這裏我們可以看出Use函數不過就是將我們添加的中間件函數存儲到了RouterGroupHandlers中,然後在添加搜索樹節點時,會與我們的路由處理函數合併,這個我們在上一章中也提到過,就像下面這樣。

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

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

在樹的節點中就像下圖這樣:
在這裏插入圖片描述

中間件實現方式

基本上每個路由的最終匹配節點都會同時含有中間件處理函數和路由處理函數,就像上圖其中的(0)(1)(0)、(1)是我們添加的中間件函數,而(2)(2)則是我們爲該路由添加的處理函數。所以每當請求被路由到某個節點時,都會遍歷執行其下的handler處理函數鏈。

設計思想

由此可見,Gin框架的中間件採用的是責任鏈模式,而不是常見的代理模式(Spring爲代表)來實現的。

路由分組

除了上面的普通Use函數來添加中間件外,Gin還支持爲某一指定路徑添加一組路由中間件,使用如下函數實現:

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
        //合併已存在的處理函數
        Handlers: group.combineHandlers(handlers),
        //計算與當前路由分組的相對路徑,默認有個根路由分組,基地址爲"/"
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

通過創建一個路由分組,即可實現爲特定路由前綴的節點應用中間件函數,而不是對整個根樹所有終節點應用。例如:

//創建一個以"/"開頭的路由分組
authorized := r.Group("/")
//對所有該分組下的路由應用AuthRequired中間件
authorized.Use(AuthRequired())
{
    //爲"/login"路由添加處理函數"loginEndpoint"
    // 同時也會被應用AuthRequired中間件
    authorized.POST("/login", loginEndpoint)
    authorized.POST("/submit", submitEndpoint)
    authorized.POST("/read", readEndpoint)

    //以authorized爲基礎,創建一個子分組,
    // 即匹配"/testing"開頭的路由
    testing := authorized.Group("testing")
    //爲"/testing/analytics"路由添加處理函數analyticsEndpoint
    //同時也會被應用AuthRequired中間件
    testing.GET("/analytics", analyticsEndpoint)
}

func AuthRequired() gin.HandlerFunc {
	return gin.BasicAuth(gin.Accounts{
		"foo":    "bar",
		"austin": "1234",
		"lena":   "hello2",
		"manu":   "4321",
	});
}

注意事項

在研讀Gin源碼時,我發現如下代碼:

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
}

從上面的代碼,可以看出每個節點的處理函數是有上限的,最多爲abortIndex(63)個,超出後就會拋出錯誤。特別要注意的是這裏限制的個數是路由分組中所有中間件的個數+我們自己編寫的處理函數的總個數,所以在對業務進行模塊化時可能會抽離出很多中間件,這個時候一定要注意切不可分割得太細,否則可能會出現上述問題。

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