当前位置: 首页 > news >正文

Gin 源码概览 - 路由

本文基于gin 1.1 源码解读
https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip

1. 注册路由

我们先来看一段gin代码,来看看最终得到的一颗路由树长啥样

func TestGinDocExp(t *testing.T) {engine := gin.Default()engine.GET("/api/user", func(context *gin.Context) {fmt.Println("api user")})engine.GET("/api/user/info/a", func(context *gin.Context) {fmt.Println("api user info a")})engine.GET("/api/user/information", func(context *gin.Context) {fmt.Println("api user information")})engine.Run()
}

看起来像是一颗前缀树,我们后面再仔细深入源码

1.1 gin.Default

gin.Default() 返回了一个Engine 结构体指针,同时添加了2个函数,LoggerRecovery

func Default() *Engine {engine := New()engine.Use(Logger(), Recovery())return engine
}

New方法中初始化了 Engine,同时还初始化了一个RouterGroup结构体,并将Engine赋值给RouterGroup,相当于互相套用了

func New() *Engine {engine := &Engine{RouterGroup: RouterGroup{Handlers: nil,  // 业务handle,也可以是中间件basePath: "/",  // 根地址root:     true, // 根路由},RedirectTrailingSlash:  true,RedirectFixedPath:      false,HandleMethodNotAllowed: false,ForwardedByClientIP:    true,trees:                  make(methodTrees, 0, 9), // 路由树}engine.RouterGroup.engine = engine// 这里使用pool来池化Context,主要目的是减少频繁创建和销毁对象带来的内存分配和垃圾回收的开销engine.pool.New = func() interface{} {   return engine.allocateContext()}return engine
}

在执行完gin.Defualt后,gin的内容,里面已经默认初始化了2个handles

1.2 Engine.Get

在Get方法内部,最终都是调用到了group.handle方法,包括其他的POST,DELETE等

  • group.handle
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {// 获取绝对路径, 将group中的地址和当前地址进行组合absolutePath := group.calculateAbsolutePath(relativePath)// 将group中的handles(Logger和Recovery)和当前的handles合并handlers = group.combineHandlers(handlers)   // 核心在这里,将handles添加到路由树中group.engine.addRoute(httpMethod, absolutePath, handlers)return group.returnObj()
}
  • group.engine.addRoute
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {// 通过遍历trees中的内容,判断是否在这之前,同一个http方法下已经添加过路由// trees 是一个[]methodTree  切片,有2个字段,method 表示方法,root 表示当前节点,是一个node结构体root := engine.trees.get(method)  // 如果这是第一个路由则创建一个新的节点if root == nil {root = new(node)engine.trees = append(engine.trees, methodTree{method: method, root: root})}root.addRoute(path, handlers)
}
  • root.addRoute(path, handlers)

这里的代码比较多,其实大家可以简单的认为就是将handlepath进行判断,注意这里的path不是一个完整的注册api,而是去掉了公共前缀后的那部分字符串。

func (n *node) addRoute(path string, handlers HandlersChain) {fullPath := path               // 存储当前节点的完整路径n.priority++                   // 优先级自增1numParams := countParams(path) // 统计当前节点的动态参数个数// non-empty tree,非空路由树if len(n.path) > 0 || len(n.children) > 0 {walk:for {// Update maxParams of the current node// 统计当前节点及子节点中最大数量的参数个数// maxParams 可以快速判断当前节点及其子树是否能匹配包含一定数量参数的路径,从而加速匹配过程。(GPT)if numParams > n.maxParams {n.maxParams = numParams}// Find the longest common prefix.// This also implies that the common prefix contains no ':' or '*'// since the existing key can't contain those chars.// 计算最长公共前缀i := 0max := min(len(path), len(n.path))for i < max && path[i] == n.path[i] {i++}// Split edge,当前路径和节点路径只有部分重叠,且存在分歧if i < len(n.path) {// 将后缀部分创建一个新的节点,作为当前节点的子节点。child := node{path:      n.path[i:],wildChild: n.wildChild,indices:   n.indices,children:  n.children,handlers:  n.handlers,priority:  n.priority - 1,}// Update maxParams (max of all children)// 路径分裂时,确保当前节点及子节点中是有最大的maxParamsfor i := range child.children {if child.children[i].maxParams > child.maxParams {child.maxParams = child.children[i].maxParams}}n.children = []*node{&child}// []byte for proper unicode char conversion, see #65n.indices = string([]byte{n.path[i]})n.path = path[:i]n.handlers = niln.wildChild = false}// Make new node a child of this nodeif i < len(path) {path = path[i:] // 获取当前节点中非公共前缀的部分// 当前节点是一个动态路径// TODO 还需要好好研究一下if n.wildChild {n = n.children[0]n.priority++// Update maxParams of the child nodeif numParams > n.maxParams {n.maxParams = numParams}numParams--// Check if the wildcard matches// 确保新路径的动态部分与已有动态路径不会冲突。if len(path) >= len(n.path) && n.path == path[:len(n.path)] {// check for longer wildcard, e.g. :name and :namesif len(n.path) >= len(path) || path[len(n.path)] == '/' {continue walk}}panic("path segment '" + path +"' conflicts with existing wildcard '" + n.path +"' in path '" + fullPath + "'")}// 获取非公共前缀部分的第一个字符c := path[0]// slash after param// 当前节点是动态参数节点,且最后一个字符时/,同时当前节点还只有一个字节if n.nType == param && c == '/' && len(n.children) == 1 {n = n.children[0]n.priority++continue walk}// Check if a child with the next path byte existsfor i := 0; i < len(n.indices); i++ {if c == n.indices[i] {i = n.incrementChildPrio(i)n = n.children[i]continue walk}}// Otherwise insert itif c != ':' && c != '*' {// []byte for proper unicode char conversion, see #65n.indices += string([]byte{c})child := &node{maxParams: numParams,}n.children = append(n.children, child)// 增加当前子节点的优先级n.incrementChildPrio(len(n.indices) - 1)n = child}n.insertChild(numParams, path, fullPath, handlers)return} else if i == len(path) { // Make node a (in-path) leafif n.handlers != nil {panic("handlers are already registered for path ''" + fullPath + "'")}n.handlers = handlers}return}} else { // Empty treen.insertChild(numParams, path, fullPath, handlers)n.nType = root}
}

1.3 Engine.Run

func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()// 处理一下web的监听地址address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)// 最终还是使用http来启动了一个web服务err = http.ListenAndServe(address, engine)return
}

2. 路由查找

在上一篇文章中介绍了,http 的web部分的实现,http.ListenAndServe(address, engine) 在接收到请求后,最终会调用engineServeHTTP方法

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {// 从context池中获取一个Contextc := engine.pool.Get().(*Context)  // 对Context进行一些初始值操作,比如赋值w和reqc.writermem.reset(w)c.Request = reqc.reset()// 最终进入这个方法来处理请求engine.handleHTTPRequest(c)// 处理结束后将Conetxt放回池中,供下一次使用engine.pool.Put(c)
}

2.1 engine.handleHTTPRequest()

func (engine *Engine) handleHTTPRequest(context *Context) {httpMethod := context.Request.Method  // 当前客户端请求的http 方法path := context.Request.URL.Path // 查询客户端请求的完整请求地址// Find root of the tree for the given HTTP methodt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method == httpMethod {root := t[i].root// Find route in treehandlers, params, tsr := root.getValue(path, context.Params)if handlers != nil {context.handlers = handlers  // 所有的handles 请求对象context.Params = params      // 路径参数,例如/api/user/:id , 此时id就是一个路径参数context.Next()  			 // 执行所有的handles方法context.writermem.WriteHeaderNow()return}}}// 这里客户端请求的地址没有匹配上,同时检测请求的方法有没有注册,若没有注册过则提供请求方法错误if engine.HandleMethodNotAllowed {for _, tree := range engine.trees {if tree.method != httpMethod {if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil {context.handlers = engine.allNoMethodserveError(context, 405, default405Body)return}}}}// 路由地址没有找到context.handlers = engine.allNoRouteserveError(context, 404, default404Body)
}

2.2 context.Next()

这个方法在注册中间件的使用会使用的较为频繁

func (c *Context) Next() {// 初始化的时候index 是 -1c.index++s := int8(len(c.handlers))for ; c.index < s; c.index++ {c.handlers[c.index](c)  // 依次执行注册的handles}
}

我们来看一段gin 是执行中间件的流程

func TestGinMdls(t *testing.T) {engine := gin.Default()engine.Use(func(ctx *gin.Context) {fmt.Println("请求过来了")// 这里可以做一些横向操作,比如处理用户身份,cors等ctx.Next()fmt.Println("返回响应")})engine.GET("/index", func(context *gin.Context) {fmt.Println("index")})engine.Run()
}

curl http://127.0.0.1:8080/index

通过响应结果我们可以分析出,请求过来时,先执行了Use中注册的中间件,然后用户调用ctx.Next() 可以执行下一个handle,也就是用户注册的/index方法的handle

相关文章:

Gin 源码概览 - 路由

本文基于gin 1.1 源码解读 https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip 1. 注册路由 我们先来看一段gin代码&#xff0c;来看看最终得到的一颗路由树长啥样 func TestGinDocExp(t *testing.T) {engine : gin.Default()engine.GET("/api/user", f…...

第6章 ThreadGroup详细讲解(Java高并发编程详解:多线程与系统设计)

1.ThreadGroup 与 Thread 在Java程序中&#xff0c; 默认情况下&#xff0c; 新的线程都会被加入到main线程所在的group中&#xff0c; main线程的group名字同线程名。如同线程存在父子关系一样&#xff0c; Thread Group同样也存在父子关系。图6-1就很好地说明了父子thread、父…...

CentOS 7乱码问题如何解决?

1.使用超级用户操作: sudo su2.修改i18n配置文件&#xff1a; vi /etc/sysconfig/i18n将文件修改或添加为以下内容&#xff1a; LANG"zh_CN.UTF8" LC_ALL"zh_CN.UTF8"保存并退出&#xff08;按Esc键&#xff0c;输入:wq&#xff0c;然后回车&#xff09…...

JavaScript语言的多线程编程

JavaScript语言的多线程编程 JavaScript是一种广泛使用的编程语言&#xff0c;主要用于网页开发。由于其单线程的特性&#xff0c;JavaScript 一直以来都有“无法进行多线程编程”的印象。尽管如此&#xff0c;随着技术的发展&#xff0c;JavaScript也逐渐引入了多线程的概念&…...

OpenSeaOtter使用手册-变更通知和持续部署

我们在OpenSeaOtter Server 0.1.1版本增加的镜像变更通知功能。通过镜像变更通知和OpenSeaOtter Agent就可以轻松获得持续部署能力。 镜像变更通知是通过push的方式下发到Agent的&#xff0c;Agent所在机器不需要外网地址。在Agent收到镜像变更通知后&#xff0c;就会调用对应的…...

(2)STM32 USB设备开发-USB虚拟串口

例程&#xff1a;STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com 本篇为USB虚拟串口教程&#xff0c;没有知识&#xff0c;全是实操&#xff0c;按照步骤就能获得一个STM32的USB虚拟串口。本例子是在野火F103MINI开发板上验证的&#xff0c;如果代码中出现一些外设的…...

他把智能科技引入现代农业领域

江苏田倍丰农业科技有限公司&#xff08;以下简称“田倍丰”&#xff09;是一家专注于粮油种植的农业科技公司&#xff0c;为拥有300亩以上田地的大户提供全面的解决方案。田倍丰通过与当地政府合作&#xff0c;将土地承包给大户&#xff0c;并提供农资和技术&#xff0c;实现利…...

ingress-nginx代理tcp使其能外部访问mysql

一、helm部署mysql主从复制 helm repo add bitnami https://charts.bitnami.com/bitnami helm repo updatehelm pull bitnami/mysql 解压后编辑values.yaml文件&#xff0c;修改如下&#xff08;storageclass已设置默认类&#xff09; 117 ## param architecture MySQL archit…...

麒麟操作系统服务架构保姆级教程(十三)tomcat环境安装以及LNMT架构

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 之前咱们学习了LNMP架构&#xff0c;但是PHP对于技术来说确实是老掉牙了&#xff0c;PHP的市场占有量越来越少了&#xff0c;我认识一个10年的PHP开发工程师&#xff0c;十年工资从15k到今天的6k&am…...

亚博microros小车-原生ubuntu支持系列:4-手部检测

一 准备工作 在学习手部检测之前&#xff0c;有2个准备工作。 1 确保小车的摄像头能显示画面 参见&#xff1a;亚博microros小车-原生ubuntu支持系列&#xff1a;2-摄像头控制-CSDN博客 启动图传代理&#xff1a; docker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm …...

关于回调函数(callback)

简介 在C中&#xff0c;回调函数是一种常见的编程技术&#xff0c;它允许你将一个函数作为参数传递给另一个函数&#xff0c;并在适当的时候调用它。回调函数通常用于事件处理、异步编程和模块化设计中。 1、函数指针&#xff1a;在C中&#xff0c;回调函数通常是通过函数指针…...

Linux Bash 中使用重定向运算符的 5 种方法

注&#xff1a;机翻&#xff0c;未校。 Five ways to use redirect operators in Bash Posted: January 22, 2021 | by Damon Garn Redirect operators are a basic but essential part of working at the Bash command line. See how to safely redirect input and output t…...

硬件作品3----STM32F103RCT6最小系统板MCU配置

参考文章&#xff1a;对stm32F103RCT6原理图解析&#xff08;详细&#xff09;-CSDN博客 本想绘制稍微复杂一些的电路&#xff0c;但是出现很多问题&#xff0c;因此先绘制一块最小系统板进行原理、绘制方法的验证。 设计难度&#xff1a;★ 适合人群&#xff1a;初学者 一、…...

人脸识别打卡系统--基于QT(附源码)

逃离舒适区 项目源代码放在我的仓库中&#xff0c;有需要自取 项目地址 https://gitcode.com/hujiahangdewa/Face_recognition.git 文章目录 一、项目结构分析二、服务器的搭建三、客户端的搭建四、人脸识别库的申请五、基于人脸识别库的识别判断六、QT人脸识别----调用百度ai…...

【深度学习入门】深度学习知识点总结

一、卷积 &#xff08;1&#xff09;什么是卷积 定义&#xff1a;特征图的局部与卷积核做内积的操作。 作用&#xff1a;① 广泛应用于图像处理领域。卷积操作可以提取图片中的特征&#xff0c;低层的卷积层提取局部特征&#xff0c;如&#xff1a;边缘、线条、角。 ② 高层…...

通过视觉语言模型蒸馏进行 3D 形状零件分割

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01;对应英文要求比较高&#xff0c;特此说明&#xff01; Abstract This paper proposes a cross-modal distillation framework, PartDistill, which transfers 2D knowledge from vision-language models …...

机器学习10-解读CNN代码Pytorch版

机器学习10-解读CNN代码Pytorch版 我个人是Java程序员&#xff0c;关于Python代码的使用过程中的相关代码事项&#xff0c;在此进行记录 文章目录 机器学习10-解读CNN代码Pytorch版1-核心逻辑脉络2-参考网址3-解读CNN代码Pytorch版本1-MNIST数据集读取2-CNN网络的定义1-无注释版…...

微服务学习-Gateway 统一微服务入口

1. 微服务为什么需要 API 网关&#xff1f; 1.1. 在微服务架构中&#xff0c;通常一个系统会被拆分为多个微服务&#xff0c;面对多个微服务客户端应该如何去调用呢&#xff1f; 如果根据每个微服务的地址发起调用&#xff0c;存在如下问题&#xff1a; 客户端多次请求不同的…...

2025寒假备战蓝桥杯02---朴素二分查找升级版本的学习+分别求解左右端点

文章目录 1.朴素二分查找的升级版2.查找左端点3.查找右端点4.代码的编写 1.朴素二分查找的升级版 和之前介绍的这个二分查找相比&#xff0c;我觉得这个区别就是我们的这个二分查找需要找到的是一个区间&#xff0c;而不是这个区间里面的某一个元素的位置&#xff1b; 2.查找…...

PHP语言的软件工程

PHP语言的软件工程 引言 软件工程是计算机科学中的一个重要分支&#xff0c;它涉及软件的规划、开发、测试和维护。在现代开发中&#xff0c;PHP作为一种流行的服务器端脚本语言&#xff0c;广泛应用于网页开发和各种企业应用中。本文将深入探讨PHP语言在软件工程中的应用&am…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

毫米波雷达基础理论(3D+4D)

3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文&#xff1a; 一文入门汽车毫米波雷达基本原理 &#xff1a;https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...