跳到主要内容

router

路由调用逻辑

gin 对外宣传的高效,很大一部分是说其路由效率。本文内容包括:

  • 路由API介绍
  • 路由调用实现逻辑
  • 路由的内部实现

路由API

设置路由

// routergroup.go:20
type IRoutes interface {
Use(handlers ...HandlerFunc) IRoutes

Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes
Any(relativePath string, handlers ...HandlerFunc) IRoutes
GET(relativePath string, handlers ...HandlerFunc) IRoutes
POST(relativePath string, handlers ...HandlerFunc) IRoutes
DELETE(relativePath string, handlers ...HandlerFunc) IRoutes
PATCH(relativePath string, handlers ...HandlerFunc) IRoutes
PUT(relativePath string, handlers ...HandlerFunc) IRoutes
OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes
HEAD(relativePath string, handlers ...HandlerFunc) IRoutes

StaticFile(relativePath, filepath string) IRoutes
Static(relativePath, root string) IRoutes
StaticFS(relativePath string, fs http.FileSystem) IRoutes
}

// routergroup.go:15
type IRouter interface {
IRoutes
Group(string, ...HandlerFunc) *RouterGroup
}

RouteGroup的获取

  • Engine嵌入了RouteGroup,它本身就实现了IRoutes接口,gin.New() 和 gin.Default() 可以得到Engine对象
  • Engine.Group(relativePath string, handlers ...HandlerFunc)可以得到一个新的RouteGroup

路由的命中

初始化时将 gin.go:handleHTTPRequest 设置为http请求的处理者,它会将请求进行预处理后去处查找命中的处理者(列表),然后去执行。 这个是调用逻辑,我们讲具体实现。

路由的调用逻辑

背景知识

我们先看 Engine 结构体和路由有关的字段

gin.go:50

type Engine struct {
RouterGroup

// 如果true,当前路由匹配失败但将路径最后的 / 去掉时匹配成功时自动匹配后者
// 比如:请求是 /foo/ 但没有命中,而存在 /foo,
// 对get method请求,客户端会被301重定向到 /foo
// 对于其他method请求,客户端会被307重定向到 /foo
RedirectTrailingSlash bool

// 如果true,在没有处理者被注册来处理当前请求时router将尝试修复当前请求路径
// 逻辑为:
// - 移除前面的 ../ 或者 //
// - 对新的路径进行大小写不敏感的查询
// 如果找到了处理者,请求会被301或307重定向
// 比如: /FOO 和 /..//FOO 会被重定向到 /foo
// RedirectTrailingSlash 参数和这个参数独立
RedirectFixedPath bool

// 如果true,当路由没有被命中时,去检查是否有其他method命中
// 如果命中,响应405 (Method Not Allowed)
// 如果没有命中,请求将由 NotFound handler 来处理
HandleMethodNotAllowed bool

// 如果true, url.RawPath 会被用来查找参数
UseRawPath bool

// 如果true, path value 会被保留
// 如果 UseRawPath是false(默认),UnescapePathValues为true
// url.Path会被保留并使用
UnescapePathValues bool

allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain

//每个http method对应一棵树
trees methodTrees
}

// gin.go:30
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc

// routergroup.go:40
type RouterGroup struct {
// 这个路由会参与处理的函数列表
Handlers HandlersChain
basePath string
// 单例存在
engine *Engine
// 是否是根
root bool
}

添加路由

// routergroup.go:70
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 将basePath和relativePath加起来得到最终的路径
absolutePath := group.calculateAbsolutePath(relativePath)
// 将现有的 Handlers 和 handlers合并起来
handlers = group.combineHandlers(handlers)
// 将这个route加入到engine.tree
group.engine.addRoute(httpMethod, absolutePath, handlers)
// 返回
return group.returnObj()
}

上面的 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)
// 维护engine.trees
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.go:340

func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
path := c.Request.URL.Path
unescape := false
// 看是否使用 RawPath
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
path = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}

t := engine.trees
// 根据 http method 得到目标树
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method == httpMethod {
// 目标树找到了,为本次请求路由树的根节点
root := t[i].root
// 根据path查找节点
// 核心,后面来讲
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 != "/" {
// 如果 trailing slash redirect,就重定向出去
if tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}

// fix path
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
// 没找到
break
}
}

// 如果是因为HTTP method有误,回复这个
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method != httpMethod {
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, 405, default405Body)
return
}
}
}
}

// 交给 NotRoute (404)
c.handlers = engine.allNoRoute
serveError(c, 404, default404Body)
}