概要
目录
主要讲述Gin框架路由和中间件的详细解释。本文章将从Radix树(基数树或者压缩前缀树)、请求处理、路由方法树、路由的注册与匹配以及中间件的详细解释这五大部分入手。
Gin 框架 路由使用前缀树,路由注册的过程就是构造前缀树的过程,路由匹配的过程是查找前缀树 的过程。
Gin框架使用的是定制版本的httprouter。我们简单介绍下关于httprouer框架。
Httprouter是一个高性能路由分发器,它负责将不同方法的多个路径分别注册到各个handle函数。当收到请求时,Httprouter会快速查找请求的路径是否有相对应的处理函数,并执行相应的业务逻辑处理。
Httprouter使用基数树(radix tree)来进行高效的路径查找,这种数据结构适用于需要快速查找和匹配的场景。此外,Httprouter还支持两种通配符匹配,使得路由规则更加灵活和强大。
它为Gin提供了高效、灵活的路由匹配功能,使得Gin成为了一个高性能的Web框架。同时,Httprouter也是Go语言中广泛使用的一个路由库,独立于Gin框架,可以被其他Go语言的Web框架所使用。
Radix Tree 是一种更节省空间的前缀树。对于基数树的每个节点,如果该节点是唯一的子树的话,就和父节点合并。
Radix Tree 可以被认为是一个简洁版的前缀树。我们注册路由的过程就是在构造前缀树的过程,具有公共前缀的节点也共享一个公共父节点。
如下图所示:GET方法对应的路由树。

URL具有层级结构,并且都是有限的字符组,所以有很多常见的前缀。这样是的我们很容易将路由简化为更小的问题。
路由器为每种请求方法管理一颗单独的树。
- type node struct {
- // 节点路径,比如上面的s,earch,和upport
- path string
- // 和children字段对应, 保存的是分裂的分支的第一个字符
- // 例如search和support, 那么s节点的indices对应的"eu"
- // 代表有两个分支, 分支的首字母分别是e和u
- indices string
- // 儿子节点
- children []*node
- // 处理函数链条(切片)
- handlers HandlersChain
- // 优先级,子节点、子子节点等注册的handler数量
- priority uint32
- // 节点类型,包括static, root, param, catchAll
- // static: 静态节点(默认),比如上面的s,earch等节点
- // root: 树的根节点
- // catchAll: 有*匹配的节点
- // param: 参数节点
- nType nodeType
- // 路径上最大参数个数
- maxParams uint8
- // 节点是否是参数节点,比如上面的:post
- wildChild bool
- // 完整路径
- fullPath string
- }
每一个HTTP method都对应一颗radix树,我们注册路由的时候都会调用addRoute函数。函数含义我们可以看到在注册路由的时候都是先根据请求方法获取对应的树,也就是gin框架会为每一个请求方法创建一颗对应的树。只不过需要注意一个细节是gin框架中请求方法对应树关系并不是使用map而是使用的切片,engine.trees的类型是methodThrees。
- type methodTree struct {
- method string
- root *node
- }
-
- type methodTrees []methodTree // slice
-
- func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
- // liwenzhou.com...
-
- // 获取请求方法对应的树
- root := engine.trees.get(method)
- if root == nil {
-
- // 如果没有就创建一个
- root = new(node)
- root.fullPath = "/"
- engine.trees = append(engine.trees, methodTree{method: method, root: root})
- }
- root.addRoute(path, handlers)
- }
考点 :为什么用切片存储请求方法树,而不用map?
节省内存。HTTP请求方法数量也就9种,用切片存储和查询效率足够。只需要做出一次性内存申请即可。
路由注册函数:
路由匹配:
-
- 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) {
- // 根据请求方法找到对应的路由树
- t := engine.trees
- for i, tl := 0, len(t); i < tl; i++ {
- if t[i].method != httpMethod {
- continue
- }
- root := t[i].root
- // 在路由树中根据path查找
- value := root.getValue(rPath, c.Params, unescape)
- if value.handlers != nil {
- c.handlers = value.handlers
- c.Params = value.params
- c.fullPath = value.fullPath
- c.Next() // 执行函数链条
- c.writermem.WriteHeaderNow()
- return
- }
-
-
- c.handlers = engine.allNoRoute
- serveError(c, http.StatusNotFound, default404Body)
- }
路由匹配是由节点的GetValue方法实现的。getValue根据给定的路径返回nodeValue值,里面保存的处理函数和匹配到的路径参数数据。如果找不到任何处理函数,会尝试TSR(尾随斜杠重定向)。
中间件是指处理HTTP请求的函数或组件,通常用于在请求到达目标处理程序之前或之后执行一些处理逻辑。中间件在Go语言中经常被使用,因为它提供了一种可扩展和可重用的机制,用于处理身份验证、授权、日志记录、错误处理等常见的Web应用程序需求。
中间件在Go语言主要是因为它提供了一种灵活、可扩展和可重用的机制,用于处理Web应用程序中的各种需求和逻辑。
中间件优势:
gin框架中涉及中间件相关有4个常用的方法,c.Next() 、c.Abort() 、c.Set()、c.Get()
Gin中间件函数和处理函数是以切片的形式调用链条存在的,我们可以顺序调用也可以借助c.Next函数方法实现嵌套调用。
c.Set()和c.Get()这两个方法多用于在多个函数之间通过c传递数据的,比如我们可以在认证中间件中获取当前请求的相关信息(userID等)通过c.Set()存入c,然后在后续处理业务逻辑的函数中通过c.Get()来获取当前请求的用户。c就像是一根绳子,将该次请求相关的所有的函数都串起来了。
c.Abort()中断整个调用链条,从当前函数返回。