go

gin

gin 路由注册分析

Posted by Liangjf on July 20, 2020

gin 路由注册分析

源码分析

重要结构体

Engine是gin的“引擎”, 负责整个路由的工作

type Engine struct {
	//配置管理路由器
	RouterGroup

	...
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	//路由树, 存放url和handle
	trees            methodTrees
}

RouterGroup实现接口, 负责管理和配置路由

type IRoutes interface {
	Use(...HandlerFunc) IRoutes

	Handle(string, string, ...HandlerFunc) IRoutes
	Any(string, ...HandlerFunc) IRoutes
	GET(string, ...HandlerFunc) IRoutes
	POST(string, ...HandlerFunc) IRoutes
	DELETE(string, ...HandlerFunc) IRoutes
	PATCH(string, ...HandlerFunc) IRoutes
	PUT(string, ...HandlerFunc) IRoutes
	OPTIONS(string, ...HandlerFunc) IRoutes
	HEAD(string, ...HandlerFunc) IRoutes

	StaticFile(string, string) IRoutes
	Static(string, string) IRoutes
	StaticFS(string, http.FileSystem) IRoutes
}

比如注意路由如下:

g.POST("/v1/push", controllers.PushMsg)

POST方法注册到路由, HandlerFunc接口 func(*gin.Context)

func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodPost, relativePath, handlers)
}

返回IRoutes, 返回接口, 是为了 多次链式调用

注册到路由树tree

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	//调整url路径, 若有group拼接
	absolutePath := group.calculateAbsolutePath(relativePath)
	//聚合所有handle(真正的响应handle在所有注册中间件后面), 链式handle最大层次62
	handlers = group.combineHandlers(handlers)
	//添加到gin engine 的路由树trees
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj() }

添加路由方法到路由树

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	//判断格式
	assert1(path[0] == '/', "path must begin with '/'")
	assert1(method != "", "HTTP method can not be empty")
	assert1(len(handlers) > 0, "there must be at least one handler")

	debugPrintRoute(method, path, handlers)
	//获取or创建POST的根
	root := engine.trees.get(method)
	if root == nil {
		root = new(node)
		root.fullPath = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	//添加
	root.addRoute(path, handlers)
}

添加路由方法

func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
	n.priority++
	numParams := countParams(path)

	//空树,直接插入子节点
	if len(n.path) == 0 && len(n.children) == 0 {
		n.insertChild(numParams, path, fullPath, handlers)
		n.nType = root
		return
	}
	
walk:
	...
	//路由树的构建
	//最长公共子串, 子节点, 插入...
}

总结

Go/src/net/http/server.go:1760

func (c *conn) serve(ctx context.Context) {
	...
	//重点逻辑, 接口方式多态
	serverHandler{c.server}.ServeHTTP(w, w.req)
	...
}

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

基本所有的go web框架(gin, beego等)都是基于原生http包来构建的, 它们主要是自己实现了路由管理.

这其中的关键是, 实现http包的Handler, 然后传入自定义路由到此, http.ListenAndServe(":8080", g),

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
}

这样在http请求到来时就会在serve()函数中根据是否自定义路由来选择自定义路由来做路由查找的逻辑处理