Gin原始碼解析和例子——路由
Gin是一個基於golang的net包實現的網路框架。從ofollow,noindex" target="_blank">github 上,我們可以看到它相對於其他框架而言,具有優越的效能。本系列將從應用的角度來解析其原始碼。(轉載請指明出於breaksoftware的csdn部落格)
本文我們將分析其路由的原理。先看個例子(源於github)
func main() { // Disable Console Color // gin.DisableConsoleColor() // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := 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 HandlersChain basePath string engine*Engine rootbool }
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 { pathstring indicesstring children[]*node handlersHandlersChain priorityuint32 nTypenodeType maxParams uint8 wildChild bool } type methodTree struct { method string root*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 = req c.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 = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) } } func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if 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.Method path := c.Request.URL.Path …… // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree handlers, params, tsr := root.getValue(path, c.Params, unescape) if handlers != nil { c.handlers = handlers c.Params = params c.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 request c.Next() …… } }
是不是有點混亂?
其實不會出錯,因為Next方法沒有使用區域性變數去遍歷計數handlers的,它使用了和Context的成員變數index。這樣就可以保證某些情況下Next()函式不會觸發任何handler的呼叫。