一.第一部分
对于gin框架的路由存储和查询的底层机制有一定了解后,我们开始进行源码的解析
1.gin.Default() ;Gin默认引擎
返回一个engine结构体对象,用来操作gin的各种函数
// 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
}
type Engine struct {
RouterGroup //继承·路由群·结构体
RedirectTrailingSlash bool
RedirectFixedPath bool
HandleMethodNotAllowed bool
ForwardedByClientIP bool
AppEngine bool
UseRawPath bool
UnescapePathValues bool
RemoveExtraSlash bool
RemoteIPHeaders []string
TrustedPlatform string
MaxMultipartMemory int64
UseH2C bool
ContextWithFallback bool
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool //用来存取context context后续文章会讲解,应该近几天我会写出来golangContext方面的blog
trees methodTrees //gin的路由时基于这个实现的
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
最核心的就是其中的 RouterGroup
这个后边会讲到,留个印象先
2.engine.GET
engine.GET("/gettest", helloController)
engine.POST("/gettest", helloController)
这个engine的GET,POST方法用来处理url和handler函数
进入GET方法实现这个功能的语句只有group.handler
这个事实上是routerGroup的实现的方法,但是engine继承了routerGroup,所以engine也能调用
传参分别是请求类型,相对路径(也就是我们请求的相对url),为什么是相对,因为这个是可以嵌套的,用过gin的都知道有个engine.Group方法可用来分组,可以提取公共url前缀,区域化代码块。
RouteGroup 是非常重要的功能,举个例子:一个完整的 server 服务,url 需要分为鉴权接口和非鉴权接口,就可以使用 RouteGroup 来实现。其实最常用的,还是用来区分接口的版本升级。这些操作, 最终都会在反应到gin的路由树上
第三个参数就是handler处理函数。
而这三个参数都是会存储在routerGroup中的,包括绝对路径,相同前缀,basepath加上relativepath得到absolutepath绝对路径。
// 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
}
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
3.group.handle(httpMethod, relativePath, handlers)IRoutes
进入这个函数后 首先会计算出绝对路径,然后将routerGroup的中间件和传入的处理函数merge成一个新的切片返回
(用的是copy方法,先是copy routerGoup的handler,然后才是我们传入的函数,因为这个顺序很重要,后续执行就是按照这个顺序执行的,这也体现了gin的中间件的结构特点),类型就是Handlerchain
得到这两个数据之后再进行addRoute方法,这个方法是重点,下一点会讲。
我们先看看计算绝对路径和整合handler的函数实现,这个basepath如果在分组中用户没有自己添加的话就是'/',这个上面也讲过了,也就是分组。
得到最后的handlers,在每个group内部是有一个engine的,一般都是隶属同一个engine,具体看上面的结构体实现,所以每个group最后通过那个engine找回引擎,最后的处理还是通过ServerHttp,engine是最后的处理体 我们要将对应的url
和absolutepath
添加入engine 这个也是非常非常核心的一个地方
func joinPaths(absolutePath, relativePath string) string {
if relativePath == "" {
return absolutePath
}
finalPath := path.Join(absolutePath, relativePath)
if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
return finalPath + "/"
}
return finalPath
}
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
}
-----------------------------
// HandlersChain defines a HandlerFunc slice.
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
}
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
--------------------------------
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()
}
4.group.engine.addRoute(httpMethod, absolutePath, handlers)(上)
前面三个 assert断言 绝对路径 请求方式不能为空 至少存在一个handlerfunc
然后就是那个核心的字典树了,root:=engine.trees.get(method)根据请求方法获取不同的树的根节点,如果没有就会创建一个结点,存在的话再去寻找对应的结点
root := engine.trees.get(method)
type Engine struct {
RouterGroup
...
...
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool
trees methodTrees
}
type methodTrees []methodTree
type methodTree struct {
method string
root *node
}
func (trees methodTrees) get(method string) *node {
for _, tree := range trees {
if tree.method == method {
return tree.root
}
}
return nil
}
------------
-----
//前三个断言 绝对路径 请求方式不能为空,handler至少有一个
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)
// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
4.root.addRoute(path, handlers)(中)
这个是上一个函数中核心的函数,简单解析一下
其中有一句n.priority++,这个就是权重。然后对于空树而言,一开始一开始i就是从根节点开始插入子孙结点,也就是insertchild函数,添加路由加绝对路径,加处理函数,然后我们再次回到group.engine.addRoute
root.addRoute(path, handlers)
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
}
--------
func (n *node) addRoute(path string, handlers HandlersChain) {
fullPath := path
n.priority++
// Empty tree
if len(n.path) == 0 && len(n.children) == 0 {
n.insertChild(path, fullPath, handlers)
n.nType = root
return
}
.............
}
------
func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
// 处理wildcard *通配符匹配 感兴趣可以去看看源码这里省略
....
....
// If no wildcard was found, simply insert the path and handle
n.path = path
n.handlers = handlers
n.fullPath = fullPath
}
5.group.engine.addRoute(httpMethod, absolutePath, handlers)(下)
一共执行两个操作,合并和切分,也就是我们前言讲的,这边就是字典树的压缩操作了。这边简单附上大致的代码,代码中有我自己的解释,可以简单看看
walk:
for {
// Find the longest common prefix. 最长公共前缀
// This also implies that the common prefix contains no ':' or '*'
// since the existing key can't contain those chars.
i := longestCommonPrefix(path, n.path)
//
// Split edge
if i < len(n.path) {//公共前缀小于结点路径长度-->中介节点
child := node{//切分结点,事实上就是将结点n下沉
path: n.path[i:],
wildChild: n.wildChild,
nType: static,
indices: n.indices,
children: n.children,
handlers: n.handlers,
priority: n.priority - 1,//权重减一
fullPath: n.fullPath,
}
n.children = []*node{&child}//开辟空间的同时插入child
// []byte for proper unicode char conversion, see #65
n.indices = bytesconv.BytesToString([]byte{n.path[i]})//n变成了n的子结点,所以这边子结点索引添加了n原来结点的path首字符
n.path = path[:i]//做完上个操作就可修改这个结点path的值,将公共前缀赋值到结点
n.handlers = nil//因为是中介结点,所以handler为空
n.wildChild = false//是否模糊匹配
n.fullPath = fullPath[:parentFullPathIndex+i]//绝对路径 这个parentFullPathIndex就是绝对路径到达其父节点尾巴的索引,加上i就是绝对路径到公共前缀的url
}
// Make new node a child of this node
if i < len(path) {//上面的操作父节点操作完了,这个操作就是将传入的path作为child进行下沉
path = path[i:]//截取后缀
c := path[0]//提取首字符索引
// '/' after param
if n.nType == param && c == '/' && len(n.children) == 1 {
parentFullPathIndex += len(n.path)
n = n.children[0]
n.priority++
continue walk
}
// Check if a child with the next path byte exists
for i, max := 0, len(n.indices); i < max; i++ {
if c == n.indices[i] {
parentFullPathIndex += len(n.path)
i = n.incrementChildPrio(i)
n = n.children[i]
continue walk
}
}
// Otherwise insert it
if c != ':' && c != '*' && n.nType != catchAll {
// []byte for proper unicode char conversion, see #65
n.indices += bytesconv.BytesToString([]byte{c})
child := &node{
fullPath: fullPath,
}
n.addChild(child)
n.incrementChildPrio(len(n.indices) - 1)
n = child
} else if n.wildChild {
// inserting a wildcard node, need to check if it conflicts with the existing wildcard
n = n.children[len(n.children)-1]
n.priority++
// Check if the wildcard matches
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
// Adding a child to a catchAll is not possible
n.nType != catchAll &&
// Check for longer wildcard, e.g. :name and :names
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
continue walk
}
// Wildcard conflict
pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
}
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in existing prefix '" + prefix +
"'")
}
n.insertChild(path, fullPath, handlers)
return
}
// Otherwise add handle to current node
if n.handlers != nil {
panic("handlers are already registered for path '" + fullPath + "'")
}
n.handlers = handlers
n.fullPath = fullPath
return
}
}
总结总结:
GET--->engine.group.handle --->group.engine.addRoute--->root.addRoute ---->group.engin.addRoute
就是engine通过封装的routerGroup进行分组,储存中间件,合并路径和handler,然后添加结点到字典树中进行压缩。