Go第三方框架--gin框架(一)

news2024/11/16 0:04:02

序言

Gin框架作为go语言使用最多的web框架,以其快速的响应速度和对复杂http路由配置的支持受到程序员和媛们的喜爱,几乎统治了web市场。但作为一名合格的程序员,要知其然更要知其所以然,不然八股文背的也没有啥意思。本着这个原则鄙人打算站在前人的大腿根上从头到尾梳理下Gin的执行流程,主要涉及两大部分:1. 服务器的建立(重点是:Gin是怎么处理和存储各种不同的路由路径和请求函数体的);2. 客户端的连接(主要涉及根据路由寻找对应函数体来执行具体业务逻辑)

1. gn框架的诞生

1.1 go 原生web框架

go 原生的 web框架 在 net/http 包里,因不是本文重点,所以只简要介绍。
net/http 主要采用 map的原理来 存储 路径和handler 其中 key 是 路径 value 是 handler ,如下图的代码

# ServeMux是一个HTTP请求多路复用器。其中 m 保存了 其请求路径和handler的映射关系。
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}
type muxEntry struct {
	h       Handler
	pattern string
}
func TestHttp(t *testing.T) {
	// 创建路由器
	mux := http.NewServeMux()
	// 设置路由规则
	mux.HandleFunc("/hello", hello)
	mux.HandleFunc("/hello/hell", hello2)
	mux.HandleFunc("/hel/ww", hello2)
	mux.HandleFunc("/helw/*ww", hello2)

	// 创建服务器
	server := &http.Server{
		Addr:         Addr,
		WriteTimeout: time.Second * 3, //超时时间
		Handler:      mux,             //路由规则  
	}
	// 监听端口并提供服务
	log.Println("Starting httpserver at " + Addr)
	err := server.ListenAndServe()
	if err != nil {
		panic(err)
		return
	}
	log.Fatal()
}

func hello(w http.ResponseWriter, r *http.Request) {
	time.Sleep(1 * time.Second)
	w.Write([]byte("bye bye ,this is httpServer"))
}

func hello2(w http.ResponseWriter, r *http.Request) {
	time.Sleep(1 * time.Second)
	w.Write([]byte("bye bye ,this is httpServer"))
}

其 建立的 map如下:
在这里插入图片描述
可以看出其确实建立了一个 map来存储 路径和handler的映射关系。采用map形式 查找的速度也比较的块。但是为啥还要采用Gin框架呢。
我们来简要梳理下我们程序员在工作过程中需要啥样的web框架吧

  1. 需要一个可以处理通配符的框架,比如这种: aa/dd* 虽然我(net/http)不支持但是我 速度快啊
  2. 需要可以处理中间件的框架 比如 对日志的处理等 虽然我(net/http)不支持但是我 速度快啊
  3. 需要 支持分组的框架 比如 v1 v2这种不同的版本 虽然我(net/http)不支持但是我 速度快啊

目前看来 net/http不适合这种复杂场景的业务逻辑 当然 Google go开发组 目的只是提供一个简小的web框架,设计目标是简单和通用。go开发组 当然也想到了 要利用开源的优势为各路大神提供大显神通的机会。问题是怎么接入呢,现实世界和虚拟世界的连接入口是 二维码。那gin框架如何接入 net/http 呢,也就是如何在重新利用它的其他功能的情况下,再进行扩展呢 你当然能想到了 这就是 interface 接口。

net/http框架中 确实是 通过实现接口来 进行 路由查找 并找到要执行的 hanlder(例如 上述代码中的hello),这样路由建立模块和路由寻址查找handler模块就可以通过不同实现来形成不同的第三方框架。建立路由模块是第三方包独自完成,而路由寻址查找模块主要是实现了 如下接口:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

这样第三方框架 就可以复用net/http 的大部分功能(包括最重要的 epoll多路复用),并通过实现 ServeHTTP 接口 来实现自己的 路由查找模块(ps: 路由建立模块需自己建立 这块功能用不到接口) ,说完了 理论 那我们再来梳理下 net/http 从 建立连接 到 ServeHTTP的调用链,来验证下上述是否是这样的。

1.2 原生 net/http的 调用链

以 TestHttp 这个函数为例,调用入口是 server.ListenAndServe() ,其调用链为

在这里插入图片描述
可以看到 其最终调用到了 ServeHTTP() 这个函数,而所有第三方框架都是实现了 这个函数。这个函数包括 根据req函数中的 url获取 handler(第三方框架提供) 、处理handler和对客户端返回结果三大块的功能。
这样第三方框架就可以 实现ServeHTTP这个函数来 实现对 handler的获取(怎么建立路由结构是第三方框架自己定义)。

1.3 gin等第三方web框架和net/http关系

让我们脱离源码 来梳理下
在这里插入图片描述
可以看到 第三方 框架 主要是头尾两个地方跟 net/http不同 中间还是需要复用net/http代码。后续 讲解会围绕这张流程图展开,其中比较重要的是 圆圈 1、2和3,1 包括 gin 的引擎 engine 其包括建立压缩前缀树的功能并实现了 serverHttp接口 形成了 3,2主要涉及了多路复用技术。
以下的 步骤 2、3、4主要涉及圆圈1 的内容,步骤5 涉及圆圈2 的内容 ,步骤6 涉及圆圈3的内容

2. gin框架简介

2.1 gin框架发展历程

gin 框架早期版本是基于julienschmidt/httprouter 发展而来,julienschmidt/httprouter是一个高性能的http请求器。但是随着gin框架的发展 它逐渐发展出了自己的 路由实现器,实现源码也部分参考 julienschmidt/httprouter 这也就是为什么好多资料都说 gin基于julienschmidt/httprouter 但是你去看它最新的源码却没发现针对 julienschmidt/httprouter的引用。
gin框架之所以运行效率高是因为采用了一种叫 Radix Tree (压缩前缀树)的结构体来存储路由路径,其是一种例如有如下路由:

/aa/bb
/aa/bd
/aa/cc
/ac/dd
/ee/ff

建立压缩前缀树 ,请思考下其建立的树是左边还是右边呢
在这里插入图片描述
gin 框架的路由树的建立 就是一步一步建立如上图所示的 压缩前缀树 ps:真是的树的节点比较复杂 但是大体步骤就是如此
那么压缩前缀树 有啥优点呢 gin 为什么使用这种结构来存储器节点呢 直觉上看 我们可以想到两点 1: 这种树形结构 查找的时间复杂度是时间复杂度为 o(k) ,k是字符串的长度 2: 压缩证明其使用的空间比较少 可以看到 路径中 有 6个a 但是 我们树节点中只有 2个。这只是我们直观看出来的,对不对呢,是否还有其他优点呢?

答案: 对,当然有其他优点,这种树 也可以用来 进行通配符的匹配 例如这种 /aa/bb/* ;还可以快速建立路由分组等。这两种优势不是本文的重点,感兴趣的同学可以自行查阅资料。
既然你说gin 路由使用的是 压缩前缀树,口说无凭 我们来验证下吧 顺便看下建立的是左边还是右边的压缩前缀树

2.2 gin框架的使用

下面示例代码为(本文后续围绕下面例子展开代码讲解):

func TestGin(t *testing.T) {
	// 创建一个默认的路由引擎
	r := gin.Default()
	// 当客户端以GET方法请求路径时,会执行后面的匿名函数
	r.GET("/aa/bb", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/aa/bb",}) })
	r.GET("/aa/bd", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/aa/bd",}) })
	r.GET("/aa/cc", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/aa/cc",}) })
	r.GET("/ac/dd", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/ac/dd",}) })
	r.GET("/ee/ff", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/ee/ff",}) })
	// 以上操作 **主要 涉及 圆圈 1**
 	
	// 启动HTTP服务,默认在0.0.0.0:8080启动服务  **涉及 net/http 框架处理主逻辑 内部 主要 调用 net/http 包**
	r.Run()

}

对上述代码 debugger 可以得到 r 这个参数的 实例 实力分析如下 其中 压缩前缀树的节点node结构体的结构如下:

type node struct {
	path      string
	indices   string
	wildChild bool
	nType     nodeType
	priority  uint32
	children  []*node // child nodes, at most 1 :param style node at the end of the array
	handlers  HandlersChain
	fullPath  string
}

现在只需关注node节点的 path 和 children 这两个参数 node结构体详情会在后续步骤介绍
2.2.1 父节点
在这里插入图片描述
可以看到 父节点 path == “/” 且有两个孩子节点

2.2.2 第二层节点

在这里插入图片描述
可以看到 第二层 节点 左边节点 path==“a” 孩子个数为2 ;右边节点 pah 是 “ee/ff” 这里就是将节点进行了压缩 ee 和 ff 不用再拆分了。因为 ff是ee的唯一的一个孩子节点 因为寻址路径唯一 所以可以向上合并,以便节省空间。

2.2.3 第三层节点

在这里插入图片描述
可以看到 左边节点 path==“a/” 孩子节点个数为2 ;右边节点 path==“c/dd”(压缩了) 孩子节点为空

2.2.4 第四层节点
在这里插入图片描述
可以看到 左边 节点 path=“b” ,其有两个孩子节点;右边节点 path=“cc” 其没有孩子节点

2.2.5 第五层 节点

在这里插入图片描述
可以看到 左边节点 path==“b” 无孩子节点 ;右边节点 path==“d” 无孩子节点

到这里我们可以看出来其确实是建立了一颗 压缩前缀树。总结下来就是: 1. 孩子节点必须大于1(否则应向上合并)2: 压缩有两层含义 第一层将 路由里面 重复的路径 进行压缩 例如 字母a 压缩后就剩2个;第二层 一个节点有一个子节点时 向上兼并 压缩空间
所以建立的前缀树是 右边的。

r.run()执行后 在浏览器输入 路径 就可以看到 对应的函数被执行(注意:默认端口是8080)结果 如图 这边主要涉及 圆圈 2–>3
在这里插入图片描述

2.3 gin框架的执行过程

梳理完毕 压缩前缀树的建立 那现在开始我们梳理下 整个 gin框架的流程图 其实主要是围绕 构建的压缩前缀树展开的 ,我个人比较愿意先学习框架使用,然后再进入细节,这样有一个提纲挈领的抓手,我们就知道这些细节在整体脉络中的位置,不至于陷进去失去了方向感。

通过1.3的图可以看到 gin 框架 大概 分为 三大部分

  1. 创建 压缩前缀树 并且 将 路由 对应的节点 按照规则 插入树节点 ---- 步骤一(圆圈1)
  2. 运行 引擎 建立 对tcp套接字的监听 这里采用多路复用技术 进行阻塞 等待链接到来 ---- 步骤二
  3. 浏览器 输入 url 进行客户端请求 这时 唤醒阻塞的程序 从圆圈2 按照箭头执行顺序 一直执行到 圆圈3,然后在圆圈3 中遍历 压缩前缀树 找到对应的 handler (对于路径 :/aa/bd 其 handler 为:func(c *gin.Context) { c.JSON(200, gin.H{"route path ": “/aa/bd”}) }) 执行后 返回结果 ---- 步骤三

3. gn框架源码–四种重要的结构体

框架一般都会采用面向对象的方式来构建,而面向对象中最重要的核心就是结构体。gin框架四种重要的结构体 分别是 Engine/RouterGroup/Node/context ,其中Engine 包含了 RouteGroup和Node 是gin框架的引擎结构体 ;Node是压缩前缀树的树节点,用来保存 压缩路径和handler,在2.2.1----2.2.5中已经做过简要介绍 ;Context 结构体官方介绍是 gin最重要的结构体 ,它允许我们在中间件之间传递变量,管理流,处理 request 请求体和 respose 响应体。可以说 gin 框架基本上是围绕着这四个结构体来操作的。

3.1 Engine 结构体
type Engine struct {
	RouterGroup   // 路由组 

	...... //  这里为了使得文章简短 一些本文讲解没用到的 属性 没有列举 感兴趣的可以自己研究下 
	// UseH2C enable h2c support.
	UseH2C bool

	// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
	ContextWithFallback bool

	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool   // 这个池化技术 用来存储 Context结构体。 它是 gin框架很重要的结构体 主要用来 处理 request和 respose 请求
	trees            methodTrees   // 方法树 针对 Get/Post/Delete 等不同请求 都生成一个树 9种请求 9种树 但实现原理都是相同的 本文只介绍 Get方法,其 包含了 Node 结构体
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
	trustedCIDRs     []*net.IPNet
}

Engine 结构体 是 gin 框架的入口,其包含了许多属性 但对于 我们学习jin框架核心执行逻辑来说,只需要知道 RouterGroup/pool/trees 这三个就行了

3.2 RouteGroup结构体
type RouterGroup struct {
	Handlers HandlersChain   // 需要处理的 handler 链,一般是 默认hanlder(例如 处理 logger和panic的handlewr)+ group组的中间件handler(例如 鉴权等)+ 用户 注册的 handler
	basePath string  // 组的基本路径
	engine   *Engine // gin 框架 引擎
	root     bool // 跟节点
}

RouterGroup 主要是用来 对路由进行操作的,包括对post/get等方法的处理。

3.3 Node 结构体
type node struct {
	path      string  // 节点路劲
	indices   string // 其子节点的 path的第一个单词 组成的 字符串 用来快速定位路径寻址时 是否走此孩子节点
	wildChild bool
	nType     nodeType // 节点类型
	priority  uint32  // 优先级 从左往右 越左侧 优先级越大 优先从左边开始 说明 左边的 重复的路径前缀比较多 同层其优先级越高 子节点越多 ,一般情况下 priority等于其直属孩子节点个数,且如果其直属孩子节点为一个 或者 为空,其 优先级 为 1
	children  []*node // child nodes, at most 1 :param style node at the end of the array
	handlers  HandlersChain  // 处理器 存储 节点 handler 链 
	fullPath  string // 全路径 fullPath 
}

node 是压缩前缀树子节点,是gin框架之所以速度快的核心原因,也是我们本篇文章重点需要介绍和理解的结构体。
对于节点属性的理解 可以对照着 标题 2.21----2.2.5来理解

3.4 context 结构体
type Context struct {
	writermem responseWriter
	Request   *http.Request
	Writer    ResponseWriter

	Params   Params
	handlers HandlersChain
	index    int8
	fullPath string

	engine       *Engine
	params       *Params
	skippedNodes *[]skippedNode

	// This mutex protects Keys map.
	mu sync.RWMutex

	// Keys is a key/value pair exclusively for the context of each request.
	Keys map[string]any

	// Errors is a list of errors attached to all the handlers/middlewares who used this context.
	Errors errorMsgs

	// Accepted defines a list of manually accepted formats for content negotiation.
	Accepted []string

	// queryCache caches the query result from c.Request.URL.Query().
	queryCache url.Values

	// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
	// or PUT body parameters.
	formCache url.Values

	// SameSite allows a server to define a cookie attribute making it impossible for
	// the browser to send this cookie along with cross-site requests.
	sameSite http.SameSite
}

context 是gin框架最重要的结构体 其包含了对 请求 和 响应的 处理逻辑,可以在中间件之间传递数据流。因不是本文的理解gin框架的需要用到的结构体,暂不做过多介绍,请自行用谷歌百度一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1545365.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

海量数据处理项目-账号微服务和流量包数据库表+索引规范(下)

海量数据处理项目-账号微服务和流量包数据库表索引规范(下) 第2集 账号微服务和流量包数据库表索引规范讲解《下》 简介:账号微服务和流量包数据库表索引规范讲解 账号和流量包的关系:一对多traffic流量包表思考点 海量数据下每…

MySQL基础(DDL,DML,DQL)

目录 一DDL 1.1数据库操作 1.1.1查询所有数据库: 1.1.2创建数据库 1.1.3 使用数据库 1.1.4 删除数据库 1.2表操作 1.2.1表操作 1.2.1.1创建表 1.2.1.1.1约束 1.2.1.1.2 数据类型 1.2.1.1.2.1 数值类型 1.2.1.1.2.2 字符串类型 1.2.1.1.2.3日期类型 1.…

win10笔记本在显示设置中不慎将主显示器禁用掉导致开机黑屏的解决方案

因为笔记本电脑的显示扩展接口有问题,所以在电脑开机之后,会误识别出几个不存在的扩展屏幕,所以我就想从显示设置中将这几个误识别出来的扩展屏幕禁用掉(不然鼠标总是移动到主屏幕边界之外的地方),在显示设…

2.8、下拉刷新与上拉加载

页面的下拉刷新与上拉加载功能在移动应用中十分常见,例如,新闻页面的内容刷新和加载。这两种操作的原理都是通过响应用户的触摸事件,在顶部或者底部显示一个刷新或加载视图,完成后再将此视图隐藏。 实现思路 以下拉刷新为例,其实现主要分成三步: 监听手指按下事件,记录…

Epic LootBox Effects

Lootbox的程式化粒子效果。 为每个效果添加稀有度效果。 (常见、不常见、罕见、史诗) 产品特点 - 25个战利品箱特效,4个稀有特效(普通、罕见、稀有、史诗) -适用于2D和3D游戏。 - 适用于移动设备和台式机。 - 包括演示场景和手册。 下载:​​Unity资源商店链接资源下载链…

C++ STL- list 的使用以及练习

目录 0.引言 1. list 介绍 2. list 使用 2.1 构造函数 2.2 list iterator 的使用 3 list capacity 4. list element access 5. list modifiers 6. list 迭代器失效 7. list 与vector 对vector 8. OJ 题讲解 删除链表的倒数第 N 个节点: 0.引言 …

绿盟《安全知识图谱技术白皮书》.PDF

共82页,包含网络安全智能化发展趋势、安全知识图谱技术框架、安全知识图谱关键技术、安全知识图谱典型应用场景、安全知识图谱技术发展趋势等。 由于篇幅限制,仅展示部分内容 完整版图谱PDF,请关公号【网络安全实战】,后台发送“…

考研数学一——概率论真题——自我总结题型整理(总分393)

系列文章目录 终于考完研了,本人考的是南京航空航天大学的仪器科学与技术,英一数一电路,以下是成绩单: 平时习惯整理自己的学习体系,以下是一个记录。 其实,每个人都应该训练,看到某一类题目…

Vue2手搓级联组件

本文的布局才用了叶落风尘大佬的一片文章&#xff0c;功能是自己加的 源代码 页面部分 <div class"h-100vh grid place-items-center"><div><div class"h-455px flex flex-col border-1px border-solid border-#eee w-885px"><div cl…

洁净环境监测相关法规指南汇总

一 洁净级别确认 1. 用于生产无菌药品的洁净室和洁净空气设备如单向流系统&#xff08;UDAF&#xff09;、限制进入屏障系统&#xff08;RABS&#xff09;和隔离器&#xff0c;应根据所需环境特性进行确认。生产操作需要在适当洁净度级别的环境中进行&#xff0c;以降低粒子或…

【漏洞复现】通天星CMSV6-inspect_file-upload文件上传漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

Android实现一周时间早中晚排班表

我们要做一个可以动态添加,修改一周早中晚时间排班表&#xff0c;需求图如下&#xff1a; one two 过程具体在这里不描述了&#xff0c;具体查看&#xff0c;https://github.com/yangxiansheng123/WorkingSchedule 上传数据格式&#xff1a; {"friday_plan":"…

个人周总结

个人周总结&#xff1a; 对于总体方向目标的完成&#xff0c;完成的不是很好&#xff0c;这周内的很多天都在游离&#xff0c;就是事情可能有需要做的&#xff0c;但是动力不很足&#xff0c;期间就是有点摆烂了。后面及时调整调整吧&#xff0c;有这种情况&#xff0c;一个原…

无人驾驶(移动机器人)路径规划之A star(Tie Breaker)算法及其matlab实现

在自动驾驶与移动机器人路径规划时&#xff0c;必定会用到经典的算法A star。下面是我未加入与加入Tie Breaker 的matlab实现效果。可以发现加入Tie Breaker之后效果明显改善。 目录 一、效果比较 1.未加入Tie Breaker&#xff08;黑色为障碍物&#xff0c;菱形绿色为目标点…

婴儿洗衣机有必要买吗?四大满分婴儿洗衣机入围榜单

宝宝衣服的清洗对父母来说都很重要&#xff0c;所以挑选一款适合宝宝的波轮洗衣机显得尤为重要。也许有许多人认为&#xff0c;为婴儿购买独立的洗衣机是不必要的&#xff0c;但是你是否了解呢&#xff1f;新生婴儿的肌肤要比成人更脆弱&#xff0c;更易受到感染而受到伤害&…

vue3.0 + ts + eslint报错:error Parsing error: ‘>‘ expected

eslint报错 这里加上对应的 eslint配置即可&#xff1a; parser: vue-eslint-parser, parserOptions: {parser: "typescript-eslint/parser",ecmaVersion: 2020,sourceType: module, }具体如下&#xff1a; module.exports {parser: vue-eslint-parser,parserOpti…

浏览器输入框自动填充默认样式移除

文章目录 浏览器输入框自动填充默认样式移除问题现象以及探索过程尝试代码有效关键代码&#xff08;解决方案&#xff09; 浏览器输入框自动填充默认样式移除 问题现象以及探索过程 &#xff08;在 uniapp 语法下&#xff09;本文的写法在 Edge 119.0.2151.58 (正式版本) (64 …

第九届蓝桥杯大赛个人赛省赛(软件类)真题C 语言 A 组-乘积尾零

solution 找末尾0的个数&#xff0c;即找有多少对2和5 >问题等价于寻找所给数据中&#xff0c;有多少个2和5的因子&#xff0c;较少出现的因子次数即为0的个数 #include <iostream> using namespace std; int main() {// 请在此输入您的代码printf("31");…

docker配置镜像加速后容器和镜像消失

一、问题描述 根据阿里云给docker配置镜像加速器 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://gt6j98xi.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl rest…

大模型落地实战指南:从选择到训练,深度解析显卡选型、模型训练技、模型选择巧及AI未来展望---打造AI应用新篇章

大模型落地实战指南&#xff1a;从选择到训练&#xff0c;深度解析显卡选型、模型训练技、模型选择巧及AI未来展望—打造AI应用新篇章 0.前言大模型发展史 早期阶段&#xff08;1950s~1980s&#xff09; 在1950年代初期&#xff0c;人们开始尝试使用计算机处理自然语言文本。…