Gin源码解析和例子——路由
? ? ? ? Gin是一個基于golang的net包實現的網絡框架。從github上,我們可以看到它相對于其他框架而言,具有優越的性能。本系列將從應用的角度來解析其源碼。(轉載請指明出于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
}
? ? ? ? 可以說,這種寫法非常的優雅。第7行新建了一個路由器;第9~15行定義了路由規則;第19行啟動該路由器。如此整個服務就跑起來了。
? ? ? ? 我們將重心放在路由規則這段,可以很清晰的看到或者猜測到:
- 這兒看到的Get、Post、Put等都是Http的協議
- 向http://host/someGet發送Get請求將由getting方法處理
- 向http://host/somePost發送Post請求將由posting方法處理
- ……
? ? ? ? 現在我們開始分析路由器是怎么將請求和處理方法(handler)關聯起來的。
? ? ? ? 第7行創建的對象叫做路由器(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
}
? ? ? ? 關注下第5行,這兒有個中間件(midlleware)的概念。目前我們只要把它看成一個函數對象(也是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中間件加入到默認的中間件集合中。之后我們會看到針對每個需要被路由的請求,這些中間件對應的handler都會被調用。
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這些方式上來,其底層都是調用了路由集合(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行將該路徑對應的handlers和之前加入的中間件(Logger()和Recovery()返回的是一個匿名函數,即handler。之后我們會看到)的handlers合并;第10行將對absolutePath路徑Get請求對應的處理方法(handlers)加入到引擎的路由中。
? ? ? ? 我們看下combineHandlers的實現。它生成一個新的handler切片,然后先把中間件的handler插入到頭部,然后把用戶自定義處理某路徑下請求的handler插入到尾部。最后返回的是這個新生成的切片,而引擎中之前設置的中間件handlers(group.Handlers)并沒改變。所以針對每個需要被路由的請求,之前注冊的中間件對應的handler都會被調用。
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是一個多維切片。每個請求方法都有對應的一個methodTree,比如Get類型請求就只有一個methodTree與其對應。
? ? ? ? 每種請求方式(Get、Post等)又有很多路徑與其對應。每個路徑是一個node結構,該結構的handlers保存了如何處理該路徑下該請求方式的方法集合。
? ? ? ?所以第3~7行先嘗試獲取請求方式的結構體。沒找到就創建一個。最后在第8行將路徑和處理方法的對應關系加入到該請求方式結構之下。
type node struct {path stringindices stringchildren []*nodehandlers HandlersChainpriority uint32nType nodeTypemaxParams uint8wildChild bool
}type methodTree struct {method stringroot *node
}type methodTrees []methodTree
? ? ? ? 我們看到node結構下還有一個node的切片,這意味著這是一個遞歸結構。當然,我們通俗的稱為葉子節點可能更容易理解點。為什么會有葉子節點這個概念?舉個例子
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的父節點的path是/pin,/pin的父節點的path是/pi。如果我們再增加一個/pingabc,那么它的父節點path就是/ping。這些節點都有對應的handlers。
? ? ? ? 方式、路徑和處理函數方法的映射準備好后,我們再看看Gin是如何驅動它們運行的。這個時候我們就要看
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結構體,并且讓它實現了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方法中調用,serve會被Serve調用。在Serve中,我們看到接受請求和處理請求的邏輯了。Serve最終會在ListenAndServe中被調用,而它就是在引擎(Engine)的Run中被調用了的。這樣我們只要關注引擎(Engine)的handleHTTPRequest實現即可。
// 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方法會找到當前請求方式對應methodTree。然后找到路徑對應的處理方法
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方法,將驅動相應的處理函數執行
func (c *Context) Next() {c.index++for s := int8(len(c.handlers)); c.index < s; c.index++ {c.handlers[c.index](c)}
}
? ? ? ? 這兒我們注意下,處理函數的參數是Context指針!!調用Next是這個Context,然后handler處理的還是這些Context。比較反常的是,handler內部還可能調用該Context的Next方法!!!是不是感覺繞到一個循環里去了。我們回顧下之前中間件Logger
func Logger() HandlerFunc {return LoggerWithWriter(DefaultWriter)
}func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
……return func(c *Context) {
……// Process requestc.Next()
……}
}
? ? ? ? 是不是有點混亂?
? ? ? ? 其實不會出錯,因為Next方法沒有使用局部變量去遍歷計數handlers的,它使用了和Context的成員變量index。這樣就可以保證某些情況下Next()函數不會觸發任何handler的調用。
總結
以上是生活随笔為你收集整理的Gin源码解析和例子——路由的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Colly源码解析——结合例子分析底层实
- 下一篇: Gin源码解析和例子——中间件(midd