關鍵結構
Context.png
-
Context
貫穿整個HTTP業務當中,為相關業務提供上下文服務-
WriteResponse
是Gin自己定義HTTPResponseWriter
接口,除了包含http.ResponseWriter
外,額外增加了一些常用的接口。 -
handles
包含了使用者自定義的業務和一些中間件(Gin
默認會使用日志中間件和崩潰恢復中間件) -
Cache
包含著QueryCaChe
和FormCache
,一個是讀url的param參數,一個是讀form表單參數 -
Param
是使用者定義的url路徑參數,例如 /user/:id -
*Engine
綁定Engine
-
Engine.png
-
Engine
是Gin
的引擎,把上下文和路由聯合起來-
RouterGroup
實現了一系列給Engine
添加路由的方法,方便使用 -
sync.Pool
是復用池的概念,目的是減少GC帶來的影響,用法是先創建再放入,不能理解為線程池或數據庫連接池的概念,因為會隨時回收,是編程人員不可控的。 -
trees
是Gin
實現快速路由匹配的利器,是基于Trie
的算法思想進行路由匹配的,:param
和*
的實現都是由它完成的,有興趣的同學可以參考下我的上一篇文章字典樹-Trie
-
常用方法
初始化
Default.jpg
- 初始化Engine
中間件
Use.jpg
- 將添加的中間件加入到Handles數組里面,形成調用鏈
創建路由
Get.jpg
- 創建一個
Get
方法并放入trees
中,用于運行中的匹配
啟動服務
Run.jpg
- 調用
http
庫中的監聽函數
調用過程
ServeHttp.jpg
-
Gin
自己實現http.Handler
接口,從trees
中去到注冊進去的業務,進行鏈式調用
中間件
這里我們看到tree中路由對應的是HandlersChain,實際就是[]HandlerFunc,所以一個路由,實際上會對應多個handlers。
首先我們已經把request和responseWriter封裝在context里面了,多個handler只要處理好這個context就可以了,所以是可以一個路由擁有多個handler的。
其次這里的handler是怎么來的呢?
每個路由的handler有幾個來源,第一個來源是在engine.GET的時候調用增加的。第二個來源是RouterGroup.GET的時候增加的,其實這兩種方式都是調用
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", 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()
}
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
}
從兩個copy的順序可以看出,group的handler高于自定義的handler。這里自定義的handler可以是多個,比如:
router.GET("/before", MiddleWare(), func(c *gin.Context) {
request := c.MustGet("request").(string)
c.JSON(http.StatusOK, gin.H{
"middile_request": request,
})
})
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("before middleware")
c.Set("request", "clinet_request")
c.Next()
fmt.Println("before middleware")
}
}
這里的/before實際上是帶了兩個handler。
第三種方法是使用Use增加中間件的方式:
router.Use(MiddleWare())
這里的會把這個中間件(實際上也是一個handler)存放到routerRroup上。所以中間件是屬于groupHandlers的。
在請求進來的時候是如何調用的呢?
答案還是在handleHTTPRequest中
func (engine *Engine) handleHTTPRequest(c *Context) {
...
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
c.handlers = handlers
c.Params = params
c.Next()
c.writermem.WriteHeaderNow()
return
}
..
}
func (c *Context) Next() {
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
每個請求進來,匹配好路由之后,會獲取這個路由最終combine的handlers,把它放在全局的context中,然后通過調用context.Next()來進行遞歸調用這個handlers。
想一想,就算是鏈式調用,Gin
的Logger
記錄時間是如何做到的?在執行完自己的業務是如何終止計時的呢?
其實GoLang
就有這種特性,想一想defer
是怎么做的。
是的,就是利用棧的特性:先進后出
鏈式調用.png
func(c *lightGin.Context) {
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
// Log only when path is not being skipped
if _, ok := skip[path]; !ok {
param := LogFormatterParams{
Request: c.Request,
isTerm: isTerm,
Keys: c.Keys,
}
// Stop timer
param.TimeStamp = time.Now()
param.Latency = param.TimeStamp.Sub(start)
param.ClientIP = c.ClientIP()
param.Method = c.Request.Method
param.StatusCode = c.Writer.Status()
param.ErrorMessage = c.Errors.ByType(lightGin.ErrorTypePrivate).String()
param.BodySize = c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
param.Path = path
fmt.Fprint(out, formatter(param))
}
}