源码分析
下面是Gin框架中登记GET、POST路由的函数实现。
// POST is a shortcut for router.Handle("POST", path, handlers).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPost, relativePath, handlers)
}
// GET is a shortcut for router.Handle("GET", path, handlers).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
使用RouterGroup类型的对象,该函数将调用的方法类型(POST、GET、PUT等)、请求路径以及其他处理函数传入handle。返回值为IRoutes类型,这是一个接口,实现了POST、GET、PUT等方法。
首先我们来看RouterGroup类型对象的生成函数。使用了一个RouterGroup生成了另一个子RouterGroup,生成时绑定了新生成的组在当前组路径上的附加路径。比如,我使用/v1组生成一个/api组,那么我访问该组路由的路径即为/v1/api/xxx。然后,还在当前组的基础上,绑定了一些处理函数(即中间件),并将之前RouterGroup与新的RouterGroup的处理函数拼接在一起。对应使用的函数下文会进行分析。
// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
接着,让我们来看处理各种路由方法的handle函数。
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()
}
其中第一个函数用于给当前传入的路径加上所在组的路径,比如当前RouterGroup为api,请求路径为/login,那么最后拼接得到的路径即为/api/login。
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
}
// 非源码,为创建路由组的例子
apiGroup := router.Group("/api")
然后,看combineHandlers函数,传入的是一系列处理函数,返回的是HandlerChain,即一个处理函数数组。
这一步是将当前传入的handler函数与该组包含的handler函数结合,形成一个新的handler函数序列。
// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
接着,handle运行了Engine下的addRoute函数。
参数分别为:
● method:请求的方法(POST、GET、PUT、DELETE等)
● path:路由的完整路径,拼接了所有父group的路径值
● handlers:路由的处理函数,拼接了所有父group的处理函数
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)
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)
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
首先会使用assert1,判断三个条件是否满足,如不满足,则发出panic。
接着,判断当前引擎中,是否记录了method类型的方法。如果没有,则新开一个如下的结构体,专门用于记录当前引擎下存放的这类型方法。
type methodTree struct {
method string
root *node
}
type node struct {
path string
indices string
wildChild bool
nType nodeType
priority uint32
children []*node // child nodes, at most 1 :param style node at the end of the array
handlers HandlersChain
fullPath string
}
如果检测到当前引擎之前记录过这一类型的方法,则必将存在对应的methodTree,返回对应的node指针。
接着。执行func (n *node) addRoute(path string, handlers HandlersChain) 方法将当前路由的路径和处理函数添加到engine对应的methodTree中。addRoute函数比较复杂,有100多行,这里就不放了。感兴趣的可以前往gin包下的tree.go文件中查找阅读。值得注意的是文件的165行那个walk,相当于c++里的goto,并不是什么关键字,而是一个标签。
最后调用group.returnObj(),如下所示,返回当前的group对象。group.root是一个bool值,只有在当前engine为使用New()方法创建时才为true。也就是除了根路由组,其他子路由组的root值都为false。
func (group *RouterGroup) returnObj() IRoutes {
if group.root {
return group.engine
}
return group
}
总结
Gin在添加一个路由方法(GET、POST、PUT)时,首先结合路由所在组,获取绝对路径以及需要执行的所有函数。然后将这个路由的相关信息添加到engine并存储入一个node结构体中。