路由調(diào)用邏輯
gin 對外宣傳的高效,很大一部分是說其路由效率。本文內(nèi)容包括:
- 路由API介紹
- 路由調(diào)用實(shí)現(xiàn)邏輯
- 路由的內(nèi)部實(shí)現(xiàn)
## 路由API
### 設(shè)置路由
```
// 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,它本身就實(shí)現(xiàn)了IRoutes接口,gin.New() 和 gin.Default() 可以得到Engine對象
- Engine.Group(relativePath string, handlers ...HandlerFunc)可以得到一個(gè)新的RouteGroup
### 路由的命中
初始化時(shí)將 `gin.go:handleHTTPRequest` 設(shè)置為http請求的處理者,它會將請求進(jìn)行預(yù)處理后去處查找命中的處理者(列表),然后去執(zhí)行。
這個(gè)是調(diào)用邏輯,我們講具體實(shí)現(xiàn)。
## 路由的調(diào)用邏輯
### 背景知識
我們先看 `Engine` 結(jié)構(gòu)體和路由有關(guān)的字段
```
gin.go:50
type Engine struct {
RouterGroup
// 如果true,當(dāng)前路由匹配失敗但將路徑最后的 / 去掉時(shí)匹配成功時(shí)自動(dòng)匹配后者
// 比如:請求是 /foo/ 但沒有命中,而存在 /foo,
// 對get method請求,客戶端會被301重定向到 /foo
// 對于其他method請求,客戶端會被307重定向到 /foo
RedirectTrailingSlash bool
// 如果true,在沒有處理者被注冊來處理當(dāng)前請求時(shí)router將嘗試修復(fù)當(dāng)前請求路徑
// 邏輯為:
// - 移除前面的 ../ 或者 //
// - 對新的路徑進(jìn)行大小寫不敏感的查詢
// 如果找到了處理者,請求會被301或307重定向
// 比如: /FOO 和 /..//FOO 會被重定向到 /foo
// RedirectTrailingSlash 參數(shù)和這個(gè)參數(shù)獨(dú)立
RedirectFixedPath bool
// 如果true,當(dāng)路由沒有被命中時(shí),去檢查是否有其他method命中
// 如果命中,響應(yīng)405 (Method Not Allowed)
// 如果沒有命中,請求將由 NotFound handler 來處理
HandleMethodNotAllowed bool
// 如果true, url.RawPath 會被用來查找參數(shù)
UseRawPath bool
// 如果true, path value 會被保留
// 如果 UseRawPath是false(默認(rèn)),UnescapePathValues為true
// url.Path會被保留并使用
UnescapePathValues bool
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
//每個(gè)http method對應(yīng)一棵樹
trees methodTrees
}
// gin.go:30
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc
// routergroup.go:40
type RouterGroup struct {
// 這個(gè)路由會參與處理的函數(shù)列表
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)
// 將現(xiàn)有的 Handlers 和 handlers合并起來
handlers = group.combineHandlers(handlers)
// 將這個(gè)route加入到engine.tree
group.engine.addRoute(httpMethod, absolutePath, handlers)
// 返回
return group.returnObj()
}
```
上面的 `addRoute()` 的實(shí)現(xiàn):
```
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
// 常規(guī)檢查
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)
// 維護(hù)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
// 根據(jù) http method 得到目標(biāo)樹
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method == httpMethod {
// 目標(biāo)樹找到了,為本次請求路由樹的根節(jié)點(diǎn)
root := t[i].root
// 根據(jù)path查找節(jié)點(diǎn)
// 核心,后面來講
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
}
}
// 如果是因?yàn)镠TTP method有誤,回復(fù)這個(gè)
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)
}
```
