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

Posted breaksoftware

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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, methodTreemethod: 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) 
……
		serverHandlerc.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(tcpKeepAliveListenerln.(*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的调用。

以上是关于Gin源码解析和例子——路由的主要内容,如果未能解决你的问题,请参考以下文章

Gin源码解析和例子——中间件(middleware)

Gin源码解析和例子——中间件(middleware)

Gin 路由解析树详解

Golang 框架 gin运行源码分析

gin-session使用以及源码分析

Gin 的启动过程、路由及上下文源码解读