02Gin Source Interpretation

Keywords: Go github less

brief introduction

Gin source interpretation, based on v1.5.0 Edition.

HttpRouter implementation

Adding routes is mainly done by 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's route is through httprouter Implemented to learn more about its source code.

data structure

github's documentation explains how this works and is available for reference How does it work?.

HttpRouter uses Radix trees internally, a compact variant of the prefix tree.

The above image, from Wikipedia, shows the structure of the Radix tree. Compared to a common prefix tree, the Radix tree stores more than one character on its edge, greatly compressing its depth.

Take a look at the definition of the data structure:

// 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

The type of Engine.trees is methodTrees, and the initialization statement is 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
}

The first step in the previous routing code is to find root, which is root: = engine.trees.get (method), combined with get code.
We can see that methodTrees are actually classified by HTTP methods, each corresponding to a tree.

If the current HTTP method of this type does not exist, create a new tree methodTree:

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

Take another look at how tree nodes are defined:

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
}

Add Route

Now that you know the data structure, take a look at how routes are added, 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

The code is a bit long. First, there are two cases based on the if statement, one is when the tree is initialized (that is, the tree is empty), the other is when the tree is not empty.

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

If the tree is empty, that is, n.path is an empty string (initial value) and n.children is an empty slice.
At this point, just insert the node through insertChild and set the node's type to root.
The code for insertChild is a bit long, so come back later.

When the tree is not empty and you enter a for loop, follow the comments to see what the for does in general.

// 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

The first two steps, updating maxParams and calculating the length of the longest prefix, are simple, just look at the code.

See how the nodes split, step three:

// 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]
}

When the length of the common prefix is less than n.path, the current node splits into a child node.

For example, the current node node.path ='/ping', will split when it encounters path ='/pong',
The length of the common prefix i=2, so the node will split into node.path ='/p'and node.path ='ing'.
The split node will take up most of the attributes of the current node.

Next, look at the fourth step, how to add a child node to the current node, which is the core code of root.addRoute(path, handlers).

This is also an if judgment. Let's look at the second half first, which is what happens when something goes wrong:

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
}

If handlers are not empty, an error will occur, indicating that handlers are only allowed to register once.

Look at the first half of if, when 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

}

This part is also a bit long and needs to be broken down step by step.

First, according to path = path[i:], we find that path has removed the common prefix.

Let's look at the first judgment, if n.wildChild, where there are wildcard child nodes:

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 +
    "'")
}

In the judgment of wildcards, wildcards conflict is usually triggered incorrectly, unless the previous wildcard part is the same, followed by /.

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
}

When the node is a wildcard and the path starts/goes into a new round of looping.

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

Check to see if there is a child node, jump directly to that node if there is one, and enter a new cycle.
When the previous node splits, n.indices = string([]byte{n.path[i]}) is set.

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

After making the previous judgment, come here and if c is not: or *, insert a node and replace the current node with this one.

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

Finally, insertChild is still called. Finally, return can be used to jump out of the loop and end the method.

insertChild

n.insertChild(numParams, path, fullPath, handlers) was called in two places above to see its implementation.

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
}

Collapse the code, mainly in two parts, a for loop, and some statements to update the properties.

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

Let's look mainly at the for loop:

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

These lines of judgment, as explained in the comment, do not really begin processing until they encounter the wildcard character':'or'*'.
Note that the judgement is numParams, which indicates several wildcard parameters.

// 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++
  }
        }

This is also a judgment to verify that no more than one':'and'*' can appear in a wildcard name.

// 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 + "'")
}

Two more judgments, the first to verify that the current node cannot store child nodes, otherwise the wildcard node will conflict.
The second name used to validate a wildcard node must be at least one character long.

Finally, construct them separately based on the wildcards. First, look at the code when 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
  }

}

Then the code for c =='*', which is the else part:

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
}

The catchAll wildcard is a bit special. No other wildcard parameters are allowed after it, so the first few lines are trying to determine if the requirements are met.
During this process, two nodes of type catchAll are created, and the first node indicates the storage of wildcard child nodes, wildChild=true.
The second node will have specific content.

This is basically the process of adding routes, so let's see how to read the data.

get data

Getting data from a tree occurs mainly in func (engine *Engine) handleHTTPRequest(c *Context).
Take a look at the snippet:

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
}

The data is obtained mainly through the getValue method, with the complete code as follows:

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

The code is a bit long. Read the comment first. Get the handlers registered on it, mainly based on the path and parameters.

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

The structure used inside is shown above. The main part of the method is a for loop.

Inside the for loop, the first half is a judgment, so let's look at the second half first.

// 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

If no corresponding match is found, an identification called tsr is set to determine whether the TSR (trailing slash redirect), or tail slash redirect, is met. For example, /path can be redirected to/path/.

To return to if judgment, first look at the first judgment section, 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")
    }
  }
}

This part of the judgment is nested with an if judgment, which is used to determine that the prefix of the path matches the path of the current node, or skip directly if it is not equal.

Then it is based on n.wildChild, that is, on whether there are wildcard child nodes.

If there are no wildcard child nodes, the next child node will continue to be found and a new round of for loops will occur.
If no child node is found, it is returned directly. The existence of a child node is determined by n.indices.
n.indices is a string that holds the first character of all subnode paths.
For example, if two paths are currently registered, /ping and/pong, then the current node is the / p public prefix,
Then it's n.indices="io".

If there are wildcard child nodes, the selection process will be based on the type of n.nType.

If the type is param, even if a named variable is used, the value of that variable will be saved first.
If there is still left if end < len(path) {in the length, it will enter a new for loop;
Denial is considered complete, just copy handlers and fullPath.

If the type is catchAll, it is easier to use any matching variable of the *command.
Because there are no further paths to consider, * matches all remaining paths.
Save the variable values directly, then copy the handlers and fullPath.

A panic is triggered if the type does not match both of the above types.

Then look at another judgment, 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
}

This part of the processing is also relatively simple, similar to the previous logic, mainly to see if there is handler registration on the path.
If no handler is registered, the value.tsr is checked for tail slash redirection.

As a result, the process of getting data from a tree has been completed.

summary

Good code should be read more, which helps you understand the principles and broaden your horizons.
On the other hand, debuggers can be really useful when reading code, especially when observing how data structures are stored.

Posted by mabwi on Fri, 06 Dec 2019 14:08:46 -0800