02Gin源码解读

作者: 刷漆猫咪 | 来源:发表于2019-12-04 11:51 被阅读0次

    简介

    Gin 源码解读, 基于 v1.5.0 版本.

    HttpRouter 实现

    添加路由主要是由 addRoute 完成:

    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)
    }
    

    Gin 的路由是通过 httprouter 实现的, 来深入了解下它的源代码.

    数据结构

    github 的文档解释了实现原理, 具体可以参考 How does it work?.

    HttpRouter 内部使用了 Radix 树, 是前缀树的紧凑版变种.

    radix_tree.png

    上图来自维基百科, 显示了 Radix 树的结构. 相比普通前缀树, Radix 树的边上能存储多个字符, 极大的压缩了树的深度.

    看一下数据结构的定义:

    // Param is a single URL parameter, consisting of a key and a value.
    type Param struct {
        Key   string
        Value string
    }
    
    // Params is a Param-slice, as returned by the router.
    // The slice is ordered, the first URL parameter is also the first slice value.
    // It is therefore safe to read values by the index.
    type Params []Param
    
    type methodTree struct {
        method string
        root   *node
    }
    
    type methodTrees []methodTree
    

    Engine.trees 的类型就是 methodTrees, 初始化语句是 trees: make(methodTrees, 0, 9),.

    func (trees methodTrees) get(method string) *node {
        for _, tree := range trees {
            if tree.method == method {
                return tree.root
            }
        }
        return nil
    }
    

    前面添加路由的代码中第一步是找到 root, 即 root := engine.trees.get(method), 结合 get 代码,
    我们可以发现 methodTrees 实际上根据 HTTP 方法分类的, 每种方法都对应一颗树.

    如果当前该类型的 HTTP 方法不存在, 就新建一棵树 methodTree:

    root = new(node)
    root.fullPath = "/"
    engine.trees = append(engine.trees, methodTree{method: method, root: root})
    

    再看一下树的节点是如何定义的:

    type nodeType uint8
    
    const (
        static nodeType = iota // default
        root
        param
        catchAll
    )
    
    type node struct {
        path      string
        indices   string
        children  []*node
        handlers  HandlersChain
        priority  uint32
        nType     nodeType
        maxParams uint8
        wildChild bool
        fullPath  string
    }
    

    添加路由

    数据结构已经了解了, 看一下路由到底是如何添加的, 即 root.addRoute(path, handlers).

    // addRoute adds a node with the given handle to the path.
    // Not concurrency-safe!
    func (n *node) addRoute(path string, handlers HandlersChain) {
        fullPath := path
        n.priority++
        numParams := countParams(path)
    
        parentFullPathIndex := 0
    
        // non-empty tree
        if len(n.path) > 0 || len(n.children) > 0 {
        walk:
            for {
                // Update maxParams of the current node
                if numParams > n.maxParams {
                    n.maxParams = numParams
                }
    
                // 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 := 0
                max := min(len(path), len(n.path))
                for i < max && path[i] == n.path[i] {
                    i++
                }
    
                // Split edge
                if i < len(n.path) {
                    child := node{
                        path:      n.path[i:],
                        wildChild: n.wildChild,
                        indices:   n.indices,
                        children:  n.children,
                        handlers:  n.handlers,
                        priority:  n.priority - 1,
                        fullPath:  n.fullPath,
                    }
    
                    // Update maxParams (max of all children)
                    for i := range child.children {
                        if child.children[i].maxParams > child.maxParams {
                            child.maxParams = child.children[i].maxParams
                        }
                    }
    
                    n.children = []*node{&child}
                    // []byte for proper unicode char conversion, see #65
                    n.indices = string([]byte{n.path[i]})
                    n.path = path[:i]
                    n.handlers = nil
                    n.wildChild = false
                    n.fullPath = fullPath[:parentFullPathIndex+i]
                }
    
                // Make new node a child of this node
                if i < len(path) {
                    path = path[i:]
    
                    if n.wildChild {
                        parentFullPathIndex += len(n.path)
                        n = n.children[0]
                        n.priority++
    
                        // Update maxParams of the child node
                        if numParams > n.maxParams {
                            n.maxParams = numParams
                        }
                        numParams--
    
                        // Check if the wildcard matches
                        if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
                            // check for longer wildcard, e.g. :name and :names
                            if len(n.path) >= len(path) || path[len(n.path)] == '/' {
                                continue walk
                            }
                        }
    
                        pathSeg := path
                        if n.nType != catchAll {
                            pathSeg = strings.SplitN(path, "/", 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 +
                            "'")
                    }
    
                    c := path[0]
    
                    // slash 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 := 0; i < len(n.indices); 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 != '*' {
                        // []byte for proper unicode char conversion, see #65
                        n.indices += string([]byte{c})
                        child := &node{
                            maxParams: numParams,
                            fullPath:  fullPath,
                        }
                        n.children = append(n.children, child)
                        n.incrementChildPrio(len(n.indices) - 1)
                        n = child
                    }
                    n.insertChild(numParams, path, fullPath, handlers)
                    return
    
                } else if i == len(path) { // Make node a (in-path) leaf
                    if n.handlers != nil {
                        panic("handlers are already registered for path '" + fullPath + "'")
                    }
                    n.handlers = handlers
                }
                return
            }
        } else { // Empty tree
            n.insertChild(numParams, path, fullPath, handlers)
            n.nType = root
        }
    }
    

    addRoute

    代码有点长, 先根据 if 语句分为两种情况, 一种是初始化的时候(即树是空的), 另一种是树是非空的.

    n.insertChild(numParams, path, fullPath, handlers)
    n.nType = root
    

    树是空的情况下, 即 n.path 是空字符串(初始值) 且 n.children 是空切片.
    这个时候, 只是通过 insertChild 插入节点, 然后将节点的类型设置为 root.
    insertChild 的代码也有点长, 等下再来看.

    当树是非空的, 进入到了一个 for 循环中, 先跟着注释看一下 for 大体上是做什么的.

    // Update maxParams of the current node
    if numParams > n.maxParams {
      n.maxParams = numParams
    }
    
    // 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 := 0
    max := min(len(path), len(n.path))
    for i < max && path[i] == n.path[i] {
      i++
    }
    
    // Split edge
    
    // Make new node a child of this node
    

    前面两个步骤, 更新 maxParams 和计算最长前缀的长度, 非常简单, 直接看代码就行.

    看一下节点是如何分裂的, 即第三步:

    // Split edge
    if i < len(n.path) {
      child := node{
        path:      n.path[i:],
        wildChild: n.wildChild,
        indices:   n.indices,
        children:  n.children,
        handlers:  n.handlers,
        priority:  n.priority - 1,
        fullPath:  n.fullPath,
      }
    
      // Update maxParams (max of all children)
      for i := range child.children {
        if child.children[i].maxParams > child.maxParams {
          child.maxParams = child.children[i].maxParams
        }
      }
    
      n.children = []*node{&child}
      // []byte for proper unicode char conversion, see #65
      n.indices = string([]byte{n.path[i]})
      n.path = path[:i]
      n.handlers = nil
      n.wildChild = false
      n.fullPath = fullPath[:parentFullPathIndex+i]
    }
    

    当公共前缀的长度小于 n.path 时, 当前节点就会分裂出一个子节点.

    比如, 当前节点 node.path = "/ping", 遇到 path = "/pong" 时就会分裂,
    公共前缀的长度 i=2, 因此节点会分裂为 node.path = "/p"node.path = "ing".
    分裂出来的后一个节点会占据当前节点的大部分属性.

    接着看第四步, 如何为当前节点添加一个子节点, 是 root.addRoute(path, handlers) 的核心代码.

    这也是一个 if 判断, 让我们先看一下后半部分, 即可能出错时的情况:

    else if i == len(path) { // Make node a (in-path) leaf
      if n.handlers != nil {
        panic("handlers are already registered for path '" + fullPath + "'")
      }
      n.handlers = handlers
    }
    

    如果 handlers 不为空, 就会发生错误, 这说明 handlers 只被允许注册一次.

    看一下 if 的前半部分, 即 if i < len(path) 时的情况:

    // Make new node a child of this node
    if i < len(path) {
      path = path[i:]
    
      if n.wildChild {
        parentFullPathIndex += len(n.path)
        n = n.children[0]
        n.priority++
    
        // Update maxParams of the child node
        if numParams > n.maxParams {
          n.maxParams = numParams
        }
        numParams--
    
        // Check if the wildcard matches
        if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
          // check for longer wildcard, e.g. :name and :names
          if len(n.path) >= len(path) || path[len(n.path)] == '/' {
            continue walk
          }
        }
    
        pathSeg := path
        if n.nType != catchAll {
          pathSeg = strings.SplitN(path, "/", 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 +
          "'")
      }
    
      c := path[0]
    
      // slash 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 := 0; i < len(n.indices); 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 != '*' {
        // []byte for proper unicode char conversion, see #65
        n.indices += string([]byte{c})
        child := &node{
          maxParams: numParams,
          fullPath:  fullPath,
        }
        n.children = append(n.children, child)
        n.incrementChildPrio(len(n.indices) - 1)
        n = child
      }
      n.insertChild(numParams, path, fullPath, handlers)
      return
    
    }
    

    这一部分也是有点长, 也需要一步步拆解来看.

    首先根据 path = path[i:], 发现 path 已经去除了公共前缀部分了.

    先看一下第一个判断, if n.wildChild, 即存在通配符子节点:

    if n.wildChild {
      parentFullPathIndex += len(n.path)
      n = n.children[0]
      n.priority++
    
      // Update maxParams of the child node
      if numParams > n.maxParams {
        n.maxParams = numParams
      }
      numParams--
    
      // Check if the wildcard matches
      if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
        // check for longer wildcard, e.g. :name and :names
        if len(n.path) >= len(path) || path[len(n.path)] == '/' {
          continue walk
        }
      }
    
      pathSeg := path
      if n.nType != catchAll {
        pathSeg = strings.SplitN(path, "/", 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 +
        "'")
    }
    

    通配符的判断中, 一般都是触发通配符冲突错误的, 除非前面通配符部分一样, 后面有 /.

    c := path[0]
    
    // slash after param
    if n.nType == param && c == '/' && len(n.children) == 1 {
      parentFullPathIndex += len(n.path)
      n = n.children[0]
      n.priority++
      continue walk
    }
    

    当节点是 : 通配符且 path 开头为 / 后, 进入到新一轮的循环中.

    // Check if a child with the next path byte exists
    for i := 0; i < len(n.indices); i++ {
      if c == n.indices[i] {
        parentFullPathIndex += len(n.path)
        i = n.incrementChildPrio(i)
        n = n.children[i]
        continue walk
      }
    }
    

    检查是否存在一个孩子节点, 如果有的话就直接跳到那个节点, 然后进入新一轮的循环中.
    前面节点分裂的时候, 设置了 n.indices = string([]byte{n.path[i]}).

    // Otherwise insert it
    if c != ':' && c != '*' {
      // []byte for proper unicode char conversion, see #65
      n.indices += string([]byte{c})
      child := &node{
        maxParams: numParams,
        fullPath:  fullPath,
      }
      n.children = append(n.children, child)
      n.incrementChildPrio(len(n.indices) - 1)
      n = child
    }
    

    经过了前面的判断之后, 走到这里, 如果 c 不是 :*, 就会插入一个节点, 并替换当前节点为这个节点.

    n.insertChild(numParams, path, fullPath, handlers)
    return
    

    最后依旧是调用 insertChild. 然后终于可以使用 return 跳出循环, 结束整个方法了.

    insertChild

    上面在两个地方调用了 n.insertChild(numParams, path, fullPath, handlers), 看一下它的实现.

    func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) {
        var offset int // already handled bytes of the path
    
        // find prefix until first wildcard (beginning with ':' or '*')
        for i, max := 0, len(path); numParams > 0; i++ {
            c := path[i]
            if c != ':' && c != '*' {
                continue
            }
    
            // find wildcard end (either '/' or path end)
            end := i + 1
            for end < max && path[end] != '/' {
                switch path[end] {
                // the wildcard name must not contain ':' and '*'
                case ':', '*':
                    panic("only one wildcard per path segment is allowed, has: '" +
                        path[i:] + "' in path '" + fullPath + "'")
                default:
                    end++
                }
            }
    
            // check if this Node existing children which would be
            // unreachable if we insert the wildcard here
            if len(n.children) > 0 {
                panic("wildcard route '" + path[i:end] +
                    "' conflicts with existing children in path '" + fullPath + "'")
            }
    
            // check if the wildcard has a name
            if end-i < 2 {
                panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
            }
    
            if c == ':' { // param
                // split path at the beginning of the wildcard
                if i > 0 {
                    n.path = path[offset:i]
                    offset = i
                }
    
                child := &node{
                    nType:     param,
                    maxParams: numParams,
                    fullPath:  fullPath,
                }
                n.children = []*node{child}
                n.wildChild = true
                n = child
                n.priority++
                numParams--
    
                // if the path doesn't end with the wildcard, then there
                // will be another non-wildcard subpath starting with '/'
                if end < max {
                    n.path = path[offset:end]
                    offset = end
    
                    child := &node{
                        maxParams: numParams,
                        priority:  1,
                        fullPath:  fullPath,
                    }
                    n.children = []*node{child}
                    n = child
                }
    
            } else { // catchAll
                if end != max || numParams > 1 {
                    panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
                }
    
                if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
                    panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
                }
    
                // currently fixed width 1 for '/'
                i--
                if path[i] != '/' {
                    panic("no / before catch-all in path '" + fullPath + "'")
                }
    
                n.path = path[offset:i]
    
                // first node: catchAll node with empty path
                child := &node{
                    wildChild: true,
                    nType:     catchAll,
                    maxParams: 1,
                    fullPath:  fullPath,
                }
                n.children = []*node{child}
                n.indices = string(path[i])
                n = child
                n.priority++
    
                // second node: node holding the variable
                child = &node{
                    path:      path[i:],
                    nType:     catchAll,
                    maxParams: 1,
                    handlers:  handlers,
                    priority:  1,
                    fullPath:  fullPath,
                }
                n.children = []*node{child}
    
                return
            }
        }
    
        // insert remaining path part and handle to the leaf
        n.path = path[offset:]
        n.handlers = handlers
        n.fullPath = fullPath
    }
    

    折叠一下代码, 主要是两部分, 一个 for 循环, 以及一些更新属性的语句.

    // insert remaining path part and handle to the leaf
    n.path = path[offset:]
    n.handlers = handlers
    n.fullPath = fullPath
    

    主要看一下 for 循环:

    // find prefix until first wildcard (beginning with ':' or '*')
    for i, max := 0, len(path); numParams > 0; i++ {
      c := path[i]
      if c != ':' && c != '*' {
        continue
      }
    

    这几行判断, 如同注释说明的那般, 直到遇到通配符字符 ':' or '*' 才开始真正处理.
    注意判断条件是 numParams, 这个参数指明了有几个通配符参数.

    // find wildcard end (either '/' or path end)
    end := i + 1
    for end < max && path[end] != '/' {
      switch path[end] {
      // the wildcard name must not contain ':' and '*'
      case ':', '*':
        panic("only one wildcard per path segment is allowed, has: '" +
          path[i:] + "' in path '" + fullPath + "'")
      default:
        end++
      }
            }
    

    这也是个判断, 用于验证通配符名字中不能出现多个 ':' and '*'.

    // check if this Node existing children which would be
    // unreachable if we insert the wildcard here
    if len(n.children) > 0 {
      panic("wildcard route '" + path[i:end] +
        "' conflicts with existing children in path '" + fullPath + "'")
    }
    
    // check if the wildcard has a name
    if end-i < 2 {
      panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
    }
    

    又是两个判断, 第一个用于验证当前 node 不能存储子节点, 否则通配符节点就冲突了.
    第二个用于验证通配符节点的名字必须有长度, 至少要有一个字符.

    最后是根据通配符的不同分别构造, 先看一下 c == ':' 时的代码:

    if c == ':' { // param
      // split path at the beginning of the wildcard
      if i > 0 {
        n.path = path[offset:i]
        offset = i
      }
    
      child := &node{
        nType:     param,
        maxParams: numParams,
        fullPath:  fullPath,
      }
      n.children = []*node{child}
      n.wildChild = true
      n = child
      n.priority++
      numParams--
    
      // if the path doesn't end with the wildcard, then there
      // will be another non-wildcard subpath starting with '/'
      if end < max {
        n.path = path[offset:end]
        offset = end
    
        child := &node{
          maxParams: numParams,
          priority:  1,
          fullPath:  fullPath,
        }
        n.children = []*node{child}
        n = child
      }
    
    }
    

    然后是 c == '*' 时的代码, 也就是 else 部分:

    else { // catchAll
      if end != max || numParams > 1 {
        panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
      }
    
      if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
        panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
      }
    
      // currently fixed width 1 for '/'
      i--
      if path[i] != '/' {
        panic("no / before catch-all in path '" + fullPath + "'")
      }
    
      n.path = path[offset:i]
    
      // first node: catchAll node with empty path
      child := &node{
        wildChild: true,
        nType:     catchAll,
        maxParams: 1,
        fullPath:  fullPath,
      }
      n.children = []*node{child}
      n.indices = string(path[i])
      n = child
      n.priority++
    
      // second node: node holding the variable
      child = &node{
        path:      path[i:],
        nType:     catchAll,
        maxParams: 1,
        handlers:  handlers,
        priority:  1,
        fullPath:  fullPath,
      }
      n.children = []*node{child}
    
      return
    }
    

    catchAll 通配符有点特殊, 这个通配符后面是不允许出现其他通配符参数的, 所以前几行都在判断要求是否符合.
    这个过程中会创建两个类型为 catchAll 的节点, 第一个节点指示存储通配符子节点, 即wildChild=true,
    第二个节点会占有具体的内容.

    添加路由的过程基本上就是这样, 接下来看一下如何读取数据.

    获取数据

    从树中获取数据, 主要发生在 func (engine *Engine) handleHTTPRequest(c *Context) 中.
    看一下代码片段:

    root := t[i].root
    // Find route in tree
    value := root.getValue(rPath, c.Params, unescape)
    if value.handlers != nil {
      c.handlers = value.handlers
      c.Params = value.params
      c.fullPath = value.fullPath
      c.Next()
      c.writermem.WriteHeaderNow()
      return
    }
    

    主要是通过 getValue 方法获取数据的, 完整代码如下:

    // getValue returns the handle registered with the given path (key). The values of
    // wildcards are saved to a map.
    // If no handle can be found, a TSR (trailing slash redirect) recommendation is
    // made if a handle exists with an extra (without the) trailing slash for the
    // given path.
    func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) {
        value.params = po
    walk: // Outer loop for walking the tree
        for {
            if len(path) > len(n.path) {
                if path[:len(n.path)] == n.path {
                    path = path[len(n.path):]
                    // If this node does not have a wildcard (param or catchAll)
                    // child,  we can just look up the next child node and continue
                    // to walk down the tree
                    if !n.wildChild {
                        c := path[0]
                        for i := 0; i < len(n.indices); i++ {
                            if c == n.indices[i] {
                                n = n.children[i]
                                continue walk
                            }
                        }
    
                        // Nothing found.
                        // We can recommend to redirect to the same URL without a
                        // trailing slash if a leaf exists for that path.
                        value.tsr = path == "/" && n.handlers != nil
                        return
                    }
    
                    // handle wildcard child
                    n = n.children[0]
                    switch n.nType {
                    case param:
                        // find param end (either '/' or path end)
                        end := 0
                        for end < len(path) && path[end] != '/' {
                            end++
                        }
    
                        // save param value
                        if cap(value.params) < int(n.maxParams) {
                            value.params = make(Params, 0, n.maxParams)
                        }
                        i := len(value.params)
                        value.params = value.params[:i+1] // expand slice within preallocated capacity
                        value.params[i].Key = n.path[1:]
                        val := path[:end]
                        if unescape {
                            var err error
                            if value.params[i].Value, err = url.QueryUnescape(val); err != nil {
                                value.params[i].Value = val // fallback, in case of error
                            }
                        } else {
                            value.params[i].Value = val
                        }
    
                        // we need to go deeper!
                        if end < len(path) {
                            if len(n.children) > 0 {
                                path = path[end:]
                                n = n.children[0]
                                continue walk
                            }
    
                            // ... but we can't
                            value.tsr = len(path) == end+1
                            return
                        }
    
                        if value.handlers = n.handlers; value.handlers != nil {
                            value.fullPath = n.fullPath
                            return
                        }
                        if len(n.children) == 1 {
                            // No handle found. Check if a handle for this path + a
                            // trailing slash exists for TSR recommendation
                            n = n.children[0]
                            value.tsr = n.path == "/" && n.handlers != nil
                        }
    
                        return
    
                    case catchAll:
                        // save param value
                        if cap(value.params) < int(n.maxParams) {
                            value.params = make(Params, 0, n.maxParams)
                        }
                        i := len(value.params)
                        value.params = value.params[:i+1] // expand slice within preallocated capacity
                        value.params[i].Key = n.path[2:]
                        if unescape {
                            var err error
                            if value.params[i].Value, err = url.QueryUnescape(path); err != nil {
                                value.params[i].Value = path // fallback, in case of error
                            }
                        } else {
                            value.params[i].Value = path
                        }
    
                        value.handlers = n.handlers
                        value.fullPath = n.fullPath
                        return
    
                    default:
                        panic("invalid node type")
                    }
                }
            } else if path == n.path {
                // We should have reached the node containing the handle.
                // Check if this node has a handle registered.
                if value.handlers = n.handlers; value.handlers != nil {
                    value.fullPath = n.fullPath
                    return
                }
    
                if path == "/" && n.wildChild && n.nType != root {
                    value.tsr = true
                    return
                }
    
                // No handle found. Check if a handle for this path + a
                // trailing slash exists for trailing slash recommendation
                for i := 0; i < len(n.indices); i++ {
                    if n.indices[i] == '/' {
                        n = n.children[i]
                        value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
                            (n.nType == catchAll && n.children[0].handlers != nil)
                        return
                    }
                }
    
                return
            }
    
            // Nothing found. We can recommend to redirect to the same URL with an
            // extra trailing slash if a leaf exists for that path
            value.tsr = (path == "/") ||
                (len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
                    path == n.path[:len(n.path)-1] && n.handlers != nil)
            return
        }
    }
    

    代码有点长, 先读一下注释. 主要是根据路径和参数, 获取注册在上面的 handlers.

    // nodeValue holds return values of (*Node).getValue method
    type nodeValue struct {
        handlers HandlersChain
        params   Params
        tsr      bool
        fullPath string
    }
    
    // Param is a single URL parameter, consisting of a key and a value.
    type Param struct {
        Key   string
        Value string
    }
    

    里面用到的结构体如上. 方法的主体部分是一个 for 循环.

    for 循环里面, 前半部分是一个判断, 先看一下后半部分.

    // Nothing found. We can recommend to redirect to the same URL with an
    // extra trailing slash if a leaf exists for that path
    value.tsr = (path == "/") ||
      (len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
        path == n.path[:len(n.path)-1] && n.handlers != nil)
    return
    

    如果没有找到对应的匹配, 会设置一个叫做 tsr 的标识, 用于判断是否符合 TSR (trailing slash redirect), 即尾部斜杆重定向. 比如 /path 可以重定向到 /path/.

    回到 if 判断上来, 先看第一个判断部分, 即 if len(path) > len(n.path).

    if len(path) > len(n.path) {
      if path[:len(n.path)] == n.path {
        path = path[len(n.path):]
        // If this node does not have a wildcard (param or catchAll)
        // child,  we can just look up the next child node and continue
        // to walk down the tree
        if !n.wildChild {
          c := path[0]
          for i := 0; i < len(n.indices); i++ {
            if c == n.indices[i] {
              n = n.children[i]
              continue walk
            }
          }
    
          // Nothing found.
          // We can recommend to redirect to the same URL without a
          // trailing slash if a leaf exists for that path.
          value.tsr = path == "/" && n.handlers != nil
          return
        }
    
        // handle wildcard child
        n = n.children[0]
        switch n.nType {
        case param:
          // find param end (either '/' or path end)
          end := 0
          for end < len(path) && path[end] != '/' {
            end++
          }
    
          // save param value
          if cap(value.params) < int(n.maxParams) {
            value.params = make(Params, 0, n.maxParams)
          }
          i := len(value.params)
          value.params = value.params[:i+1] // expand slice within preallocated capacity
          value.params[i].Key = n.path[1:]
          val := path[:end]
          if unescape {
            var err error
            if value.params[i].Value, err = url.QueryUnescape(val); err != nil {
              value.params[i].Value = val // fallback, in case of error
            }
          } else {
            value.params[i].Value = val
          }
    
          // we need to go deeper!
          if end < len(path) {
            if len(n.children) > 0 {
              path = path[end:]
              n = n.children[0]
              continue walk
            }
    
            // ... but we can't
            value.tsr = len(path) == end+1
            return
          }
    
          if value.handlers = n.handlers; value.handlers != nil {
            value.fullPath = n.fullPath
            return
          }
          if len(n.children) == 1 {
            // No handle found. Check if a handle for this path + a
            // trailing slash exists for TSR recommendation
            n = n.children[0]
            value.tsr = n.path == "/" && n.handlers != nil
          }
    
          return
    
        case catchAll:
          // save param value
          if cap(value.params) < int(n.maxParams) {
            value.params = make(Params, 0, n.maxParams)
          }
          i := len(value.params)
          value.params = value.params[:i+1] // expand slice within preallocated capacity
          value.params[i].Key = n.path[2:]
          if unescape {
            var err error
            if value.params[i].Value, err = url.QueryUnescape(path); err != nil {
              value.params[i].Value = path // fallback, in case of error
            }
          } else {
            value.params[i].Value = path
          }
    
          value.handlers = n.handlers
          value.fullPath = n.fullPath
          return
    
        default:
          panic("invalid node type")
        }
      }
    }
    

    这部分的判断里嵌套了一个 if 判断, 用于判断路径的前缀和当前节点的 path 相符, 如果不相等就直接跳过.

    然后是根据 n.wildChild 判断, 即基于是否有通配符子节点.

    如果没有通配符子节点, 会继续查找下一个子节点, 然后进行新一轮的 for 循环.
    如果找不到子节点, 就直接返回了. 是否存在子节点是根据 n.indices 判断的,
    n.indices 是个字符串, 保存了所有子节点路径的第一个字符.
    比如, 当前注册了两个路径, /ping/pong, 那么当前的节点就是 /p 公共前缀,
    然后它的 n.indices="io".

    如果存在通配符子节点, 就会根据 n.nType 的类型进行选择处理.

    如果类型是 param, 即使用 : 命名的变量, 就会先保存那个变量的值.
    如果长度还有剩余 if end < len(path) {, 就会进入到新一个 for 循环中;
    否认就认为是结束了, 将 handlersfullPath 复制一下就行了.

    如果类型是 catchAll, 即使用 * 命令的任意匹配变量, 处理就比较简单了,
    因为不用考虑后面还有路径的问题, * 会匹配所有剩余的 path 路径.
    直接保存变量值, 然后将 handlersfullPath 复制一下就行了.

    如果类型不符合上述的两种类型, 就会触发 panic.

    接着看另一个判断, 即 else if path == n.path.

    else if path == n.path {
      // We should have reached the node containing the handle.
      // Check if this node has a handle registered.
      if value.handlers = n.handlers; value.handlers != nil {
        value.fullPath = n.fullPath
        return
      }
    
      if path == "/" && n.wildChild && n.nType != root {
        value.tsr = true
        return
      }
    
      // No handle found. Check if a handle for this path + a
      // trailing slash exists for trailing slash recommendation
      for i := 0; i < len(n.indices); i++ {
        if n.indices[i] == '/' {
          n = n.children[i]
          value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
            (n.nType == catchAll && n.children[0].handlers != nil)
          return
        }
      }
    
      return
    }
    

    这部分的处理也是比较简单的, 和前面的逻辑类似, 主要是看路径上是否有 handler 注册.
    如果没有 handler 注册, 就会检查 value.tsr 的值, 是否属于尾部斜杆重定向.

    由此, 从树中获取数据的过程也已经看完了.

    总结

    优秀的代码还是要多读读的, 即有助于理解原理, 又能开阔自己的视野.
    另外一点, 读代码的时候调试器真的是非常有用, 尤其是观察数据结构是怎么存储的.

    相关文章

      网友评论

        本文标题:02Gin源码解读

        本文链接:https://www.haomeiwen.com/subject/lsnkgctx.html