zoukankan      html  css  js  c++  java
  • Gin的路由算法

    Gin的是路由算法其实就是一个Trie树(也就是前缀树)。

    注册路由预处理

    我们在使用gin时通过下面的代码注册路由

    普通注册

    router.POST("/somePost", func(context *gin.Context) {
        context.String(http.StatusOK, "some post")
    })

    使用中间件

    router.Use(Logger())

    使用Group

    v1 := router.Group("v1")
    {
        v1.POST("login", func(context *gin.Context) {
            context.String(http.StatusOK, "v1 login")
        })
    }

    这些操作, 最终都会在反应到gin的路由树上

    具体实现

    // routergroup.go
    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()
    }

    在调用POSTGETHEAD等路由HTTP相关函数时, 会调用handle函数

    重点关注下面两个函数:

    // routergroup.go 
    func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
        finalSize := len(group.Handlers) + len(handlers)
        if finalSize >= int(abortIndex) {
            panic("too many handlers")
        }
        mergedHandlers := make(HandlersChain, finalSize)
        copy(mergedHandlers, group.Handlers)
        copy(mergedHandlers[len(group.Handlers):], handlers)
        return mergedHandlers
    }
    func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
        return joinPaths(group.basePath, relativePath)
    }
    
    func joinPaths(absolutePath, relativePath string) string {
        if relativePath == "" {
            return absolutePath
        }
    
        finalPath := path.Join(absolutePath, relativePath)
        appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
        if appendSlash {
            return finalPath + "/"
        }
        return finalPath
    }

    joinPaths函数里面有段代码, 很有意思, 我还以为是写错了. 主要是path.Join的用法.

    finalPath := path.Join(absolutePath, relativePath)
    appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
    在当路由是/user/这种情况就满足了lastChar(relativePath) == '/' && lastChar(finalPath) != '/'. 主要原因是path.Join(absolutePath, relativePath)之后, finalPath是user

    综合来看, 在预处理阶段

    1. 在调用中间件的时候, 是将某个路由的handler处理函数和中间件的处理函数都放在了Handlers的数组中
    2. 在调用Group的时候, 是将路由的path上面拼上Group的值. 也就是/user/:name, 会变成v1/user:name

    真正注册

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

    调用group.engine.addRoute(httpMethod, absolutePath, handlers)将预处理阶段的结果注册到gin Engine的trees上

    gin路由树

    gin的路由树算法是一棵前缀树. 不过并不是只有一颗树, 而是每种方法(POST, GET ...)都有自己的一颗树
    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)
            engine.trees = append(engine.trees, methodTree{method: method, root: root})
        }
        root.addRoute(path, handlers)
    }

    Gin的路由树的大致样子

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

    其实gin的实现不像一个真正的树, children []*node所有的孩子都放在这个数组里面, 利用indices, priority变相实现一棵树

    获取路由handler

    当服务端收到客户端的请求时, 根据pathtrees匹配到相关的路由, 拿到相关的处理handlers

    ...
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        if t[i].method != httpMethod {
            continue
        }
        root := t[i].root
        // Find route in tree
        handlers, params, tsr := root.getValue(path, c.Params, unescape) // 看这里
        if handlers != nil {
            c.handlers = handlers
            c.Params = params
            c.Next()
            c.writermem.WriteHeaderNow()
            return
        }
        if httpMethod != "CONNECT" && path != "/" {
            if tsr && engine.RedirectTrailingSlash {
                redirectTrailingSlash(c)
                return
            }
            if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
                return
            }
        }
        break
    }
    ...

    主要在下面这个函数里面调用程序注册的路由处理函数

    func (c *Context) Next() {
        c.index++
        for c.index < int8(len(c.handlers)) {
            c.handlers[c.index](c)
            c.index++
        }
    }
  • 相关阅读:
    kvm virtio
    GPU 线程块/线程束
    ubuntu source
    React
    分布式系统
    honpeyhonepy
    css是干什么的
    bootstrap中的横的列
    数据安全之 alert logic
    viewset的使用的方法
  • 原文地址:https://www.cnblogs.com/peteremperor/p/14033173.html
Copyright © 2011-2022 走看看