[TOC]
https://www.cnblogs.com/binHome/p/13411878.html
![](https://img.kancloud.cn/b8/91/b8914c0d9c8f6b61f32151d3dd319dd6_2261x1908.png)
### 路由树
是一颗前缀树
一共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`本身进行分割,将`/`分开的部分分别作为节点保存,形成一棵树结构。参数匹配中的`:`和`*`的区别是,前者是匹配一个字段而后者是匹配后面所有的路径。
![](https://img.kancloud.cn/46/33/4633a4e368e1115c4e5d6523fe35ae0f_800x420.gif)
### 路由匹配
~~~
这是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`就像是一根绳子,将该次请求相关的所有的函数都串起来了
![](https://img.kancloud.cn/b3/c4/b3c4ed4553b2e9d0a58b6af24f66a388_1560x640.png)
### 多次绑定 报错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