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) 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
root 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 string
indices string
children []*node
handlers HandlersChain
priority uint32
nType nodeType
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的呼叫。