Gin 是一個用 Go (Golang) 編寫的 web 框架傍药,由于出色的性能優(yōu)勢而被廣泛使用,這里我們就來分析下 Gin 的請求生命周期
1 Gin 目錄結(jié)構(gòu)
先來了解下其目錄結(jié)構(gòu):
.├──binding依據(jù)HTTP請求Accept解析響應(yīng)數(shù)據(jù)格式│?? ├──binding.go│?? ├──binding_nomsgpack.go│?? ├──default_validator.go│?? ├──form.go│?? ├──form_mapping.go│?? ├──header.go│?? ├──json.go│?? ├──msgpack.go│?? ├──multipart_form_mapping.go│?? ├──protobuf.go│?? ├──query.go│?? ├──uri.go│?? ├──xml.go│?? ├──yaml.go├──ginS│?? └──gins.go├──internal│?? ├──bytesconv│?? │?? ├──bytesconv.go│?? └──json│??? ? ├──json.go│??? ? └──jsoniter.go├──render依據(jù)解析的HTTP請求Accept響應(yīng)格式生成響應(yīng)│?? ├──data.go│?? ├──html.go│?? ├──json.go│?? ├──msgpack.go│?? ├──protobuf.go│?? ├──reader.go│?? ├──redirect.go│?? ├──render.go│?? ├──text.go│?? ├──xml.go│?? └──yaml.go├──auth.go├── *context.go├──context_appengine.go├──debug.go├──deprecated.go├──errors.go├──fs.go├── *gin.go├──logger.go├──mode.go設(shè)置Gin運行環(huán)境模式├──path.goPath處理├──recovery.go處理Panic的Recovery中間件├── *response_writer.goResponseWriter├── *routergroup.go路由組設(shè)置├──tree.go路由算法├──utils.gohelper函數(shù)└──version.go
其中比較重要的模塊為: context.go靴姿,gin.go沃但,routergroup.go,以及 tree.go佛吓;分別處理 HTTP 請求及響應(yīng)上下文宵晚,gin 引擎初始化垂攘,路由注冊及路由查找算法實現(xiàn)。
binding 目錄內(nèi)提供基于 HTTP 請求消息頭 Context-Type 的 MIME 信息自動解析功能淤刃,相對應(yīng)的 Render 目錄下提供具體數(shù)據(jù)格式渲染的實現(xiàn)方法晒他。
2 Gin 請求生命周期
本文著重介紹 Gin 實現(xiàn)一個 Web 服務(wù)器,從請求到達到生成響應(yīng)整個生命周期內(nèi)的核心功能點逸贾,這將有助于我們理解 Gin 的執(zhí)行原理和以后的開發(fā)工作的展開陨仅。
2.1 簡單了解下 Gin 服務(wù)執(zhí)行流程
先從官網(wǎng)的第一個 demo example.go 出發(fā):
packagemainimport"github.com/gin-gonic/gin"funcmain(){// 創(chuàng)建 Gin Engine 實例r := gin.Default()// 設(shè)置請求 URI /ping 的路由及響應(yīng)處理函數(shù)r.GET("/ping",func(c *gin.Context){? ? ? ? c.JSON(200, gin.H{"message":"pong",? ? ? ? })? ? })// 啟動 Web 服務(wù),監(jiān)聽端口,等待 HTTP 請求到并生成響應(yīng)r.Run()// 監(jiān)聽并在 0.0.0.0:8080 上啟動服務(wù)}
通過執(zhí)行 go run example.go 命令來運行代碼铝侵,它會啟動一個阻塞進程監(jiān)聽并等待 HTTP 請求:
# 運行 example.go并且在瀏覽器中訪問0.0.0.0:8080/ping$gorun example.go
從代碼中我們可以看出通過 Gin 實現(xiàn)一個最簡單的 Web 服務(wù)器灼伤,只需 3 個步驟:
1)創(chuàng)建 Gin 實例
2)注冊路由及處理函數(shù)
3)啟動 Web 服務(wù)
2.2 Gin 生命周期
2.2.1 創(chuàng)建 Gin 實例
Gin 實例創(chuàng)建通過?gin.Default()?方法完成,其定義在 gin.go#L159 文件里:
// Default returns an Engine instancewiththe LoggerandRecoverymiddleware already attached.funcDefault() *Engine{? ? debugPrintWARNINGDefault()engine:=New()? ? engine.Use(Logger(),Recovery())returnengine}
Default()?方法實現(xiàn)如下功能:
1)創(chuàng)建 Gin 框架對象 Engine
2)配置 Gin 默認的中間件咪鲜,Logger() 和 Recovery()狐赡,其實現(xiàn)分別位于 logger.go 和 recovery.go 文件內(nèi)
3)返回 Gin 框架對象
其中?New()?方法會實例化 Gin 的 Engine 對象,gin.go#L129
//NewreturnsanewblankEngineinstancewithoutanymiddlewareattached.//By default the configuration is://-RedirectTrailingSlash:true//-RedirectFixedPath:false//-HandleMethodNotAllowed:false//-ForwardedByClientIP:true//-UseRawPath:false//-UnescapePathValues:truefuncNew()*Engine{debugPrintWARNINGNew()engine:=&Engine{RouterGroup:RouterGroup{Handlers:nil,basePath:"/",root:true,},FuncMap:template.FuncMap{},RedirectTrailingSlash:true,RedirectFixedPath:false,HandleMethodNotAllowed:false,ForwardedByClientIP:true,AppEngine:defaultAppEngine,UseRawPath:false,RemoveExtraSlash:false,UnescapePathValues:true,MaxMultipartMemory:defaultMultipartMemory,trees:make(methodTrees,0,9),delims:render.Delims{Left:"{{", Right: "}}"},secureJSONPrefix:"while(1);",}engine.RouterGroup.engine=engineengine.pool.New=func()interface{}{returnengine.allocateContext()}returnengine}
實例化比較核心的功能是:
1)初始化 Engine 對象, 關(guān)鍵步驟是初始化路由組 RouterGroup疟丙。
2)初始化 pool, 這是核心步驟. pool 用來存儲 context 上下文對象. 用來優(yōu)化處理 http 請求時的性能猾警。
后面會重點分析 engine.pool 的實現(xiàn)細節(jié)。
Engine?是 Gin 框架的核心引擎 gin.go#L56隆敢,數(shù)據(jù)結(jié)構(gòu)如下:
typeEngine struct {RouterGroup// 關(guān)鍵:路由組//設(shè)置開關(guān)RedirectTrailingSlashboolRedirectFixedPathboolHandleMethodNotAllowedboolForwardedByClientIPboolAppEngineboolUseRawPathboolUnescapePathValuesboolMaxMultipartMemoryint64RemoveExtraSlashbool//界定符delimsrender.DelimssecureJSONPrefixstringHTMLRenderrender.HTMLRenderFuncMaptemplate.FuncMapallNoRouteHandlersChainallNoMethodHandlersChainnoRouteHandlersChainnoMethodHandlersChainpoolsync.Pool // 關(guān)鍵:context 處理treesmethodTreesmaxParamsuint16}
Engine 結(jié)構(gòu)體內(nèi)部除一些功能性開關(guān)設(shè)置外发皿,核心的就是 RouterRroup,pool 和 trees拂蝎。Gin 的所有組件都是由 Engine 驅(qū)動穴墅。
2.2.2 路由注冊
完成 Gin 的實例化之后,我們可以通過?r.GET("/ping", func(c *gin.Context) {})?定義 HTTP 路由及處理 handler 函數(shù)温自。
以 gin.GET 為例玄货,展開源碼如下:
// GET is a shortcut for router.Handle("GET", path, handle).func(group *RouterGroup)GET(relativePathstring, handlers ...HandlerFunc)IRoutes{returngroup.handle(http.MethodGet, relativePath, handlers)}
gin.GET?定義 HTTP GET 請求的路由及處理方法,并返回?IRoutes?對象實例悼泌。
2.2.2.1 RouterGroup 結(jié)構(gòu)體
// RouterGroup is used internally to configure router, a RouterGroup is associated with// a prefix and an array of handlers (middleware).typeRouterGroupstruct{Handlers HandlersChain? ? basePath string? ? engine? *Engine? ? rootbool}
RouterGroup?routergroup.go#L41 用于配置路由松捉,其中:
Handlers 數(shù)組定義了 Gin 中間件調(diào)用的 handler 方法
engine 為 gin.go 實例化時設(shè)置的 Engine 實例對象
2.2.2.2 handle 添加路由
在?gin.GET?方法內(nèi)部通過調(diào)用?group.handle()?routergroup.go#L72 方法添加路由:
func(group*RouterGroup)handle(httpMethod,relativePathstring,handlersHandlersChain)IRoutes{absolutePath := group.calculateAbsolutePath(relativePath)? ? handlers = group.combineHandlers(handlers)? ? ? ? group.engine.addRoute(httpMethod, absolutePath, handlers)// 添加路由? ? return group.returnObj()}
路由就和 Engine 綁定好關(guān)系了。
2.2.2.3 IRoute 接口類型
// IRoutes defines all router handle interface.typeIRoutesinterface{? ? Use(...HandlerFunc) IRoutes? ? Handle(string,string, ...HandlerFunc) IRoutes? ? Any(string, ...HandlerFunc) IRoutes? ? GET(string, ...HandlerFunc) IRoutes? ? POST(string, ...HandlerFunc) IRoutes? ? DELETE(string, ...HandlerFunc) IRoutes? ? PATCH(string, ...HandlerFunc) IRoutes? ? PUT(string, ...HandlerFunc) IRoutes? ? OPTIONS(string, ...HandlerFunc) IRoutes? ? HEAD(string, ...HandlerFunc) IRoutes? ? StaticFile(string,string) IRoutes? ? Static(string,string) IRoutes? ? StaticFS(string, http.FileSystem) IRoutes}
IRoute?是個接口類型馆里,定義了 router 所需的 handle 接口隘世,RouterGroup 實現(xiàn)了這個接口。
2.2.2.4 小結(jié)
推而廣之鸠踪,Gin 還支持如下等路由注冊方法:
r.POST
r.DELETE
r.PATCH
r.PUT
r.OPTIONS
r.HEAD
以及 r.Any
它們都定義在 routergroup.go 文件內(nèi)丙者。
2.2.3 接收請求并響應(yīng)
Gin 實例化和路由設(shè)置后工作完成后,我們進入 Gin 生命周期執(zhí)行的核心功能分析营密,Gin 究竟是如何啟動 Web 服務(wù)械媒,監(jiān)聽 HTTP 請求并執(zhí)行 HTTP 請求處理函數(shù)生成響應(yīng)的。這些工作統(tǒng)統(tǒng)從?gin.Run()?出發(fā) gin.go#L305:
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.// It is a shortcut for http.ListenAndServe(addr, router)// Note: this method will block the calling goroutine indefinitely unless an error happens.func(engine *Engine)Run(addr ...string)(err error){deferfunc(){ debugPrintError(err) }()? ? address := resolveAddress(addr)? ? debugPrint("Listening and serving HTTP on %s\n", address)? ? err = http.ListenAndServe(address, engine)return}
gin.Run()?是 net/http 標準庫?http.ListenAndServe(addr, router)?的簡寫,功能是將路由連接到 http.Server 啟動并監(jiān)聽 HTTP 請求纷捞。
由此痢虹,我們不得不放下手頭的工作,率先了解下 net/http 標準庫的執(zhí)行邏輯主儡。
2.2.3.1 net/http 標準庫
net/http 標準庫的?ListenAndServe(addr string, handler Handler)?方法定義在 net/http/server.go#L3162 文件里奖唯。
參數(shù)簽名的第一個參數(shù)是監(jiān)聽的服務(wù)地址和端口;
第二個參數(shù)接收一個 Handler 對象它是一個接口類型需要實現(xiàn)?ServeHTTP(ResponseWriter, *Request)?方法缀辩。
func ListenAndServe(addr string,handlerHandler)error{server:= &Server{Addr: addr,Handler:handler}returnserver.ListenAndServe()}
在?ListenAndServe(addr string, handler Handler)?內(nèi)部則調(diào)用的是 Server 對象的?ListenAndServe()?方法由交由它啟動監(jiān)聽和服務(wù)功能:
// ListenAndServe listens on the TCP network address srv.Addr and then// calls Serve to handle requests on incoming connections.// Accepted connections are configured to enable TCP keep-alives.//// If srv.Addr is blank, ":http" is used.//// ListenAndServe always returns a non-nil error. After Shutdown or Close,// the returned error is ErrServerClosed.func(srv *Server)ListenAndServe()error{ifsrv.shuttingDown() {returnErrServerClosed? ? }? ? addr := srv.Addrifaddr ==""{? ? ? ? addr =":http"}? ? ln, err := net.Listen("tcp", addr)// 監(jiān)聽iferr !=nil{returnerr? ? }returnsrv.Serve(ln)// 啟動服務(wù)等待連接}
然后,執(zhí)行?srv.Serve(ln)?即?Server.Serve(l net.Listener)?server.go#L2951踪央,在?net.Listen("tcp", addr)?等待連接臀玄,創(chuàng)建新的 goroutine 來處理請求和生成響應(yīng)的業(yè)務(wù)邏輯:
func(srv *Server)Serve(l net.Listener)error{? ? ...for{? ? ? ? rw, e := l.Accept()ife !=nil{select{case<-srv.getDoneChan():returnErrServerCloseddefault:? ? ? ? ? ? }? ? ? ? ? ? ...returne? ? ? ? }ifcc := srv.ConnContext; cc !=nil{? ? ? ? ? ? ctx = cc(ctx, rw)ifctx ==nil{panic("ConnContext returned nil")? ? ? ? ? ? }? ? ? ? }? ? ? ? tempDelay =0c := srv.newConn(rw)? ? ? ? c.setState(c.rwc, StateNew)// before Serve can returngoc.serve(ctx)// 啟動 Web 服務(wù)}}
最后,進入到?go c.serve(ctx)?啟動 Web 服務(wù)畅蹂,讀取 HTTP 請求數(shù)據(jù)健无,生成響應(yīng) server.go#L1817:
// Serve a new connection.func(c*conn)serve(ctx context.Context) {? ? ...// HTTP/1.x from here on.for{? ? ? ? w, err :=c.readRequest(ctx)// 讀取 HTTP 去請求...// HTTP cannot have multiple simultaneous active requests.[*]// Until the server replies to this request, it can't read another,// so we might as well run the handler in this goroutine.// [*] Not strictly true: HTTP pipelining. We could let them all process// in parallel even if their responses need to be serialized.// But we're not going to implement HTTP pipelining because it// was never deployed in the wild and the answer is HTTP/2.serverHandler{c.server}.ServeHTTP(w, w.req)? ? ? ? ...? ? }}
最終,調(diào)用?r.Run()?方法傳入的 Engine 來執(zhí)行?serverHandler{c.server}.ServeHTTP(w, w.req)?處理接收到的 HTTP 請求和生成響應(yīng)液斜,這里將響應(yīng)處理的控制權(quán)交回給 Gin Engine累贤。
小結(jié)
Go 標準庫 net/http 提供了豐富的 Web 編程接口支持,感興趣的朋友可以深入研究下 net/http 標準庫源碼少漆,了解其實現(xiàn)細節(jié)臼膏。
2.2.3.2 Engine.ServeHTTP 處理 HTTP 請求
Engine.ServeHTTP?是 Gin 框架核心中的核心,我們來看下它是如何處理請求和響應(yīng)的:
// ServeHTTP conforms to the http.Handler interface.func(engine *Engine)ServeHTTP(w http.ResponseWriter, req *http.Request) {c:= engine.pool.Get().(*Context)// 從臨時對象池 pool 獲取 context 上下文對象c.writermem.reset(w)c.Request= reqc.reset()? ? engine.handleHTTPRequest(c)// 處理 HTTP 請求engine.pool.Put(c)// 使用完 context 對象, 歸還給 pool }
ServeHTTP會先獲取 Gin Context 上下文信息示损,接著將 Context 注入到 engine.handleHTTPRequest(c) 方法內(nèi)來處理 HTTP 請求:
func(engine *Engine)handleHTTPRequest(c *Context){? ? httpMethod := c.Request.Method? ? rPath := c.Request.URL.Path? ? ...// Find root of the tree for the given HTTP methodt := engine.treesfori, tl :=0,len(t); i < tl; i++ {? ? ? ? ...? ? ? ? root := t[i].root// Find route in treevalue := root.getValue(rPath, c.params, unescape)? ? ? ? ? ? ? ? ...ifvalue.handlers !=nil{? ? ? ? ? ? c.handlers = value.handlers? ? ? ? ? ? c.fullPath = value.fullPath? ? ? ? ? ? c.Next()// 具體執(zhí)行響應(yīng)處理c.writermem.WriteHeaderNow()return}ifhttpMethod !="CONNECT"&& rPath !="/"{? ? ? ? ? ? ...? ? ? ? }break}? ? ...}
handleHTTPRequest?完成?路由?及?回調(diào)?方法的查找渗磅,執(zhí)行 Gin.Context.Next() 調(diào)用處理響應(yīng)。
2.2.3.3 Gin.Context.Next() 在內(nèi)部中間件執(zhí)行 handler 方法
Gin.Context.Next()?僅有數(shù)行代碼:
// Next should be used only inside middleware.// It executes the pending handlers in the chain inside the calling handler.// See example in GitHub.func(c*Context)Next() {c.index++forc.index < int8(len(c.handlers)) {c.handlers[c.index](c)c.index++? ? }}
功能是在 Gin 內(nèi)部中間件中執(zhí)行 handler 調(diào)用检访,即 r.GET() 中傳入的
func(c*gin.Context){c.JSON(200, gin.H{"message":"pong",? ? })}
方法生成 HTTP 響應(yīng)始鱼。
到這里我們完成了 Gin 的請求和響應(yīng)的完整流程的源碼走讀,但是我們有必要對 Gin.Context 有多一些的了解脆贵。
2.2.3.4 Gin.Context 上下文處理
Gin 的 Context 實現(xiàn)了對 request 和 response 的封裝是 Gin 的核心實現(xiàn)之一医清,其數(shù)據(jù)結(jié)構(gòu)如下:
// Context is the most important part of gin. It allows us to pass variables between middleware,// manage the flow, validate the JSON of a request and render a JSON response for example.typeContextstruct{? ? writermem responseWriter? ? Request? *http.Request// HTTP 請求Writer? ? ResponseWriter// HTTP 響應(yīng)Params? Params? ? handlers HandlersChain// 關(guān)鍵: 數(shù)組: 內(nèi)包含方法集合indexint8engine *Engine// 關(guān)鍵: 引擎// Keys is a key/value pair exclusively for the context of each request.Keysmap[string]interface{}// Errors is a list of errors attached to all the handlers/middlewares who used this context.Errors errorMsgs// Accepted defines a list of manually accepted formats for content negotiation.Accepted []string}
其包含了 Gin 請求及響應(yīng)的上下文信息和 Engine 指針數(shù)據(jù)
Request *http.Request : HTTP 請求
Writer ResponseWriter : HTTP 響應(yīng)
handlers HandlersChain : 是?type HandlerFunc func(*Context)?方法集即路由設(shè)置的回調(diào)函數(shù)
engine *Engine : gin框架對象
Gin 官方文檔 幾乎所有的示例都是在講解 Context 的使用方法,可用說研究 Context 源碼對用好 Gin 框架會起到只管重要的作用卖氨。