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

breaksoftware · · 636 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/breaksoftware/article/details/84667656

        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的调用。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:breaksoftware

查看原文:Gin源码解析和例子——路由

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

636 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传