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):一系列…...

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

隧道vs免费爬虫ip:为何要选择隧道爬虫ip?
在网络爬虫的世界中,爬虫ip是一项关键技术,它可以帮助我们隐藏身份、突破限制、提高抓取效率。但是,在选择爬虫ip时,我们常常会面对隧道爬虫ip和免费爬虫ip之间的抉择。在本文中,我们将探讨隧道爬虫ip相对于免费爬虫ip…...

C++day6(多态实现动物园的讲解员和动物表演的相关介绍、用函数模板实现不同数据类型的交换功能)
1.比喻:动物园的讲解员和动物表演 想象一下你去了一家动物园,看到了许多不同种类的动物,如狮子、大象、猴子等。现在,动物园里有一位讲解员,他会为每种动物表演做简单的介绍。 在这个场景中,我们可以将动…...

多线程学习之生产者和消费者与阻塞队列的关系
生产者和消费者 概述: 生产者消费者问题,实际上主要是包含了两类线程: 生产者线程用于生产数据消费者线程用于消费数据 生产者和消费者之间通常会采用一个共享的数据区域,这样就可以将生产者和消费者进行解耦, 两…...

JAVA语言代入电商平台api接口拼多多根据关键词获取商品列表示例
拼多多根据关键词获取商品接口的意义; 实现商品搜索:通过关键词搜索商品API接口,电商平台可以为消费者提供一个简单、快捷的商品搜索功能。用户只需输入关键词,就可以得到与该关键词相关的商品列表。 提供便捷的商品搜索服务&…...

Centos7更新glibc2.18
Centos7更新glibc2.18 查看glibc版本下载解压glibc2.18编译安装结果验证 查看glibc版本 # 查看glibc版本 ldd --version下载解压glibc2.18 参考: https://blog.csdn.net/qq_39295044/article/details/86685789 https://blog.csdn.net/myhes/article/details/106923039 # 下载…...

QT初学者该安装qt creator哪个版本?
对于Qt初学者,建议安装最新版本的Qt Creator。Qt Creator是Qt官方提供的集成开发环境(IDE),用于开发Qt应用程序。每个Qt版本都会配套提供对应的Qt Creator版本,确保兼容性和稳定性。同时,选择合适的Qt版本也…...

VR智慧校园资中控管理平台综合提升了课堂教学质量
随着越来越多高校在课堂中引进VR虚拟仿真实训系统,为了方便老师对全班同学进行高效率地管理,VR中控平台应运而生。下面为您详细介绍VR中控平台在课堂教学中的应用优势。 VR中控系统安装在教师总控端,融合了课件、视频、3D动画等丰富的教学资源…...

【Go 基础篇】Go语言中的数组:初识与应用
Go语言以其简洁、高效和强大的特性在编程界广受欢迎。数组作为一种基本的数据结构,在各种应用场景中扮演着重要角色。本文将引入Go语言中的数组,介绍其特点、创建、初始化以及基本应用,为你打开数组的大门。 前言 数组是一种固定大小的数据…...

(vue)el-table 怎么把表格列中相同的数据 合并为一行
(vue)el-table 怎么把表格列中相同的数据 合并为一行 效果: 文档解释: 写法: <el-table:data"tableData"size"mini"class"table-class"borderstyle"width:100%"max-height"760":span-…...

精准高效农业作业,植保无人机显身手
中国作为农业大国,拥有约18亿亩的农田,每年都需要进行种子喷洒和农药施用等农业作业,对于普通农户来说,这是一项耗时耗力的工程,同时,人工喷洒农药极易造成农药慢性中毒,对农民的身体健康产生极…...