[Gin]框架底层实现理解(二)

一.第一部分

对于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是最后的处理体 我们要将对应的urlabsolutepath添加入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,然后添加结点到字典树中进行压缩。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值