go web框架 gin-gonic源码解读03————middleware
go web框架 gin-gonic源码解读03————middleware(context)
今天打完游戏有空整理整理之前看的gin的中间件设计,go的中间件设计相较于前两站还是蛮简单,蛮容易看懂的,所以顺便把context也一起写一下。
中间件是现在web服务里统一化拓展最常用的功能,,他是为了在我们的web服务中实现一些可重复使用,可组合的功能方法、可以让我们的 web逻辑在执行之前或者之后进行预处理,后处理,验证等操作。 在说中间件之前,我们先回忆一下之前几张看过的代码的。
**gin.go** 文件中gin为了实现http.Handler接口,而实现的ServeHTTP()方法
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {// 从对象池获取Context对象(对象池这里就不讲了,大家可以搜博客去看,这里只要大家会用就可以了)c := engine.pool.Get().(*Context)// 这三行其实就context的构造,传入http.ResponseWriter,*http.Requestc.writermem.reset(w)c.Request = reqc.reset()// 去我们的路由中查找响应的url并执行响应的逻辑engine.handleHTTPRequest(c)engine.pool.Put(c)
}
这里再顺便给大家介绍一下context,context实际上就是gin框架为了统一的参数管理包装的数据结构,他除了包含了我们每次访问ServeHTTP的传参(w http.ResponseWriter, req *http.Request)还包含了一些封装的web Response方法(调用就返回,非常好用),和一些请求获取参数的方法,其基本上覆盖了开发者平时所以操作需求,有兴趣的同学可以自己看看 .\Go\gin\context.go 中的内容。
// 这里缩略一下,只讲今天会用到的几个参数
type Context struct {writermem responseWriterRequest *http.RequestWriter ResponseWriterhandlers HandlersChain // 中间件执行链handlers index int8 // 执行下标fullPath string// 引擎的指针engine *Engine```略```
}
我们回到正题,看看我们的**handleHTTPRequest()**方法
// 去我们的路由中查找响应的url并执行响应的逻辑
engine.handleHTTPRequest( c )
这就是我们上次手撕的那个前缀树的查找方法。而我们的目的是通过收到http请求的url来找到,客户端需要请求的逻辑接口。
// 由于篇幅所限,代码会有所缩略func (engine *Engine) handleHTTPRequest(c *Context) {// 很显然就是把我们请求方法和路径从Context里拿出来,方便使用httpMethod := c.Request.MethodrPath := c.Request.URL.Pathunescape := false```略```// Find root of the tree for the given HTTP method// 这里就是我们的前缀树的t := engine.treesfor i, tl := 0, len(t); i < tl; i++ {// 这里前面说过的引擎(engine)中存的是一个前缀树的切片([]tree)// 每个请求方法一棵树,这里是遍历切片找到对应的请求方法if t[i].method != httpMethod {continue}root := t[i].root// Find route in tree// 找到树了,去树里查找value := root.getValue(rPath, c.params, c.skippedNodes, unescape)// 参数节点,拿参数if value.params != nil {c.Params = *value.params}// 关键点来了,这里就找到了我们要执行的逻辑方法if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.Next()c.writermem.WriteHeaderNow()return}```略```}break}```略```serveError(c, http.StatusNotFound, default404Body)
}
为了讲的详细的点我们把我们的关键代码单独拿出来说
c.handlers = value.handlers // 1.将接口的handlers赋值给了Context的handlersc.fullPath = value.fullPath // 2.给fullPath 赋值c.Next() // 3.执行Next() 方法c.writermem.WriteHeaderNow()// 4. 给HTTP response写入status codereturn
步骤1的赋值给的是handlers 而不是handler,这里大家可能会很奇怪,我业务逻辑其实一个函数就可以解决,这里为啥会缓存一个handlers呢,难道我的业务函数要拆分成好几个函数来写?其实这个handlers 存储的除了我们业务函数就是我们所有的中间件函数。
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc
gin的中间件调用的密码也都包含在步骤3里
func (c *Context) Next() {// 执行下标++c.index++// 一上来就自增是因为func (c *Context) reset()这个初始化方法里,会把下标初始化成-1,所以我们一上来就要++,让他变成0。// 这么做的目的就是因为我们的中间件函数中也会调用Next()for c.index < int8(len(c.handlers)) {// 通过下标去执行中间件c.handlers[c.index](c)// 显然执行完了++,不然就死循环了c.index++}
}
gin的中间件调用的秘密还是蛮简单的,接下来我们看看gin中间件的注册。
gin的中间件注册,大家都知道engine可以使用Use,路由组的RouterGroup也可以使用Use,实际上engine.Use()也是调用了RouterGroup.Use(),因为我们engine的路由包括了我们所有的RouterGroup的路由。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {engine.RouterGroup.Use(middleware...)// 这边是初始化的时候顺便注册一些异常的处理方式// 当然为了避免重复的调用这两个rebuild40X函数,这个Use还是建议一次性调用到位engine.rebuild404Handlers() engine.rebuild405Handlers()return engine
}// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {// 非常的简单,我们的Handlers 是一个切片,他把所有的middleware插入到中间件的末端group.Handlers = append(group.Handlers, middleware...)return group.returnObj()
}
这样子就实现了我们功能强大的中间件调用链条,只说这些感觉内容少了点,再带大家看看gin自带的两个中间件把。
// 这是gin代码中获取默认Engine 的方法,大家一定都用过
func Default() *Engine {// 无关紧要的版本校验debugPrintWARNINGDefault()// New()第一篇应该说过engine := New()// 这里调用了两个中间件Logger(), Recovery()engine.Use(Logger(), Recovery())return engine
}
Logger() 顾名思义,很显然是打印日志的一个中间件,打印日志也是中间件在开发中最有用的几个用处之一
func Logger() HandlerFunc {return LoggerWithConfig(LoggerConfig{})
}// LoggerWithConfig instance a Logger middleware with config.
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {// LoggerConfig不是我们本文讲解的重点,大家可以自己去看看// 这下面的代码大意就是获取日志的打印格式和输出位置formatter := conf.Formatterif formatter == nil {formatter = defaultLogFormatter}out := conf.Outputif out == nil {out = DefaultWriter}notlogged := conf.SkipPathsisTerm := trueif w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {isTerm = false}// 知识点:struct{}不占内存空间// 在go语言map[string]struct{}的写法相当于创建了一个set结构体。var skip map[string]struct{}if length := len(notlogged); length > 0 {skip = make(map[string]struct{}, length)for _, path := range notlogged {skip[path] = struct{}{}}}// 正文开始return func(c *Context) {// Start timerstart := time.Now()path := c.Request.URL.Pathraw := c.Request.URL.RawQuery// Process request```// 可能很多人会好奇为什么在中间件的中间还要执行一个next// 这是一个中间件在逻辑函数执行之前还有逻辑函数执行之后,都有钩子逻辑可以执行// 大家可以把这个想象成一个套娃的结构// 例如:logger() 前半段logic() 业务逻辑代码logger() 后半段```c.Next()// Log only when path is not being skipped// 上面英文很简单大家自己看看if _, ok := skip[path]; !ok {param := LogFormatterParams{Request: c.Request,isTerm: isTerm,Keys: c.Keys,}// Stop timer// 结构化打印,然后输出到流中,没啥好说的param.TimeStamp = time.Now()param.Latency = param.TimeStamp.Sub(start)param.ClientIP = c.ClientIP()param.Method = c.Request.Methodparam.StatusCode = c.Writer.Status()param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()param.BodySize = c.Writer.Size()if raw != "" {path = path + "?" + raw}param.Path = path// 输出完了fmt.Fprint(out, formatter(param))}}
}
不过一般大家的项目之中都不会用gin提供的logger中间件,因为大家都有自己的日志格式,不过你实在想用也可以,把自己的logger实例实现gin.logger的接口,然后在初始化engine的时候传给engine就好了
完
相关文章:
go web框架 gin-gonic源码解读03————middleware
go web框架 gin-gonic源码解读03————middleware(context) 今天打完游戏有空整理整理之前看的gin的中间件设计,go的中间件设计相较于前两站还是蛮简单,蛮容易看懂的,所以顺便把context也一起写一下。 中间件是现在w…...

win10电脑记事本在哪里?电脑记事本如何查看字数?
在日常工作中,我们会遇到许多需要记录的信息和事项,而使用电脑记事本工具可以帮助我们方便地保存、管理这些内容。无论是记录工作会议的要点、制定工作计划,还是记录灵感和创意,电脑记事本都是非常实用的工具。 那么win10电脑记事…...

【微服务】06-安全问题
文章目录 1.反跨站请求伪造1.1 攻击过程1.2 攻击核心1.3 如何防御1.4 使用AntiforgeryToken机制来防御用到的类 2. 防开发重定向共计2.1 攻击过程2.2 攻击核心2.3 防范措施 3.防跨站脚本3.1 攻击过程3.2 防范措施 4.跨域请求4.1 同源与跨域4.2 CORS过程4.2 CORS是什么4.3 CORS请…...

js的this指向问题
代码一: 这段代码定义了run函数、obj对象,然后我们把run函数作为obj的方法。 function run(){console.log(this);}let obj{a:1,b:2};obj.runrun;obj.run(); 那么我们调用obj的run方法,那么这个方法打印的this指向obj。 分析:即…...

Redis常用数据类型及命令
Redis 常用数据类型 常用数据类型 主要是指value类型 key都是字符串类型的 各种数据类型对应的特点 应用场景 哈希:一般来存储一些对象 列表:存一些跟顺序有关系的数据,比如朋友圈点赞 集合:一般用来做运算,交集&a…...
软件工程(六) 面向对象分析(OOA)之UML图特点
1、UML 面向对象分析里面有一个非常重要的工具叫UML,UML不仅在工作中非常重要,在考试当中也是非常重要的,即作为上午综合体,又大概率又会出现在下午的案例分析中,作为一个25分的大题。 UML叫做统一建模语言,它主要用于需求分析和软件的设计,来做一些模型的制作。比如我…...

QT 消息对话框按钮显示
前言 搞QT嘛,大多数都是军工。都要国产化,而且消息对话框的按钮的英文也不是很得劲,所以需要汉化。使用静态函数的按钮就是显示英文,汉化的代码如下。 void Widget::on_pushButton_clicked() {QMessageBox box(QMessageBox::Inf…...

平衡二叉树及其应用详解
平衡二叉树 定义与性质 平衡二叉树(Balanced Binary Tree)是计算机科学中的一种数据结构,它是二叉排序树的一种特殊情况。 平衡二叉树满足以下性质: 左子树和右子树的高度差不超过 1。也就是说,对于任意节点&#…...
vue3+ ts ts语法在script写不知道为啥一直报错
在vue3页面中写ts语法 发现识别不了 一直报错 1.出现这种问题的话,首先查看自己写的有没有问题,没有问题的话 2.再查看 script里边有没有写 lang"ts" <script setup lang"ts">解析 setup:是vue3在单文件组件 (SFC) 中使用 composition …...

c#写的端口监听,程序退出后,再次运行提示端口占用,且进程不存在
我用c#写了一个监听29999端口,进程结束后再次启动发现端口被占用,但是运行netstat -ano | findstr 29999找到进程ID后,却没有这个进程 经查询这个监听29999进程虽然没了,但是要找到他的父进程,把父进程关闭了才可以,参…...
跨域案例go gf ,请求代理,前端请求后端A转发给多个后端B
跨域案例go gf ,请求代理,前端请求后端A转后端B 案例:从前端请求后端A(路径携带argusx),后端A转发请求到多个不同地区(可一个)后端B(切掉argusx,其他不变进行请求)&…...

9.4 集成功率放大电路
OTL、OCL 和 BTL 电路均有各种不同输出功率和不同电压增益的集成电路。应当注意,在使用 OTL 电路时,需外接输出电容。为了改善频率特性,减小非线性失真,很多电路内部还引入深度负反馈。这里以低频功放为例。 一、集成功率放大电路…...

Java“牵手“拼多多商品详情数据、拼多多优惠券信息、拼多多到手价信息获取方法,拼多多API实现批量商品数据抓取示例
拼多多商城是一个网上购物平台,售卖各类商品,包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取拼多多商品详情数据,您可以通过开放平台的接口或者直接访问拼多多商城的网页来获取商品详情信息。以下是两种常用方法的介绍:…...

亚马逊云科技 re:Inforce 大会云安全合规与技术实践及 Security Jam 大赛,快来报名吧!...
2023年8月31日在北京 亚马逊云科技 re:Inforce 大会 首次登陆中国! 我们期待您的莅临, 并与您一起迎接 AI 时代, 开启全面智能的安全旅程! 在13:00-17:00的 培训与动手实验环节中 云安全合规与技术实践 及 Security Jam 大赛…...

网络安全(黑客技术)学习手册
1.网络安全是什么 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高; 二、则是发展相对成熟…...

泡泡玛特回应头部IP营收增速放缓:IP上市时间不固定
8月23日,针对今年上半年头部IP营收增速放缓问题,泡泡玛特(09992.HK)管理层在业绩会上解释称,每个IP上市时间并不固定,单从上半年看同比增长会有偏差,而随着下半年两个新系列的推出,全…...

很干的 Nginx
🎨 前言 本篇文章有些概念性的东西,是结合自己的理解表达出来的,可能有些理解不到位的地方。希望多多指教,谢谢大家。 红包献上 🧧🧧🧧🧧🧧🧧🧧…...

【已解决】pycharm突然双击无法打开,重启电脑也不管用
1.问题: pycharm突然双击无法打开,重启电脑也不管用 2.解决 2.1 方法一(修改Roaming) 1.找到C盘对应路径下的pycharm版本 2. 用记事本打开文件类型为VMOPTIONS文件 3. 修改或删除最后一行的映射路径 4.保存退出 2.2 方法二…...

【HCIP】15.MPLS基础
多协议标签交换 MPLS位于TCP/IP协议栈中的数据链路层和网络层之间,可以向所有网络层提供服务。 通过在数据链路层和网络层之间增加额外的MPLS头部,基于MPLS头部实现数据快速转发。 术语 MPLS域(MPLS Domain):一系列…...

热烈祝贺重庆融能成功入选航天系统采购供应商库
经过航天系统采购平台的严审,重庆融能机电设备股份有限公司成功入选中国航天系统采购供应商库。航天系统采购平台是航天系统内企业采购专用平台,服务航天全球范围千亿采购需求,目前,已有华为、三一重工、格力电器、科大讯飞等企业…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...