[TOC]
https://www.cnblogs.com/binHome/p/13411878.html

### 路由樹
是一顆前綴樹
一共9個總結點,即:get、post、put等等
### 注冊路由
> 邏輯主要有`addRoute`函數和`insertChild`方法。
1、
~~~
這是gin.go的方法
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
。。。
root := engine.trees.get(method) //1、調用的樹的get方法,獲取當前總的方法節點,沒有返回nil,eg:get的樹節點,
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers) //2、再調用樹的的addRoute,注冊
。。。
}
~~~
2、如果是新添加的,那就直接添加返回,否則就獲取前綴,獲取前綴后,已前綴作為新的父節點,并把之前的節點(通過insertChild方法)作為子節點,放到前綴節點的下面
>`insertChild`函數是根據`path`本身進行分割,將`/`分開的部分分別作為節點保存,形成一棵樹結構。參數匹配中的`:`和`*`的區別是,前者是匹配一個字段而后者是匹配后面所有的路徑。

### 路由匹配
~~~
這是gin.go的方法
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
`// 這里使用了對象池`
c := engine.pool.Get().(*Context)
// 這里有一個細節就是Get對象后做初始化
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c) //處理HTTP請求的函數
engine.pool.Put(c) // 處理完請求后將對象放回池子
}
~~~
~~~
func (engine *Engine) handleHTTPRequest(c *Context) {
。。。。
// Find root of the tree for the given HTTP method
// 根據請求方法找到對應的路由樹
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
// 在路由樹中根據path查找,主要的方法
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next() // 執行函數鏈條
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
。。。。。
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
~~~
>路由匹配是由節點的`getValue`方法實現的。`getValue`根據給定的路徑(鍵)返回`nodeValue`值,
保存注冊的處理函數和匹配到的路徑參數數據。
如果找不到任何處理函數,則會嘗試TSR(尾隨斜杠重定向)。
步驟:
調用 handleHTTPRequest 函數,通過getValue ,獲取方法樹的節點,沒有就報錯,有就遍歷節點上的handers(切片類型,next遍歷中間件和方法)
## gin框架中間件詳解
gin框架涉及中間件相關有4個常用的方法,它們分別是`c.Next()`、`c.Abort()`、`c.Set()`、`c.Get()`。
### gin.Default()和gin.New()
本質上,起到的作用是一樣的,都是初始化engine引擎
default是調用了new,并且use了兩個中間件
#### Default
~~~
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
~~~
#### New
~~~
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9), //初始化路由樹,get/post/put 按類型分
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
engine.pool.New = func() any { //這里是context的臨時對象池
return engine.allocateContext()
}
return engine
}
~~~
### 流程分析
1、從default,找到Use方法,這是注冊中間件的方法
2、
~~~
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...) // 實際上調用的是RouterGroup的Use函數
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
~~~
從下方的代碼可以看出,注冊中間件其實就是將中間件函數追加到`group.Handlers`中:
~~~
RouterGroup的Use函數
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
~~~
注冊路由時會將對應路由的函數和之前的中間件函數結合到一起:
~~~
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()
}
~~~
結合操作的函數內容如下,注意觀察這里是如何實現拼接兩個切片得到一個新切片的
~~~
const abortIndex int8 = math.MaxInt8 >> 1 //相當于除以2
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize) //`type``HandlersChain []HandlerFunc`
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
~~~
### gin 中間件的原理
洋蔥模型,通過next()和abort()
next():后面代碼先不執行(本方法掛起等待),先調用其他中間件(類似指針)
abort():組織掛起,后面的不再執行,直接返回
next:通過索引遍歷`HandlersChain`鏈條,從而實現依次調用該路由的每一個函數
Abor:直接把index設置為最大值,中斷遍歷
~~~
func (c *Context) Abort() {
c.index = abortIndex
~~~
### c.Set()/c.Get()
`c.Set()`和`c.Get()`這兩個方法多用于在多個函數之間通過`c`傳遞數據的,比如我們可以在認證中間件中獲取當前請求的相關信息(userID等)通過`c.Set()`存入`c`,然后在后續處理業務邏輯的函數中通過`c.Get()`來獲取當前請求的用戶。`c`就像是一根繩子,將該次請求相關的所有的函數都串起來了

### 多次綁定 報錯eof
- Go準備工作
- 依賴管理
- Go基礎
- 1、變量和常量
- 2、基本數據類型
- 3、運算符
- 4、流程控制
- 5、數組
- 數組聲明和初始化
- 遍歷
- 數組是值類型
- 6、切片
- 定義
- slice其他內容
- 7、map
- 8、函數
- 函數基礎
- 函數進階
- 9、指針
- 10、結構體
- 類型別名和自定義類型
- 結構體
- 11、接口
- 12、反射
- 13、并發
- 14、網絡編程
- 15、單元測試
- Go常用庫/包
- Context
- time
- strings/strconv
- file
- http
- Go常用第三方包
- Go優化
- Go問題排查
- Go框架
- 基礎知識點的思考
- 面試題
- 八股文
- 操作系統
- 整理一份資料
- interface
- array
- slice
- map
- MUTEX
- RWMUTEX
- Channel
- waitGroup
- context
- reflect
- gc
- GMP和CSP
- Select
- Docker
- 基本命令
- dockerfile
- docker-compose
- rpc和grpc
- consul和etcd
- ETCD
- consul
- gin
- 一些小點
- 樹
- K8s
- ES
- pprof
- mycat
- nginx
- 整理后的面試題
- 基礎
- Map
- Chan
- GC
- GMP
- 并發
- 內存
- 算法
- docker