gin的是路由算法其实就是一个Trie树(也就是前缀树). 有关数据结构的可以自己去网上找相关资料查看.

1.1.1. 注册路由预处理

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

1.1.2. 普通注册

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

1.1.3. 使用中间件

  1. router.Use(Logger())

1.1.4. 使用Group

  1. v1 := router.Group("v1")
  2. {
  3. v1.POST("login", func(context *gin.Context) {
  4. context.String(http.StatusOK, "v1 login")
  5. })
  6. }

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

1.1.5. 具体实现

  1. // routergroup.go:L72-77
  2. func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
  3. absolutePath := group.calculateAbsolutePath(relativePath) // <---
  4. handlers = group.combineHandlers(handlers) // <---
  5. group.engine.addRoute(httpMethod, absolutePath, handlers)
  6. return group.returnObj()
  7. }

在调用POST, GET, HEAD等路由HTTP相关函数时, 会调用handle函数
如果调用了中间件的话, 会调用下面函数

  1. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
  2. group.Handlers = append(group.Handlers, middleware...)
  3. return group.returnObj()
  4. }

如果使用了Group的话, 会调用下面函数

  1. func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
  2. return &RouterGroup{
  3. Handlers: group.combineHandlers(handlers),
  4. basePath: group.calculateAbsolutePath(relativePath),
  5. engine: group.engine,
  6. }
  7. }

重点关注下面两个函数:

  1. // routergroup.go:L208-217
  2. func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
  3. finalSize := len(group.Handlers) + len(handlers)
  4. if finalSize >= int(abortIndex) {
  5. panic("too many handlers")
  6. }
  7. mergedHandlers := make(HandlersChain, finalSize)
  8. copy(mergedHandlers, group.Handlers)
  9. copy(mergedHandlers[len(group.Handlers):], handlers)
  10. return mergedHandlers
  11. }
  1. func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
  2. return joinPaths(group.basePath, relativePath)
  3. }
  4. func joinPaths(absolutePath, relativePath string) string {
  5. if relativePath == "" {
  6. return absolutePath
  7. }
  8. finalPath := path.Join(absolutePath, relativePath)
  9. appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
  10. if appendSlash {
  11. return finalPath + "/"
  12. }
  13. return finalPath
  14. }

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

  1. finalPath := path.Join(absolutePath, relativePath)
  2. 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

1.1.6. 真正注册

  1. // routergroup.go:L72-77
  2. func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
  3. absolutePath := group.calculateAbsolutePath(relativePath) // <---
  4. handlers = group.combineHandlers(handlers) // <---
  5. group.engine.addRoute(httpMethod, absolutePath, handlers)
  6. return group.returnObj()
  7. }

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

1.1.7. gin路由树简单介绍

gin的路由树算法是一棵前缀树. 不过并不是只有一颗树, 而是每种方法(POST, GET …)都有自己的一颗树

  1. func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
  2. assert1(path[0] == '/', "path must begin with '/'")
  3. assert1(method != "", "HTTP method can not be empty")
  4. assert1(len(handlers) > 0, "there must be at least one handler")
  5. debugPrintRoute(method, path, handlers)
  6. root := engine.trees.get(method) // <-- 看这里
  7. if root == nil {
  8. root = new(node)
  9. engine.trees = append(engine.trees, methodTree{method: method, root: root})
  10. }
  11. root.addRoute(path, handlers)
  12. }

gin 路由最终的样子大概是下面的样子
gin的路由算法 - 图1

  1. type node struct {
  2. path string
  3. indices string
  4. children []*node
  5. handlers HandlersChain
  6. priority uint32
  7. nType nodeType
  8. maxParams uint8
  9. wildChild bool
  10. }

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

1.1.8. 获取路由handler

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

  1. ...
  2. t := engine.trees
  3. for i, tl := 0, len(t); i < tl; i++ {
  4. if t[i].method != httpMethod {
  5. continue
  6. }
  7. root := t[i].root
  8. // Find route in tree
  9. handlers, params, tsr := root.getValue(path, c.Params, unescape) // 看这里
  10. if handlers != nil {
  11. c.handlers = handlers
  12. c.Params = params
  13. c.Next()
  14. c.writermem.WriteHeaderNow()
  15. return
  16. }
  17. if httpMethod != "CONNECT" && path != "/" {
  18. if tsr && engine.RedirectTrailingSlash {
  19. redirectTrailingSlash(c)
  20. return
  21. }
  22. if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
  23. return
  24. }
  25. }
  26. break
  27. }
  28. ...

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

  1. func (c *Context) Next() {
  2. c.index++
  3. for c.index < int8(len(c.handlers)) {
  4. c.handlers[c.index](c)
  5. c.index++
  6. }
  7. }