首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gin源码解析和例子——路由

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

作者头像
方亮
发布2019-01-16 16:41:45
1.3K0
发布2019-01-16 16:41:45
举报
文章被收录于专栏:方亮方亮方亮

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年11月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档