前言

本文转载至 https://www.liwenzhou.com/posts/Go/read_gin_sourcecode/ 可以直接去原文看, 比我这里直观

我这里只是略微的修改

正文

gin的路由实现

使用 Radix Tree , 简洁版的前缀树

前缀树

别名: 字典树 / 单词查找树 / 键树

为什么使用前缀树

  • url是有限的,不可能无限长

  • url是有规律的

  • url是一级一级的, restful 更是如此

比如博客有的是按年和月分割 /2020/3/aaaa.html /2020/3/bbbb.html 此时使用前缀树更合适

gin的路由树

基数树/PAT位树, 是一种更节省空间的前缀树, 对于基数树的每个节点,如果该节点是唯一的子树的话,就和父节点合并。

  • 越常匹配的前缀, 权重越大
  • 因为前缀树的构建模式导致越长的路径定位的时间越长, gin在注册路由时越长的路由排的越前, 如果最长的节点能优先匹配, 那么路由匹配所花的时间不一定比短路由更长

gin首先按照请求类型(POST/GET/...), 分为多个PAT树, 每个PAT树存储这个请求类型下面注册的路由, 路由又根据权重进行排序

路由树节点

路由树由一个个节点组成, gin的路由树节点由结构体 node 表示, 其构造结构如下

// tree.go

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
}

请求的方法树

在gin的路由中, 每一个 HTTP Method (GET/POST/PUT/....) 都对应了一棵PAT树, 在注册路由时会调用 addRoute 函数

// gin.go
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { // 获取请求方法对应的树
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)
}

而在gin中, 每一个 Method 对应的树关系时是存放在一个切片中, engine.trees

的类型是 methodTrees , 其定义如下

type methodTree struct {
method string
root *node
} type methodTrees []methodTree // slice

engine.trees.get 方法如下,(就是for循环)

func (trees methodTrees) get(method string) *node {
for _, tree := range trees {
if tree.method == method {
return tree.root
}
}
return nil
}

使用切片而不是使用map来存储, 可能是考虑到节省内存, 而且HTTP请求一共就9种, 使用切片也比较合适, 效率也高, 初始化在gin的 engine

func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
// liwenzhou.com ...
// 初始化容量为9的切片(HTTP1.1请求方法共9种)
trees: make(methodTrees, 0, 9),
// liwenzhou.com...
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}

路由匹配

当新的请求进入gin时, 会先经过函数 ServeHTTP

// 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) // 处理完请求后将对象放回池子
}

ServeHTTP 调用 handleHTTPRequest 函数(节选)

// gin.go
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)
}

大致为先COPY一份路由的切片, 先找到与该请求对应的请求类型, 然后在这个请求类型的路由树种使用 getValue 方法查找对应的路由, 没有则返回404

gin框架的路由源码解析的更多相关文章

  1. Thinkphp6源码分析之解析,Thinkphp6路由,Thinkphp6路由源码解析,Thinkphp6请求流程解析,Thinkphp6源码

    Thinkphp6源码解析之分析 路由篇-请求流程 0x00 前言: 第一次写这么长的博客,所以可能排版啊,分析啊,什么的可能会比较乱.但是我大致的流程已经觉得是说的够清楚了.几乎是每行源码上都有注释 ...

  2. NIO框架之MINA源码解析(五):NIO超级陷阱和使用同步IO与MINA通信

    1.NIO超级陷阱 之所以说NIO超级陷阱,就是因为我在本系列开头的那句话,因为使用缺陷导致客户业务系统瘫痪.当然,我对这个问题进行了很深的追踪,包括对MINA源码的深入了解,但其实之所以会出现这个问 ...

  3. Mybaits 源码解析 (十)----- 全网最详细,没有之一:Spring-Mybatis框架使用与源码解析

    在前面几篇文章中我们主要分析了Mybatis的单独使用,在实际在常规项目开发中,大部分都会使用mybatis与Spring结合起来使用,毕竟现在不用Spring开发的项目实在太少了.本篇文章便来介绍下 ...

  4. NIO框架之MINA源码解析(转)

    http://blog.csdn.net/column/details/nio-mina-source.html http://blog.csdn.net/chaofanwei/article/det ...

  5. NIO框架之MINA源码解析(四):粘包与断包处理及编码与解码

    1.粘包与段包 粘包:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾.造成的可能原因: 发送端需要等缓冲区满才发送出去,造成粘包 接收 ...

  6. 解析分布式应用框架Ray架构源码

    摘要:Ray的定位是分布式应用框架,主要目标是使能分布式应用的开发和运行. Ray是UC Berkeley大学 RISE lab(前AMP lab) 2017年12月 开源的新一代分布式应用框架(刚发 ...

  7. SpringBoot 源码解析 (九)----- Spring Boot的核心能力 - 整合Mybatis

    本篇我们在SpringBoot中整合Mybatis这个orm框架,毕竟分析一下其自动配置的源码,我们先来回顾一下以前Spring中是如何整合Mybatis的,大家可以看看我这篇文章Mybaits 源码 ...

  8. tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解

    TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客.如果觉得本篇文章对您有所帮助.点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) ...

  9. Gin框架源码解析

    Gin框架源码解析 Gin框架是golang的一个常用的web框架,最近一个项目中需要使用到它,所以对这个框架进行了学习.gin包非常短小精悍,不过主要包含的路由,中间件,日志都有了.我们可以追着代码 ...

随机推荐

  1. nginx学习之——虚拟主机配置

    例子1: 基于域名的虚拟主机 server { listen 80;  #监听端口 server_name a.com; #监听域名 location / { root /var/www/a.com; ...

  2. 【python接口自动化】- 使用json及jsonpath转换和提取数据

    前言 ​ JSON(JavaScript Object Notation)是一种轻量级的数据交换格式.它可以让人们很容易的进行阅读和编写,同时也方便了机器进行解析和生成,适用于进行数据交互的场景,比如 ...

  3. 10分钟快速入门vue.js

    Vue.js是一个轻巧.高性能.可组件化的MVVM库,一套用于构建用户界面的渐进式框架,上手简单,兼容强大. 官方文档:https://cn.vuejs.org/v2/guide/ 下面我们就直接来使 ...

  4. VS2015配置海康威视工业相机SDK二次开发

    1.概述:工业相机SDK是用于控制相机的一个独立组件,支持获取实时图像数据.配置参数.对图像进行后续处理等功能.工业相机SDK兼容GigE Vision协议.USB3 Vision协议.Camera ...

  5. Java8的StreamAPI常用方法总结

    目录 什么是Stream? Stream的创建 测试API 新建测试数据 findFirst.findAny anyMatch.noneMatch filter max.count peek.map ...

  6. MySQL锁:02.InnoDB锁

    目录 InnoDB锁 InnoDB行锁实现机制 InnoDB隐式.显式锁 InnoDB锁类型 共享锁 排他锁 意向锁 InnoDB锁兼容性 InnoDB行锁范围.粒度 InnoDB行锁粒度一览 意向插 ...

  7. bp VNext 入门——让ABP跑起来

    因好多群友@我说,ABP他们简单的了解了下,按照官方的教程一路下来跑不起来(倒在了入门的门口),才有了此文. 此文结合官方文档,一步一步带领大家让ABP跑起来(跨过门口). 建议大家一步一步实际动手操 ...

  8. 洛谷题解 P1051 【谁拿了最多奖学金】

    其实很水 链接: P1051 [谁拿了最多奖学金] 注意: 看好信息,不要看漏或看错因为信息很密集 AC代码: 1 #include<bits/stdc++.h>//头文件 2 using ...

  9. 神奇的 SQL 之 HAVING → 容易被轻视的主角

    开心一刻 一天,楼主和隔壁小男孩一起坐电梯,中途进来一位高挑的美女,她牵着一条雪白的贵宾犬 小男孩看着这条雪白的贵宾犬,甚是喜欢,说道:阿姨,我能摸下这个狗狗吗? 美女:叫姐姐 小男孩低头看了下贵宾犬 ...

  10. ASP.NET Core 3.1使用log4net/nlog/Serilog记录日志

    Serilog中的结构化日志支持非常好,而且配置简便.我能够比其他任何人更轻松地启动和运行Seirlog.Serilog中的日志可以发送到很多目的地.Serilog称这些东西为"接收器&qu ...