白糖貿(mào)易怎么做網(wǎng)站廈門(mén)seo公司到1火星
Go 源碼之 gin 框架
go源碼之gin - Jxy 博客
一、總結(jié)
-
gin.New()初始化一個(gè)實(shí)例:gin.engine,該實(shí)例實(shí)現(xiàn)了http.Handler接口。實(shí)現(xiàn)了ServeHTTP方法
-
注冊(cè)路由、注冊(cè)中間件,調(diào)用addRoute將路由和中間件注冊(cè)到 methodTree 前綴樹(shù)(節(jié)省空間,搜索快)下,methodTree 的非葉子節(jié)點(diǎn)是相同 method 的 url 的最長(zhǎng)公共前綴字符串,葉子節(jié)點(diǎn)是完整的 url 路徑
-
http請(qǐng)求會(huì)執(zhí)行ServeHTTP方法,內(nèi)部會(huì)根據(jù)請(qǐng)求的 method 和 url 到前綴樹(shù) methodTree 中匹配 url,然后遍歷 handlers 數(shù)組,依次執(zhí)行c.next()執(zhí)行,可以通過(guò)c.Abort()進(jìn)行中斷
-
流程:
-
啟動(dòng):
初始化engine;初始化一個(gè)長(zhǎng)度為 9 的 methodTrees 數(shù)組;調(diào)用 addRoute 注冊(cè)全局中間件、路由到 methodTree 數(shù)組中
-
處理:
gin.engine 實(shí)現(xiàn)了 http.handler 接口,實(shí)現(xiàn)了 ServeHTTP(ResponseWriter, *Request),所有的請(qǐng)求都會(huì)經(jīng)過(guò) ServeHTTP 處理;
ServeHTTP 方法:從 methodTrees 中找到本次請(qǐng)求的 httpMethod 的 Tree–>根據(jù)請(qǐng)求Url Path 找到Tree下對(duì)應(yīng)的節(jié)點(diǎn)nodeValue(包含了中間件handler)—> 執(zhí)行c.Next() 依次執(zhí)行handler,,handler內(nèi)部 可以調(diào)用c.JSON等往響應(yīng)的http寫(xiě)入數(shù)據(jù)
-
-
注意點(diǎn):
- 路由中間件的執(zhí)行順序和添加順序一致,遵循先進(jìn)先出規(guī)則
- c.JSON()等是http請(qǐng)求的末端函數(shù),如果要添加后置攔截器,需要在此之前執(zhí)行c.Next()即可, c.Abort()為終止執(zhí)行后續(xù)的中間件
-
使用 sync.Pool 來(lái)復(fù)用上下午對(duì)象
-
gin 底層依舊是依賴 net/http 包,本質(zhì)上是一個(gè)路由處理器,實(shí)現(xiàn)了 http.handler 接口,實(shí)現(xiàn)了 ServeHTTP
-
維護(hù)了 method 數(shù)組,每個(gè)元素是一個(gè) radix 樹(shù)(壓縮前綴樹(shù))
-
gin 的中間件是使用切片實(shí)現(xiàn)的,添加中間件也就是切片追加元素的過(guò)程,中間件按追加先后順序依次執(zhí)行
-
gin 允許為不同的路由組添加不同的中間件
-
路由組本質(zhì)上是一個(gè)模版,維護(hù)了路徑前綴、中間件等信息,讓用戶省去重復(fù)配置相同前綴和中間件的操作
-
新路由組繼承父路由組的所有處理器
-
如果上下文需要并發(fā)處理使用,需要使用上下文副本copy
二、源碼
(一)engine結(jié)構(gòu)
// Engine的實(shí)例,包括了路由組,路由樹(shù),中間件和其他等一系列配置
type Engine struct {// 路由組RouterGroup// 如果當(dāng)前路徑無(wú)法匹配,但存在帶有(不帶有)尾斜杠的路徑處理程序,RedirectTrailingFlash將啟用自動(dòng)重定向// 例如,如果請(qǐng)求了/foo/,但只有/foo的路由存在,則客戶端將被重定向到/foo// GET請(qǐng)求的http狀態(tài)代碼為301,所有其他請(qǐng)求方法的http狀態(tài)為307。RedirectTrailingSlash bool// RedirectFixedPath如果啟用,如果沒(méi)有為其注冊(cè)句柄,則路由器將嘗試 修復(fù) 當(dāng)前請(qǐng)求路徑。// 首先刪除像../或//這樣的多余路徑元素。然后,路由器對(duì)清理后的路徑進(jìn)行不區(qū)分大小寫(xiě)的查找。如果可以找到該路由的句柄,則路由器對(duì)GET請(qǐng)求使用狀態(tài)代碼301,// 對(duì)所有其他請(qǐng)求方法使用狀態(tài)代碼307,重新定向到正確的路徑。例如/FOO和/..//FOO可以重定向到/FOO。// RedirectTrailingFlash獨(dú)立于此選項(xiàng)。RedirectFixedPath bool// HandleMethodNotAllowed如果啟用,則如果當(dāng)前請(qǐng)求無(wú)法路由,則路由器會(huì)檢查當(dāng)前路由是否允許其他方法。// 如果是這種情況,則使用“Method Not Allowed”和 HTTP狀態(tài)代碼405 回答請(qǐng)求。// 如果不允許其他方法,則將請(qǐng)求委托給NotFound處理程序。HandleMethodNotAllowed bool// ForwardedByClientIP(如果啟用),將從與存儲(chǔ)在“(*gin.Engine).RemoteIPHeaders”中的標(biāo)頭匹配的請(qǐng)求標(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(默認(rèn)情況下),則UnescapePathValues實(shí)際上為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ù)的映射的映射類型。// 每個(gè)函數(shù)必須有一個(gè)返回值或兩個(gè)返回值,其中第二個(gè)返回值具有類型錯(cuò)誤。// 在這種情況下,如果在執(zhí)行期間第二個(gè)(error)參數(shù)的計(jì)算結(jié)果為非nil,則執(zhí)行終止,Execute返回該錯(cuò)誤。// FuncMap在“text/template”中具有與FuncMap相同的基本類型,復(fù)制到此處,因此客戶端無(wú)需導(dǎo)入“text/template”。FuncMap template.FuncMap trees methodTrees // 請(qǐng)求的method數(shù)組,每個(gè)元素是一個(gè)前綴樹(shù)}
(二)ServeHTTP(核心處理http方法)
// 所有的http請(qǐng)求最終都會(huì)走這里
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context) // 從池里取一個(gè)contextc.writermem.reset(w) // 重置c.Request = req // 賦值請(qǐng)求c.reset() // 重置數(shù)據(jù)engine.handleHTTPRequest(c) // 核心處理函數(shù)engine.pool.Put(c) // 放回緩沖池
}
// 核心處理函數(shù)
func (engine *Engine) handleHTTPRequest(c *Context) {// 請(qǐng)求的Method類型,如GET等httpMethod := c.Request.Method// 請(qǐng)求的路徑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和請(qǐng)求urlt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// 從前綴樹(shù)methodTree中匹配到葉子節(jié)點(diǎn)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() // 最終寫(xiě)入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() 中會(huì)初始化容量為9,匹配9個(gè)http.Method
每個(gè)節(jié)點(diǎn)都是壓縮前綴樹(shù):將相同請(qǐng)求method的url計(jì)算出最長(zhǎng)公共前綴字符串然后作為子節(jié)點(diǎn)
type methodTrees []methodTree// 壓縮前綴樹(shù),存儲(chǔ)了http.Method的請(qǐng)求路徑
type methodTree struct {method stringroot *node
}
type node struct {path string // 存儲(chǔ):共同的最長(zhǎng)前綴字符indices stringwildChild boolnType nodeTypepriority uint32children []*node // 有共同的最長(zhǎng)前綴字符path的url pathhandlers HandlersChainfullPath string // 葉子節(jié)點(diǎn)存儲(chǔ)的是完整的請(qǐng)求路徑
}
// 構(gòu)建樹(shù)的函數(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 // 請(qǐng)求句柄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é)點(diǎn)
}
(六)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 {// 計(jì)算出絕對(duì)路徑absolutePath := group.calculateAbsolutePath(relativePath)// 將請(qǐng)求的handler和group的全局handler合并handlers = group.combineHandlers(handlers)// 添加路由到前綴樹(shù)methodTree中group.engine.addRoute(httpMethod, absolutePath, handlers)return group.returnObj()
}
// 根據(jù)請(qǐng)求的method和path,構(gòu)建前綴樹(shù)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中獲取對(duì)應(yīng)method的根節(jié)點(diǎn)root := engine.trees.get(method)if root == nil {// 不存在,常見(jiàn)根節(jié)點(diǎn)root = new(node)root.fullPath = "/"engine.trees = append(engine.trees, methodTree{method: method, root: root})}// 構(gòu)建前綴樹(shù)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ù)寫(xiě)入c.Writer,但是還沒(méi)有響應(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è)計(jì),這個(gè)設(shè)計(jì)可以用來(lái)暫停執(zhí)行當(dāng)前handler,先執(zhí)行后面的handler,然后再執(zhí)行當(dāng)前handler后面的代碼
// handlers是一個(gè)中間件執(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()
可以用來(lái)終止后面所有handler的執(zhí)行,
// 這里將 c.index的值改了超級(jí)大,在c.Next()中會(huì)判斷c.index<len(handler),從而達(dá)到終止handler執(zhí)行的效果
func (c *Context) IsAborted() bool {return c.index >= abortIndex
}
三、常見(jiàn)問(wèn)題
如何設(shè)置前置攔截器和后置攔截器
-
方法一:
利用 handler 的存儲(chǔ)結(jié)構(gòu):所有的 handler 會(huì)按順序添加到數(shù)數(shù)組 []HandlerFunc 中,執(zhí)行的是按FIFO遍歷執(zhí)行,所有先添加的handler會(huì)先執(zhí)行,也就是說(shuō)越先添加的就是前置攔截器,越晚添加的就是后置攔截器
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(),會(huì)遍歷執(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ù)