白糖貿(mào)易怎么做網(wǎng)站廈門seo公司到1火星
Go 源碼之 gin 框架
go源碼之gin - Jxy 博客
一、總結(jié)
-
gin.New()初始化一個實例:gin.engine,該實例實現(xiàn)了http.Handler接口。實現(xiàn)了ServeHTTP方法
-
注冊路由、注冊中間件,調(diào)用addRoute將路由和中間件注冊到 methodTree 前綴樹(節(jié)省空間,搜索快)下,methodTree 的非葉子節(jié)點是相同 method 的 url 的最長公共前綴字符串,葉子節(jié)點是完整的 url 路徑
-
http請求會執(zhí)行ServeHTTP方法,內(nèi)部會根據(jù)請求的 method 和 url 到前綴樹 methodTree 中匹配 url,然后遍歷 handlers 數(shù)組,依次執(zhí)行c.next()執(zhí)行,可以通過c.Abort()進行中斷
-
流程:
-
啟動:
初始化engine;初始化一個長度為 9 的 methodTrees 數(shù)組;調(diào)用 addRoute 注冊全局中間件、路由到 methodTree 數(shù)組中
-
處理:
gin.engine 實現(xiàn)了 http.handler 接口,實現(xiàn)了 ServeHTTP(ResponseWriter, *Request),所有的請求都會經(jīng)過 ServeHTTP 處理;
ServeHTTP 方法:從 methodTrees 中找到本次請求的 httpMethod 的 Tree–>根據(jù)請求Url Path 找到Tree下對應(yīng)的節(jié)點nodeValue(包含了中間件handler)—> 執(zhí)行c.Next() 依次執(zhí)行handler,,handler內(nèi)部 可以調(diào)用c.JSON等往響應(yīng)的http寫入數(shù)據(jù)
-
-
注意點:
- 路由中間件的執(zhí)行順序和添加順序一致,遵循先進先出規(guī)則
- c.JSON()等是http請求的末端函數(shù),如果要添加后置攔截器,需要在此之前執(zhí)行c.Next()即可, c.Abort()為終止執(zhí)行后續(xù)的中間件
-
使用 sync.Pool 來復(fù)用上下午對象
-
gin 底層依舊是依賴 net/http 包,本質(zhì)上是一個路由處理器,實現(xiàn)了 http.handler 接口,實現(xiàn)了 ServeHTTP
-
維護了 method 數(shù)組,每個元素是一個 radix 樹(壓縮前綴樹)
-
gin 的中間件是使用切片實現(xiàn)的,添加中間件也就是切片追加元素的過程,中間件按追加先后順序依次執(zhí)行
-
gin 允許為不同的路由組添加不同的中間件
-
路由組本質(zhì)上是一個模版,維護了路徑前綴、中間件等信息,讓用戶省去重復(fù)配置相同前綴和中間件的操作
-
新路由組繼承父路由組的所有處理器
-
如果上下文需要并發(fā)處理使用,需要使用上下文副本copy
二、源碼
(一)engine結(jié)構(gòu)
// Engine的實例,包括了路由組,路由樹,中間件和其他等一系列配置
type Engine struct {// 路由組RouterGroup// 如果當(dāng)前路徑無法匹配,但存在帶有(不帶有)尾斜杠的路徑處理程序,RedirectTrailingFlash將啟用自動重定向// 例如,如果請求了/foo/,但只有/foo的路由存在,則客戶端將被重定向到/foo// GET請求的http狀態(tài)代碼為301,所有其他請求方法的http狀態(tài)為307。RedirectTrailingSlash bool// RedirectFixedPath如果啟用,如果沒有為其注冊句柄,則路由器將嘗試 修復(fù) 當(dāng)前請求路徑。// 首先刪除像../或//這樣的多余路徑元素。然后,路由器對清理后的路徑進行不區(qū)分大小寫的查找。如果可以找到該路由的句柄,則路由器對GET請求使用狀態(tài)代碼301,// 對所有其他請求方法使用狀態(tài)代碼307,重新定向到正確的路徑。例如/FOO和/..//FOO可以重定向到/FOO。// RedirectTrailingFlash獨立于此選項。RedirectFixedPath bool// HandleMethodNotAllowed如果啟用,則如果當(dāng)前請求無法路由,則路由器會檢查當(dāng)前路由是否允許其他方法。// 如果是這種情況,則使用“Method Not Allowed”和 HTTP狀態(tài)代碼405 回答請求。// 如果不允許其他方法,則將請求委托給NotFound處理程序。HandleMethodNotAllowed bool// ForwardedByClientIP(如果啟用),將從與存儲在“(*gin.Engine).RemoteIPHeaders”中的標(biāo)頭匹配的請求標(biāo)頭解析客戶端IP。// 如果未提取任何IP,它將返回到從“(*gin.Context).Request.Remoddr”獲取的IP。ForwardedByClientIP bool// AppEngine was deprecated.// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD// #726 #755 If enabled, it will trust some headers starting with// 'X-AppEngine...' for better integration with that PaaS.AppEngine bool// UseRawPath if enabled, the url.RawPath will be used to find parameters.// UseRawPath(如果啟用),url.RawPath將用于查找參數(shù)。UseRawPath bool// UnescapePathValues如果為true,則路徑值將被取消轉(zhuǎn)義。// 如果UseRawPath為false(默認情況下),則UnescapePathValues實際上為true,// 作為url。將使用路徑,該路徑已未被覆蓋。UnescapePathValues bool// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.// See the PR #1817 and issue #1644RemoveExtraSlash bool// RemoteIPHeaders list of headers used to obtain the client IP when// `(*gin.Engine).ForwardedByClientIP` is `true` and// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.RemoteIPHeaders []string// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by// that platform, for example to determine the client IPTrustedPlatform string// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm// method call.MaxMultipartMemory int64// UseH2C enable h2c support.UseH2C bool// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.ContextWithFallback booldelims render.Delims// https://cloud.tencent.com/developer/article/1580456secureJSONPrefix string HTMLRender render.HTMLRender// FuncMap是定義從名稱到函數(shù)的映射的映射類型。// 每個函數(shù)必須有一個返回值或兩個返回值,其中第二個返回值具有類型錯誤。// 在這種情況下,如果在執(zhí)行期間第二個(error)參數(shù)的計算結(jié)果為非nil,則執(zhí)行終止,Execute返回該錯誤。// FuncMap在“text/template”中具有與FuncMap相同的基本類型,復(fù)制到此處,因此客戶端無需導(dǎo)入“text/template”。FuncMap template.FuncMap trees methodTrees // 請求的method數(shù)組,每個元素是一個前綴樹}
(二)ServeHTTP(核心處理http方法)
// 所有的http請求最終都會走這里
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context) // 從池里取一個contextc.writermem.reset(w) // 重置c.Request = req // 賦值請求c.reset() // 重置數(shù)據(jù)engine.handleHTTPRequest(c) // 核心處理函數(shù)engine.pool.Put(c) // 放回緩沖池
}
// 核心處理函數(shù)
func (engine *Engine) handleHTTPRequest(c *Context) {// 請求的Method類型,如GET等httpMethod := c.Request.Method// 請求的路徑rPath := c.Request.URL.Pathunescape := falseif engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {rPath = c.Request.URL.RawPathunescape = engine.UnescapePathValues}if engine.RemoveExtraSlash {rPath = cleanPath(rPath)}// 從methodTree中匹配method和請求urlt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// 從前綴樹methodTree中匹配到葉子節(jié)點valuevalue := root.getValue(rPath, c.params, c.skippedNodes, unescape)if value.params != nil {c.Params = *value.params}// 執(zhí)行中間件if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.Next() // 依次從handlers數(shù)組中FIFO執(zhí)行中間件c.writermem.WriteHeaderNow() // 最終寫入http響應(yīng)流responseWriter中return}if httpMethod != http.MethodConnect && rPath != "/" {if value.tsr && engine.RedirectTrailingSlash {redirectTrailingSlash(c)return}if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {return}}break}if engine.HandleMethodNotAllowed {for _, tree := range engine.trees {if tree.method == httpMethod {continue}if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {c.handlers = engine.allNoMethodserveError(c, http.StatusMethodNotAllowed, default405Body)return}}}c.handlers = engine.allNoRouteserveError(c, http.StatusNotFound, default404Body)
}
(三)methodTrees
在New() 中會初始化容量為9,匹配9個http.Method
每個節(jié)點都是壓縮前綴樹:將相同請求method的url計算出最長公共前綴字符串然后作為子節(jié)點
type methodTrees []methodTree// 壓縮前綴樹,存儲了http.Method的請求路徑
type methodTree struct {method stringroot *node
}
type node struct {path string // 存儲:共同的最長前綴字符indices stringwildChild boolnType nodeTypepriority uint32children []*node // 有共同的最長前綴字符path的url pathhandlers HandlersChainfullPath string // 葉子節(jié)點存儲的是完整的請求路徑
}
// 構(gòu)建樹的函數(shù)
func addRoute(){}
r := gin.Default()r.Use(gin.Recovery(), gin.Logger())r.GET("/user/GetUserInfo", func(context *gin.Context) {})r.GET("/user/GetManyUserInfo", func(context *gin.Context) {})r.Run(":9091")
(四)context結(jié)構(gòu)
// gin.Context是gin框架中最重要的一部分
type Context struct {writermem responseWriter // 響應(yīng)的數(shù)據(jù)流Request *http.Request // 請求句柄Writer ResponseWriter // 響應(yīng)的Writerhandlers HandlersChain // 中間件handler數(shù)組index int8 // handler數(shù)組的下標(biāo),表示已經(jīng)執(zhí)行的下標(biāo)fullPath string // 完整的url路徑engine *Engine
}
(五)RouterGroup
// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
// RouterGroup在內(nèi)部用于配置路由器,RouterGroup與前綴和處理程序(中間件)數(shù)組相關(guān)聯(lián)。
type RouterGroup struct {Handlers HandlersChain // 中間件handlerbasePath string // 基礎(chǔ)路徑engine *Engine root bool // 是否是根節(jié)點
}
(六)c.GET()
// 調(diào)用了handler,httpMethod=http.MethodGet,其他什么c.POST等都是差不多
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)// 將請求的handler和group的全局handler合并handlers = group.combineHandlers(handlers)// 添加路由到前綴樹methodTree中group.engine.addRoute(httpMethod, absolutePath, handlers)return group.returnObj()
}
// 根據(jù)請求的method和path,構(gòu)建前綴樹methodTree
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)// 從數(shù)組methodTrees中獲取對應(yīng)method的根節(jié)點root := engine.trees.get(method)if root == nil {// 不存在,常見根節(jié)點root = new(node)root.fullPath = "/"engine.trees = append(engine.trees, methodTree{method: method, root: root})}// 構(gòu)建前綴樹root.addRoute(path, handlers)// Update maxParamsif paramsCount := countParams(path); paramsCount > engine.maxParams {engine.maxParams = paramsCount}if sectionsCount := countSections(path); sectionsCount > engine.maxSections {engine.maxSections = sectionsCount}
}
(七)c.JSON
func (c *Context) JSON(code int, obj any) {c.Render(code, render.JSON{Data: obj})
}
// 將數(shù)據(jù)寫入c.Writer,但是還沒有響應(yīng)http
func (c *Context) Render(code int, r render.Render) {c.Status(code)if !bodyAllowedForStatus(code) {r.WriteContentType(c.Writer)c.Writer.WriteHeaderNow()return}if err := r.Render(c.Writer); err != nil {panic(err)}
}
(八)c.Next()
非常巧妙的設(shè)計,這個設(shè)計可以用來暫停執(zhí)行當(dāng)前handler,先執(zhí)行后面的handler,然后再執(zhí)行當(dāng)前handler后面的代碼
// handlers是一個中間件執(zhí)行函數(shù)的數(shù)組,[]HandlerFunc
func (c *Context) Next() {// index記錄 已經(jīng)執(zhí)行到數(shù)組[]HandlerFunc的下標(biāo),// index++ 繼續(xù)執(zhí)行后面的handlerFuncc.index++ for c.index < int8(len(c.handlers)) {c.handlers[c.index](c)c.index++}
}
(九)c.Abort()
可以用來終止后面所有handler的執(zhí)行,
// 這里將 c.index的值改了超級大,在c.Next()中會判斷c.index<len(handler),從而達到終止handler執(zhí)行的效果
func (c *Context) IsAborted() bool {return c.index >= abortIndex
}
三、常見問題
如何設(shè)置前置攔截器和后置攔截器
-
方法一:
利用 handler 的存儲結(jié)構(gòu):所有的 handler 會按順序添加到數(shù)數(shù)組 []HandlerFunc 中,執(zhí)行的是按FIFO遍歷執(zhí)行,所有先添加的handler會先執(zhí)行,也就是說越先添加的就是前置攔截器,越晚添加的就是后置攔截器
r := gin.Default()r.Use(gin.Recovery()) // 前置攔截器r.GET("/user/GetUserInfo", func(context *gin.Context) {}) // 中間執(zhí)行函數(shù)r.Use(func(context *gin.Context) {}) // 后置攔截器
-
方法二
使用c.Next()方法,在handler函數(shù)內(nèi)部,可以先執(zhí)行一部分代碼,然后執(zhí)行c.Next(),會遍歷執(zhí)行后續(xù)的handler,當(dāng)所有的handler結(jié)束后,在執(zhí)行當(dāng)前handler c.Next()之后的代碼
r := gin.Default()r.Use(, func(c *gin.Context) {// 前置代碼c.Next() // 執(zhí)行所有handler// 后置代碼}) r.GET("/user/GetUserInfo", func(context *gin.Context) {}) // 中間執(zhí)行函數(shù)