企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[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