深入了解Go語(yǔ)言中web框架的中間件運(yùn)行機(jī)制
一、中間件的基本使用
在web開(kāi)發(fā)中,中間件起著很重要的作用。比如,身份驗(yàn)證、權(quán)限認(rèn)證、日志記錄等。以下就是各框架對(duì)中間件的基本使用。
1.1 iris框架中間件的使用
package main import ( "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/middleware/recover" ) func main() { app := iris.New() //通過(guò)use函數(shù)使用中間件recover app.Use(recover.New()) app.Get("/home",func(ctx *context.Context) { ctx.Write([]byte("Hello Wolrd")) }) app.Listen(":8080") }
1.2 gin框架中使用中間件
package main import ( "github.com/gin-gonic/gin" ) func main() { g := gin.New() // 通過(guò)Use函數(shù)使用中間件 g.Use(gin.Recovery()) g.GET("/", func(ctx *gin.Context){ ctx.Writer.Write([]byte("Hello World")) }) g.Run(":8000") }
1.3 echo框架中使用中間件示例
package main import ( v4echo "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) func main() { e := v4echo.New() // 通過(guò)use函數(shù)使用中間件Recover e.Use(middleware.Recover()) e.GET("/home", func(c v4echo.Context) error { c.Response().Write([]byte("Hello World")) return nil }) e.Start(":8080") }
首先我們看下三個(gè)框架中使用中間件的共同點(diǎn):
- 都是使用
Use
函數(shù)來(lái)使用中間件 - 都內(nèi)置了
Recover
中間件 - 都是先執(zhí)行中間件
Recover
的邏輯,然后再輸出Hello World
接下來(lái)我們繼續(xù)分析中間件的具體實(shí)現(xiàn)。
二、中間件的實(shí)現(xiàn)
2.1 iris中間件實(shí)現(xiàn)
2.1.1 iris框架中間件類(lèi)型
首先,我們看下Use函數(shù)的簽名,如下:
func (api *APIBuilder) Use(handlers ...context.Handler) { api.middleware = append(api.middleware, handlers...) }
在該函數(shù)中,handlers是一個(gè)不定長(zhǎng)參數(shù),說(shuō)明是一個(gè)數(shù)組。參數(shù)類(lèi)型是context.Handler,我們?cè)賮?lái)看context.Handler的定義如下:
type Handler func(*Context)
這個(gè)類(lèi)型是不是似曾相識(shí)。是的,在注冊(cè)路由時(shí)定義的請(qǐng)求處理器也是該類(lèi)型。如下:
func (api *APIBuilder) Get(relativePath string, handlers ...context.Handler) *Route { return api.Handle(http.MethodGet, relativePath, handlers...) }
總結(jié):在iris框架上中間件也是一個(gè)請(qǐng)求處理器。通過(guò)Use函數(shù)使用中間件,實(shí)際上是將該中間件統(tǒng)一加入到了api.middleware切片中。該切片我們?cè)诤竺嬖偕钊胙芯?/strong>。
2.1.2 iris中自定義中間件
了解了中間件的類(lèi)型,我們就可以根據(jù)其規(guī)則來(lái)定義自己的中間件了。如下:
import "github.com/kataras/iris/v12/context" func CustomMiddleware(ctx *context.Context) { fmt.Println("this is the custom middleware") // 具體的處理邏輯 ctx.Next() }
當(dāng)然,為了代碼風(fēng)格統(tǒng)一,也可以類(lèi)似Recover中間件那樣定義個(gè)包,然后定義個(gè)New函數(shù),New函數(shù)返回的是一個(gè)中間件函數(shù),如下:
package CustomMiddleware func New() context.Handler { return func(ctx *context.Context) { fmt.Println("this is the custom middleware") // 具體的處理邏輯 ctx.Next() } }
到此為止,你有沒(méi)有發(fā)現(xiàn),無(wú)論是自定義的中間件,還是iris框架中已存在的中間件,在最后都有一行ctx.Next()代碼。那么,該為什么要有這行代碼呢? 通過(guò)函數(shù)名可以看到執(zhí)行下一個(gè)請(qǐng)求處理器。 再結(jié)合我們?cè)谑褂肬se函數(shù)使用中間件的時(shí)候,是把該中間件處理器加入到了一個(gè)切片中。所以,Next和請(qǐng)求處理器切片是有關(guān)系的。這個(gè)我們?cè)谙挛牡倪\(yùn)行機(jī)制部分詳細(xì)解釋。
2.2 gin中間件的實(shí)現(xiàn)
2.2.1 gin框架中間件類(lèi)型
同樣先查看gin的Use函數(shù)的簽名和實(shí)現(xiàn),如下:
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine }
在gin框架的Use函數(shù)中,middleware
也是一個(gè)不定長(zhǎng)的參數(shù),其參數(shù)類(lèi)型是HandlerFunc
。而HandlerFunc
的定義如下:
type HandlerFunc func(*Context)
同樣,在gin框架中注冊(cè)路由時(shí)指定的請(qǐng)求處理器的類(lèi)型也是HandlerFunc,即func(*Context)。我們?cè)倏碪se中的第2行代碼engine.RouterGroup.Use(middleware...)的實(shí)現(xiàn):
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() }
同樣,也是將中間件加入到了路由的Handlers切片中。
總結(jié):在gin框架中,中間件也是一個(gè)請(qǐng)求處理函數(shù)。通過(guò)Use函數(shù)使用中間件,實(shí)際上也是將該中間件統(tǒng)一加入到了group.Handlers切片中。
2.2.2 gin中自定義中間件
了解了gin的中間件類(lèi)型,我們就可以根據(jù)其規(guī)則來(lái)定義自己的中間件了。如下:
import "github.com/gin-gonic/gin" func CustomMiddleware(ctx *gin.Context) { fmt.Println("this is gin custom middleware") // 處理邏輯 ctx.Next() }
當(dāng)然,為了代碼風(fēng)格統(tǒng)一,也可以類(lèi)似Recover中間件那樣返回一個(gè),然后定義個(gè)New函數(shù),New函數(shù)返回的是一個(gè)中間件函數(shù),如下:
func CustomMiddleware() gin.HandlerFunc { return func(ctx *gin.Context) { fmt.Println("this is gin custom middleware") // 處理邏輯 ctx.Next() } }
同樣,在gin的中間件中,代碼的最后一行也是ctx.Next()
函數(shù)。如果不要這行代碼行不行呢?和iris的道理是一樣的,我們也在下文的運(yùn)行機(jī)制中講解。
2.3 echo框架中間件的實(shí)現(xiàn)
2.3.1 echo框架中間件類(lèi)型
func (e *Echo) Use(middleware ...MiddlewareFunc) { e.middleware = append(e.middleware, middleware...) }
在echo框架中,Use函數(shù)中的middleware參數(shù)也是一個(gè)不定長(zhǎng)參數(shù),說(shuō)明可以添加多個(gè)中間件。其類(lèi)型是MiddlewareFunc。如下是MiddewareFunc類(lèi)型的定義:
type MiddlewareFunc func(next HandlerFunc) HandlerFunc
這個(gè)中間件的函數(shù)類(lèi)型跟iris和gin的不一樣。該函數(shù)類(lèi)型接收一個(gè)HandlerFunc,并返回一個(gè)HanderFunc。而HanderFunc的定義如下:
HandlerFunc func(c Context) error
HanderFunc類(lèi)型才是指定路由時(shí)的請(qǐng)求處理器類(lèi)型。我們?cè)倏聪耬cho框架中Use的實(shí)現(xiàn),也是將middleware加入到了一個(gè)全局的切片中。
總結(jié):在echo框架中,中間件是一個(gè)輸入請(qǐng)求處理器,并返回一個(gè)新請(qǐng)求處理器的函數(shù)類(lèi)型。這是和iris和gin框架不一樣的地方。通過(guò)Use函數(shù)使用中間件,也是將該中間件統(tǒng)一加入到全局的中間件切片中。
2.3.2 echo中自定義中間件
了解了echo的中間件類(lèi)型,我們就可以根據(jù)其規(guī)則來(lái)定義自己的中間件了。如下:
import ( v4echo "github.com/labstack/echo/v4" ) func CustomMiddleware(next v4echo.HandlerFunc) v4echo.HandlerFunc { return func(c v4echo.Context) error { fmt.Println("this is echo custom middleware") // 中間件處理邏輯 return next(c) } }
這里中間件的實(shí)現(xiàn)看起來(lái)比較復(fù)雜,做下簡(jiǎn)單的解釋。根據(jù)上面可知,echo的中間件類(lèi)型是輸入一個(gè)請(qǐng)求處理器,然后返回一個(gè)新的請(qǐng)求處理器。在該函數(shù)中,從第6行到第10行該函數(shù)其實(shí)是中間件的執(zhí)行邏輯。第9行的next(c)實(shí)際上是要執(zhí)行下一個(gè)請(qǐng)求處理器的邏輯,類(lèi)似于iris和gin中的ctx.Next()函數(shù)。** 本質(zhì)上是用一個(gè)新的請(qǐng)求處理器(返回的請(qǐng)求處理器)包裝了一下舊的請(qǐng)求處理器(輸入的next請(qǐng)求處理器)**。
中間件的定義和使用都介紹了。那么,中間件和具體路由中的請(qǐng)求處理器是如何協(xié)同工作的呢?下面我們介紹中間件的運(yùn)行機(jī)制。
三、中間件的運(yùn)行機(jī)制
3.1 iris中間件的運(yùn)行機(jī)制
根據(jù)上文介紹,我們知道使用iris.Use函數(shù)之后,是將中間件加入到了APIBuilder結(jié)構(gòu)體的middleware切片中。那么,該middleware是如何和路由中的請(qǐng)求處理器相結(jié)合的呢?我們還是從注冊(cè)路由開(kāi)始看。
app.Get("/home",func(ctx *context.Context) { ctx.Write([]byte("Hello Wolrd")) })
使用Get函數(shù)指定一個(gè)路由。該函數(shù)的第二個(gè)參數(shù)就是對(duì)應(yīng)的請(qǐng)求處理器,我們稱(chēng)之為handler。然后,查看Get的源代碼,一直到APIBuilder.handle函數(shù),在該函數(shù)中有創(chuàng)建的路由的邏輯,如下:
routes := api.createRoutes(errorCode, []string{method}, relativePath, handlers...)
在api.createRoutes函數(shù)的入?yún)⒅?,我們只需關(guān)注handlers,該handlers即是在app.Get中傳遞的handler。繼續(xù)進(jìn)入api.createRoutes函數(shù)中,該函數(shù)是創(chuàng)建路由的邏輯。其實(shí)現(xiàn)如下:
func (api *APIBuilder) createRoutes(errorCode int, methods []string, relativePath string, handlers ...context.Handler) []*Route { //...省略代碼 var ( // global middleware to error handlers as well. beginHandlers = api.beginGlobalHandlers doneHandlers = api.doneGlobalHandlers ) if errorCode == 0 { beginHandlers = context.JoinHandlers(beginHandlers, api.middleware) doneHandlers = context.JoinHandlers(doneHandlers, api.doneHandlers) } else { beginHandlers = context.JoinHandlers(beginHandlers, api.middlewareErrorCode) } mainHandlers := context.Handlers(handlers) //...省略代碼 routeHandlers := context.JoinHandlers(beginHandlers, mainHandlers) // -> done handlers routeHandlers = context.JoinHandlers(routeHandlers, doneHandlers) //...省略代碼 routes := make([]*Route, len(methods)) // 構(gòu)建routes對(duì)應(yīng)的handler for i, m := range methods { // single, empty method for error handlers. route, err := NewRoute(api, errorCode, m, subdomain, path, routeHandlers, *api.macros) // ...省略代碼 routes[i] = route } return routes }
這里省略了大部分的代碼,只關(guān)注和中間件及對(duì)應(yīng)的請(qǐng)求處理器相關(guān)的邏輯。從實(shí)現(xiàn)上來(lái)看,可以得知:
- 首先看第12行,將全局的beginGlobalHandlers(即beginHandlers)和中間件api.middleware進(jìn)行合并。這里的api.middleware就是我們開(kāi)頭處使用Use函數(shù)加入的中間件。
- 再看第18行和22行,18行是將路由的請(qǐng)求處理器轉(zhuǎn)換成了切片 []Handler切片。這里的handlers就是使用Get函數(shù)進(jìn)行注冊(cè)的路由。22行是將beginHandlers和mainHandlers進(jìn)行合并,可以簡(jiǎn)單的認(rèn)為是將api.middlewares和路由注冊(cè)時(shí)的請(qǐng)求處理器進(jìn)行了合并。這里需要注意的是,通過(guò)合并請(qǐng)求處理器,中間件的處理器排在前面,具體的路由請(qǐng)求處理器排在了后面。
- 再看第24行,將合并后的請(qǐng)求處理器再和全局的doneHandlers進(jìn)行合并。這里可暫且認(rèn)為doneHandlers為空。
根據(jù)以上邏輯,對(duì)于一個(gè)具體的路由來(lái)說(shuō),其對(duì)應(yīng)的請(qǐng)求處理器不僅僅是自己指定的那個(gè),而是形成如下順序的一組請(qǐng)求處理器:
接下來(lái),我們?cè)倏丛诼酚善ヅ溥^(guò)程中,即匹配到了具體的路由后,這一組請(qǐng)求處理器是如何執(zhí)行的。
在iris中,路由匹配的過(guò)程是在文件的/iris/core/router/handler.go
文件中的routerHandler
結(jié)構(gòu)體的HandleRequest函數(shù)
中執(zhí)行的。如下:
func (h *routerHandler) HandleRequest(ctx *context.Context) { method := ctx.Method() path := ctx.Path() // 省略代碼... for i := range h.trees { t := h.trees[i] // 省略代碼... // 根據(jù)路徑匹配具體的路由 n := t.search(path, ctx.Params()) if n != nil { ctx.SetCurrentRoute(n.Route) // 這里是找到了路由,并執(zhí)行具體的請(qǐng)求邏輯 ctx.Do(n.Handlers) // found return } // not found or method not allowed. break } ctx.StatusCode(http.StatusNotFound) }
在匹配到路由后,會(huì)執(zhí)行該路由對(duì)應(yīng)的請(qǐng)求處理器n.Handlers,這里的Handlers就是上面提到的那組包含中間件的請(qǐng)求處理器數(shù)組。我們?cè)賮?lái)看ctx.Do函數(shù)的實(shí)現(xiàn):
func (ctx *Context) Do(handlers Handlers) { if len(handlers) == 0 { return } ctx.handlers = handlers handlers[0](ctx) }
這里看到在第7行中,首先執(zhí)行第1個(gè)請(qǐng)求處理器。到這里是不是有疑問(wèn):handlers既然是一個(gè)切片,那后面的請(qǐng)求處理器是如何執(zhí)行的呢?這里就涉及到在每個(gè)請(qǐng)求處理器中都有一個(gè)ctx.Next函數(shù)了。我們?cè)倏聪耤tx.Nex函數(shù)的實(shí)現(xiàn):
func (ctx *Context) Next() { // ...省略代碼 nextIndex, n := ctx.currentHandlerIndex+1, len(ctx.handlers) if nextIndex < n { ctx.currentHandlerIndex = nextIndex ctx.handlers[nextIndex](ctx) } }
這里我們看第11行到15行的代碼。在ctx
中有一個(gè)當(dāng)前執(zhí)行到哪個(gè)handler的下標(biāo)currentHandlerIndex
,如果還有未執(zhí)行完的hander
,則繼續(xù)執(zhí)行下一個(gè),即ctx.handlers[nextIndex](ctx)
。這也就是為什么在每個(gè)請(qǐng)求處理器中都應(yīng)該加一行ctx.Next的原因。如果不加改行代碼,則就執(zhí)行不到后續(xù)的請(qǐng)求處理器。
完整的執(zhí)行流程如下:
3.2 gin中間件運(yùn)行機(jī)制
由于gin和iris都是使用數(shù)組來(lái)存儲(chǔ)中間件,所以中間件運(yùn)行的機(jī)制本質(zhì)上是和iris一樣的。也是在注冊(cè)路由時(shí),將中間件的請(qǐng)求處理器和路由的請(qǐng)求處理器進(jìn)行合并后作為該路由的最終的請(qǐng)求處理器組。在匹配到路由后,也是通過(guò)先執(zhí)行請(qǐng)求處理器組的第一個(gè)處理器,然后調(diào)用ctx.Next()函數(shù)進(jìn)行迭代調(diào)用的。
但是,gin的請(qǐng)求處理器比較簡(jiǎn)單,只有中間件和路由指定的請(qǐng)求處理器組成。我們還是從路由注冊(cè)指定請(qǐng)求處理器開(kāi)始,如下
g.GET("/", func(ctx *gin.Context){ ctx.Writer.Write([]byte("Hello World")) })
進(jìn)入GET的源代碼,直到進(jìn)入到/gin/routergroup.go文件中的handle源碼,如下:
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() }
在該函數(shù)中我們可以看到第3行處是將group.combineHandlers(handlers),由名字可知是對(duì)請(qǐng)求處理器進(jìn)行組合。我們進(jìn)入繼續(xù)查看:
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) assert1(finalSize < int(abortIndex), "too many handlers") mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
在第5行,是先將group.Handlers即中間件加入到mergedHandlers,然后再第6行再將路由具體的handlers加入到mergedHandlers,最后將組合好的mergedHandlers作為該路由最終的handlers。如下:
接下來(lái),我們?cè)倏丛诼酚善ヅ溥^(guò)程中,即匹配到了具體的路由后,這一組請(qǐng)求處理器是如何執(zhí)行的。
在gin中,路由匹配的邏輯是在/gin/gin.go文件的Engine.handleHTTPRequest函數(shù)中,如下:
func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path // ...省略代碼 t := engine.trees for i, tl := 0, len(t); i < tl; i++ { // ...省略代碼 root := t[i].root // Find route in tree value := root.getValue(rPath, c.params, c.skippedNodes, unescape) //...省略代碼 if value.handlers != nil { c.handlers = value.handlers c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } // ...省略代碼 break } // ...省略代碼 }
匹配路由以及執(zhí)行對(duì)應(yīng)路由處理的邏輯是在第13行到18行。在第14行,首先將匹配到的路由的handlers(即中間件+具體的路由處理器)賦值給上下文c,然后執(zhí)行c.Next()函數(shù)。c.Next()函數(shù)如下:
func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }
在Next函數(shù)
中直接就是使用下標(biāo)c.index
進(jìn)行循環(huán)handlers
的執(zhí)行。這里需要注意的是c.index
是從-1開(kāi)始的。所以先進(jìn)行c.index++
則初始值就是0。整體執(zhí)行流程如下:
3.3 echo中間件的運(yùn)行機(jī)制
根據(jù)上文介紹,我們知道使用echo.Use函數(shù)來(lái)注冊(cè)中間件,注冊(cè)的中間件是放到了Echo結(jié)構(gòu)體的middleware切片中。那么,該middleware是如何和路由中的請(qǐng)求處理器相結(jié)合的呢?我們還是從注冊(cè)路由開(kāi)始看。
e.GET("/home", func(c v4echo.Context) error { c.Response().Write([]byte("Hello World")) return nil })
使用Get函數(shù)指定一個(gè)路由。該函數(shù)的第二個(gè)參數(shù)就是對(duì)應(yīng)的請(qǐng)求處理器,我們稱(chēng)之為handler。當(dāng)然,在該函數(shù)中還有第三個(gè)可選的參數(shù)是針對(duì)該路由的中間件的,其原理和全局的中間件是一樣的。
echo框架的中間件和路由的處理器結(jié)合并是在路由注冊(cè)的時(shí)候進(jìn)行的,而是在匹配到路由后才結(jié)合的。其邏輯是在Echo的ServeHTTP函數(shù)中,如下:
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Acquire context c := e.pool.Get().(*context) c.Reset(r, w) var h HandlerFunc if e.premiddleware == nil { e.findRouter(r.Host).Find(r.Method, GetPath(r), c) h = c.Handler() h = applyMiddleware(h, e.middleware...) } else { h = func(c Context) error { e.findRouter(r.Host).Find(r.Method, GetPath(r), c) h := c.Handler() h = applyMiddleware(h, e.middleware...) return h(c) } h = applyMiddleware(h, e.premiddleware...) } // Execute chain if err := h(c); err != nil { e.HTTPErrorHandler(err, c) } // Release context e.pool.Put(c) }
在該函數(shù)的第10行或第18行。我們接著看第10行中的applyMiddleware(h, e.middleware...)函數(shù)的實(shí)現(xiàn):
func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc { for i := len(middleware) - 1; i >= 0; i-- { h = middleware[i](h) } return h }
這里的h是注冊(cè)路由時(shí)指定的請(qǐng)求處理器。middelware就是使用Use函數(shù)注冊(cè)的所有的中間件。這里實(shí)際上循環(huán)對(duì)h進(jìn)行層層包裝。 索引i從middleware切片的最后一個(gè)元素開(kāi)始執(zhí)行,這樣就實(shí)現(xiàn)了先試用Use函數(shù)注冊(cè)的中間件先執(zhí)行。
這里的實(shí)現(xiàn)跟使用數(shù)組實(shí)現(xiàn)不太一樣。我們以使用Recover中間件為例看下具體的嵌套過(guò)程。
package main import ( v4echo "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) func main() { e := v4echo.New() // 通過(guò)use函數(shù)使用中間件Recover e.Use(middleware.Recover()) e.GET("/home", func(c v4echo.Context) error { c.Response().Write([]byte("Hello World")) return nil }) e.Start(":8080") }
這里的Recover中間件實(shí)際上是如下函數(shù):
func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if config.Skipper(c) { return next(c) } defer func() { // ...省略具體邏輯代碼 }() return next(c) } }
然后路由對(duì)應(yīng)的請(qǐng)求處理器我們假設(shè)是h:
func(c v4echo.Context) error { c.Response().Write([]byte("Hello World")) return nil }
那么,執(zhí)行applyMiddleware函數(shù),則結(jié)果執(zhí)行了Recover函數(shù),傳給Recover函數(shù)的next參數(shù)的值是h(即路由注冊(cè)的請(qǐng)求處理器),如下: 那么新的請(qǐng)求處理器就變成了如下:
func(c echo.Context) error { if config.Skipper(c) { return next(c) } defer func() { // ...省略具體邏輯代碼 }() return h(c) // 這里的h就是路由注冊(cè)的請(qǐng)求處理 }
你看,最終還是個(gè)請(qǐng)求處理器的類(lèi)型。這就是echo框架中間件的包裝原理:返回一個(gè)新的請(qǐng)求處理器,該處理器的邏輯是 中間件的邏輯 + 輸入的請(qǐng)求處理的邏輯。其實(shí)這個(gè)也是經(jīng)典的pipeline模式。如下:
四、總結(jié)
本文分析了gin、iris和echo主流框架的中間件的實(shí)現(xiàn)原理。其中g(shù)in和iris是通過(guò)遍歷切片的方式實(shí)現(xiàn)的,結(jié)構(gòu)也比較簡(jiǎn)單。而echo是通過(guò)pipeline模式實(shí)現(xiàn)的。相信通過(guò)本篇文章,你對(duì)中間件的運(yùn)行原理有了更深的理解。
以上就是深入了解Go語(yǔ)言中web框架的中間件運(yùn)行機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于Go語(yǔ)言web框架中間件運(yùn)行機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
goland中使用leetcode插件實(shí)現(xiàn)
本文主要介紹了goland中使用leetcode插件實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04詳解Gotorch多機(jī)定時(shí)任務(wù)管理系統(tǒng)
遵循著“學(xué)一門(mén)語(yǔ)言最好的方式是使用它”的理念,想著用Go來(lái)實(shí)現(xiàn)些什么,剛好有一個(gè)比較讓我煩惱的問(wèn)題,于是用Go解決一下,即使不在生產(chǎn)環(huán)境使用,也可以作為Go語(yǔ)言學(xué)習(xí)的一種方式。2021-05-05GoLang中的互斥鎖Mutex和讀寫(xiě)鎖RWMutex使用教程
RWMutex是一個(gè)讀/寫(xiě)互斥鎖,在某一時(shí)刻只能由任意數(shù)量的reader持有或者一個(gè)writer持有。也就是說(shuō),要么放行任意數(shù)量的reader,多個(gè)reader可以并行讀;要么放行一個(gè)writer,多個(gè)writer需要串行寫(xiě)2023-01-01GO的基礎(chǔ)知識(shí)掃盲注意事項(xiàng)
這篇文章主要介紹了GO的基礎(chǔ)知識(shí)注意事項(xiàng),本文是GO語(yǔ)言小白的掃盲文,主要講解了go語(yǔ)言的基本知識(shí),GO程序目錄結(jié)構(gòu),GO程序包的導(dǎo)入與別名運(yùn)用,GO內(nèi)置關(guān)鍵字,GO注釋方法需要的朋友可以參考下2022-12-12