日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Gin源码解析和例子——路由

發(fā)布時間:2023/11/27 生活经验 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Gin源码解析和例子——路由 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

? ? ? ? Gin是一個基于golang的net包實現(xiàn)的網(wǎng)絡(luò)框架。從github上,我們可以看到它相對于其他框架而言,具有優(yōu)越的性能。本系列將從應(yīng)用的角度來解析其源碼。(轉(zhuǎn)載請指明出于breaksoftware的csdn博客)

? ? ? ? 本文我們將分析其路由的原理。先看個例子(源于github)

func main() {// Disable Console Color// gin.DisableConsoleColor()// Creates a gin router with default middleware:// logger and recovery (crash-free) middlewarerouter := gin.Default()router.GET("/someGet", getting)router.POST("/somePost", posting)router.PUT("/somePut", putting)router.DELETE("/someDelete", deleting)router.PATCH("/somePatch", patching)router.HEAD("/someHead", head)router.OPTIONS("/someOptions", options)// By default it serves on :8080 unless a// PORT environment variable was defined.router.Run()// router.Run(":3000") for a hard coded port
}

? ? ? ? 可以說,這種寫法非常的優(yōu)雅。第7行新建了一個路由器;第9~15行定義了路由規(guī)則;第19行啟動該路由器。如此整個服務(wù)就跑起來了。

? ? ? ? 我們將重心放在路由規(guī)則這段,可以很清晰的看到或者猜測到:

  1. 這兒看到的Get、Post、Put等都是Http的協(xié)議
  2. 向http://host/someGet發(fā)送Get請求將由getting方法處理
  3. 向http://host/somePost發(fā)送Post請求將由posting方法處理
  4. ……

? ? ? ? 現(xiàn)在我們開始分析路由器是怎么將請求和處理方法(handler)關(guān)聯(lián)起來的。

? ? ? ? 第7行創(chuàng)建的對象叫做路由器(router),但是其底層名稱卻是“引擎”(Engine)

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

? ? ? ? 關(guān)注下第5行,這兒有個中間件(midlleware)的概念。目前我們只要把它看成一個函數(shù)對象(也是handler)即可。

? ? ? ? 每個引擎(Engine)都有一個路由集合(RouterGroup)。每個路由集合都有一個默認中間件集合。

type Engine struct {RouterGroup……
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {Handlers HandlersChainbasePath stringengine   *Engineroot     bool
}

? ? ? ? Use方法就是將Logger和Recovery中間件加入到默認的中間件集合中。之后我們會看到針對每個需要被路由的請求,這些中間件對應(yīng)的handler都會被調(diào)用。

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {engine.RouterGroup.Use(middleware...)engine.rebuild404Handlers()engine.rebuild405Handlers()return engine
}func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {group.Handlers = append(group.Handlers, middleware...)return group.returnObj()
}

? ? ? ? 我們再回到GET、POST這些方式上來,其底層都是調(diào)用了路由集合(RouterGroup)的handle方法

router.GET("/someGet", getting)
……
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()
}

? ? ? ? 第8行通過相對路徑獲取絕對路徑;第9行將該路徑對應(yīng)的handlers和之前加入的中間件(Logger()和Recovery()返回的是一個匿名函數(shù),即handler。之后我們會看到)的handlers合并;第10行將對absolutePath路徑Get請求對應(yīng)的處理方法(handlers)加入到引擎的路由中。

? ? ? ? 我們看下combineHandlers的實現(xiàn)。它生成一個新的handler切片,然后先把中間件的handler插入到頭部,然后把用戶自定義處理某路徑下請求的handler插入到尾部。最后返回的是這個新生成的切片,而引擎中之前設(shè)置的中間件handlers(group.Handlers)并沒改變。所以針對每個需要被路由的請求,之前注冊的中間件對應(yīng)的handler都會被調(diào)用。

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
}

? ? ? ? 再看下addRoute干了什么

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {……root := engine.trees.get(method)if root == nil {root = new(node)engine.trees = append(engine.trees, methodTree{method: method, root: root})}root.addRoute(path, handlers)
}

? ? ? ? 引擎的trees是一個多維切片。每個請求方法都有對應(yīng)的一個methodTree,比如Get類型請求就只有一個methodTree與其對應(yīng)。

? ? ? ? 每種請求方式(Get、Post等)又有很多路徑與其對應(yīng)。每個路徑是一個node結(jié)構(gòu),該結(jié)構(gòu)的handlers保存了如何處理該路徑下該請求方式的方法集合。

? ? ? ?所以第3~7行先嘗試獲取請求方式的結(jié)構(gòu)體。沒找到就創(chuàng)建一個。最后在第8行將路徑和處理方法的對應(yīng)關(guān)系加入到該請求方式結(jié)構(gòu)之下。

type node struct {path      stringindices   stringchildren  []*nodehandlers  HandlersChainpriority  uint32nType     nodeTypemaxParams uint8wildChild bool
}type methodTree struct {method stringroot   *node
}type methodTrees []methodTree

? ? ? ? 我們看到node結(jié)構(gòu)下還有一個node的切片,這意味著這是一個遞歸結(jié)構(gòu)。當然,我們通俗的稱為葉子節(jié)點可能更容易理解點。為什么會有葉子節(jié)點這個概念?舉個例子

	r.GET("/pi", func(c *gin.Context) {c.String(http.StatusOK, "po")})r.GET("/pin", func(c *gin.Context) {c.String(http.StatusOK, "pon")})r.GET("/ping", func(c *gin.Context) {c.String(http.StatusOK, "pong")})

? ? ? ? /ping的父節(jié)點的path是/pin,/pin的父節(jié)點的path是/pi。如果我們再增加一個/pingabc,那么它的父節(jié)點path就是/ping。這些節(jié)點都有對應(yīng)的handlers。

? ? ? ? 方式、路徑和處理函數(shù)方法的映射準備好后,我們再看看Gin是如何驅(qū)動它們運行的。這個時候我們就要看

func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)err = http.ListenAndServe(address, engine)return
}

? ? ? ? Gin的底層使用了net/http包。只是它封裝了Engine結(jié)構(gòu)體,并且讓它實現(xiàn)了Handler接口

type Handler interface {ServeHTTP(ResponseWriter, *Request)
}// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context)c.writermem.reset(w)c.Request = reqc.reset()engine.handleHTTPRequest(c)engine.pool.Put(c)
}

? ? ? ??ServeHTTP方法會在serve方法中調(diào)用,serve會被Serve調(diào)用。在Serve中,我們看到接受請求和處理請求的邏輯了。Serve最終會在ListenAndServe中被調(diào)用,而它就是在引擎(Engine)的Run中被調(diào)用了的。這樣我們只要關(guān)注引擎(Engine)的handleHTTPRequest實現(xiàn)即可。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
……serverHandler{c.server}.ServeHTTP(w, w.req)
……
}func (srv *Server) Serve(l net.Listener) error {
……for {rw, e := l.Accept()
……tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx)}
}func (srv *Server) ListenAndServe() error {if srv.shuttingDown() {return ErrServerClosed}addr := srv.Addrif addr == "" {addr = ":http"}ln, err := net.Listen("tcp", addr)if err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

? ? ? ??handleHTTPRequest方法會找到當前請求方式對應(yīng)methodTree。然后找到路徑對應(yīng)的處理方法

func (engine *Engine) handleHTTPRequest(c *Context) {httpMethod := c.Request.Methodpath := c.Request.URL.Path
……// Find root of the tree for the given HTTP methodt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// Find route in treehandlers, params, tsr := root.getValue(path, c.Params, unescape)if handlers != nil {c.handlers = handlersc.Params = paramsc.Next()c.writermem.WriteHeaderNow()return}
……

? ? ? ? 第17行Next方法,將驅(qū)動相應(yīng)的處理函數(shù)執(zhí)行

func (c *Context) Next() {c.index++for s := int8(len(c.handlers)); c.index < s; c.index++ {c.handlers[c.index](c)}
}

? ? ? ? 這兒我們注意下,處理函數(shù)的參數(shù)是Context指針!!調(diào)用Next是這個Context,然后handler處理的還是這些Context。比較反常的是,handler內(nèi)部還可能調(diào)用該Context的Next方法!!!是不是感覺繞到一個循環(huán)里去了。我們回顧下之前中間件Logger

func Logger() HandlerFunc {return LoggerWithWriter(DefaultWriter)
}func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
……return func(c *Context) {
……// Process requestc.Next()
……}
}

? ? ? ? 是不是有點混亂?

? ? ? ? 其實不會出錯,因為Next方法沒有使用局部變量去遍歷計數(shù)handlers的,它使用了和Context的成員變量index。這樣就可以保證某些情況下Next()函數(shù)不會觸發(fā)任何handler的調(diào)用。

總結(jié)

以上是生活随笔為你收集整理的Gin源码解析和例子——路由的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。