Gin
是使用Go語言編寫的高性能的web
服務框架,根據官方的測試,性能是httprouter
的40倍左右。要使用好這套框架呢,首先我們就得對這個框架的基本結構有所瞭解,所以我將從以下幾個方面來對Gin
的源碼進行解讀。
- 第一章:
Gin
是如何儲存和映射URL
路徑到相應的處理函數的 - 第二章:
Gin
中間件的設計思想及其實現 - 第三章:
Gin
是如何解析客戶端發送請求中的參數的 - 第四章:
Gin
是如何將各類格式(JSON/XML/YAML
等)數據解析
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
函數不過就是將我們添加的中間件函數存儲到了RouterGroup
的Handlers
中,然後在添加搜索樹節點時,會與我們的路由處理函數合併,這個我們在上一章中也提到過,就像下面這樣。
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()
}
在樹的節點中就像下圖這樣:
中間件實現方式
基本上每個路由的最終匹配節點都會同時含有中間件處理函數和路由處理函數,就像上圖其中的是我們添加的中間件函數,而則是我們爲該路由添加的處理函數。所以每當請求被路由到某個節點時,都會遍歷執行其下的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)
個,超出後就會拋出錯誤。特別要注意的是這裏限制的個數是路由分組中所有中間件
的個數+我們自己編寫的處理函數
的總個數,所以在對業務進行模塊化時可能會抽離出很多中間件,這個時候一定要注意切不可分割得太細,否則可能會出現上述問題。