路由調用邏輯
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)
}
```