1. 程式人生 > >Gin原始碼解析和例子——路由

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行啟動該路由器。如此整個服務就跑起來了。

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

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

        現在我們開始分析路由器是怎麼將請求和處理方法(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的呼叫。