gin路由例子1

2021-07-24  本文已影响0人  xiaolv17

上篇文章说将请求加入树中是比较关键的,现在我们开始举第一个例子说明。

这个是最简单的例子,很多文章写gin的使用都会写到

r := gin.Default()
    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080

现在我们就来看看这个/hello的请求是怎么添加到路由树中的。

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

此时addRoute()三个参数的值分别是"GET","/hello",handlers,它会先去取GET请求的root节点,因为我们这个是第一个请求那么root就是空的,这个时候就会新建一个node作为GET请求的根节点,这个时候当前节点的结构如下:

type node struct {
    path      string
    indices   string
    wildChild bool
    nType     nodeType
    priority  uint32
    children  []*node
    handlers  HandlersChain
    fullPath  "/"
}

紧接着就会调用addRoute()方法,将该请求加到树中。此时addRoute的参数path的值是"/hello"

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
    }

    parentFullPathIndex := 0

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) {
            //添加给n的children节点
            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,
            }

            n.children = []*node{&child}
            // []byte for proper unicode char conversion, see #65
            n.indices = bytesconv.BytesToString([]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++

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

                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, 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 != '*' {
                // []byte for proper unicode char conversion, see #65
                n.indices += bytesconv.BytesToString([]byte{c})
                child := &node{
                    fullPath: fullPath,
                }
                n.children = append(n.children, child)
                n.incrementChildPrio(len(n.indices) - 1)
                n = child
            }
            n.insertChild(path, fullPath, handlers)
            return
        }

        // Otherwise and handle to current node
        if n.handlers != nil {
            panic("handlers are already registered for path '" + fullPath + "'")
        }
        n.handlers = handlers
        n.fullPath = fullPath
        return
    }
}

我们可以看到给根节点的priority加1;然后判断是不是一个空树,由上面的node的值判断,这里if条件是成立的,这个时候会调用insertChild(path,fullPath,handlers)函数,此时path和fullPath的值都是"/hello"。

func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
    for {
        // Find prefix until first wildcard
        //找到:或者*后路径字符串并返回其位置判断之后还有没有:或者*
        wildcard, i, valid := findWildcard(path)
        if i < 0 { // No wildcard found
            break
        }

        // The wildcard name must not contain ':' and '*'
        if !valid {
            panic("only one wildcard per path segment is allowed, has: '" +
                wildcard + "' in path '" + fullPath + "'")
        }

        // check if the wildcard has a name
        //判断通配符后面的字符串长度
        if len(wildcard) < 2 {
            panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
        }

        // Check if this node has existing children which would be
        // unreachable if we insert the wildcard here
        if len(n.children) > 0 {
            panic("wildcard segment '" + wildcard +
                "' conflicts with existing children in path '" + fullPath + "'")
        }

        //如果是参数的话
        if wildcard[0] == ':' { // param
            if i > 0 {
                // Insert prefix before the current wildcard
                n.path = path[:i]   //结点的path就是:之前的
                path = path[i:]     //把:之后的值赋值给path
            }

            n.wildChild = true  //将通配符子节点置为true
            child := &node{
                nType:    param,    //参数类型
                path:     wildcard, //通配符后面的字符串
                fullPath: fullPath, //完整的路径
            }
            n.children = []*node{child} //把节点塞到子节点里面
            n = child
            n.priority++

            // if the path doesn't end with the wildcard, then there
            // will be another non-wildcard subpath starting with '/'
            //类似于:asd/ahshd
            if len(wildcard) < len(path) {
                path = path[len(wildcard):]

                child := &node{
                    priority: 1,
                    fullPath: fullPath,
                }
                n.children = []*node{child} //将后面的放在children里面
                n = child
                continue
            }

            // Otherwise we're done. Insert the handle in the new leaf
            n.handlers = handlers
            return
        }

        // catchAll
        if i+len(wildcard) != len(path) {
            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 '/'、
        ///iu/kyut
        i--
        if path[i] != '/' {
            panic("no / before catch-all in path '" + fullPath + "'")
        }
        //通配符/前面的字符串
        n.path = path[:i]

        // First node: catchAll node with empty path
        //将/前面的作为一个子节点
        child := &node{
            wildChild: true,
            nType:     catchAll,
            fullPath:  fullPath,
        }

        n.children = []*node{child}
        n.indices = string('/')
        n = child
        n.priority++

        // second node: node holding the variable
        //将/后面的作为/前面的节点的子节点
        child = &node{
            path:     path[i:],
            nType:    catchAll,
            handlers: handlers,
            priority: 1,
            fullPath: fullPath,
        }
        n.children = []*node{child}

        return
    }

    // If no wildcard was found, simply insert the path and handle
    //如果没有通配符就直接插进去
    n.path = path
    n.handlers = handlers
    n.fullPath = fullPath
}
func findWildcard(path string) (wildcard string, i int, valid bool) {
    // Find start
    for start, c := range []byte(path) {
        // A wildcard starts with ':' (param) or '*' (catch-all)
        if c != ':' && c != '*' {
            continue
        }

        // Find end and check for invalid characters
        valid = true
        for end, c := range []byte(path[start+1:]) {
            switch c {
            case '/':
                return path[start : start+1+end], start, valid
            case ':', '*':
                valid = false
            }
        }
        return path[start:], start, valid
    }
    return "", -1, false
}

由我上面的注释可以看出insertChild函数会先判断有没有:或者*,因为我们的url为"/hello"不含这两个字符,所以就会直接返回,insertChild的for循环也会直接break,直接走到下面代码。

    n.path = path
    n.handlers = handlers
    n.fullPath = fullPath

给根节点赋值,此时我们的根节点的结构为

type node struct {
    path      "/hello"
    indices   string
    wildChild bool
    nType     nodeType
    priority  uint32
    children  []*node
    handlers  handlers
    fullPath  "/hello"
}

之后insertChild()返回,将根节点的nType的值置为root,直接返回,然后addRoute()函数更新参数数量。最后数据的结构为

type node struct {  path      "/hello"  indices   ""    wildChild false nType     root  priority  1 children  []    handlers  handlers  fullPath  "/hello"}

以上操作就已经把这个/hello请求加到了gin的路由树了,此时这棵GET树上只有一个节点。

未命名文件.png

下篇文章,我会举第二个例子,在已经有一个/hello节点的时候添加另一个节点的过程·。

这就是添加第一个请求路由的过程,上述内容入如有错误和不妥之处,请指出,望不吝赐教,谢谢。

上一篇下一篇

猜你喜欢

热点阅读