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就好了

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

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

相关文章

开源全球地理空间数据可视化框架——Cesium学习(2023.8.21)

Cesium学习 2023.8.21 1、Cesium简介1.1 Github上的Cesium 2、Cesium下载安装使用2.1 方式一&#xff1a;页面在线引用2.2 方式二&#xff1a;页面离线使用2.3 方式三&#xff1a;完整项目使用 3、CesiumJS学习教程&#xff08;快速上手 API文档&#xff09;3、Cesium官方示例…

Python 读写 Excel 文件库推荐和使用教程

文章目录 前言Python 读写 Excel 库简介openpyxl 处理 Excel 文件教程pandas 处理 Excel 文件教程总结 前言 Python 读写 Excel 文件的库总体看还是很多的&#xff0c; 各有其优缺点&#xff0c; 以下用一图总结各库的优缺点&#xff0c; 同时对整体友好的库重点介绍其使用教程…

java中多个list怎么用List表示?

如果你有多个List对象&#xff0c;想要将它们合并成一个List对象&#xff0c;可以使用addAll()方法来实现。addAll()方法将会把一个List中的元素逐个添加到另一个List中。 以下是一个示例&#xff0c;展示了如何将多个List对象合并为一个List对象&#xff1a; import java.ut…

vue离线缓存资源文件

本文章主要是解决大文件,实时请求资源浪费网络资源的问题 从而有效的将解决用户体验的问题 话不多说上才艺 ⬇️⬇️⬇️⬇️⬇️⬇️⬇️ 找到项目中的 index.html 文件,并在 html 标签中加入 manifest"manifest.appcache" 安装 appcache-manifest 包 npm ins…

【NPM】包的指令

npm 安装的包可以根据其用途和作用进行分类&#xff0c;一般可以分为以下几种类型&#xff1a; 普通依赖&#xff08;Regular Dependencies&#xff09;&#xff1a; 这些是你项目中的实际依赖项&#xff0c;用于构建、运行或扩展你的应用程序。这些依赖会被包含在你的应用程序…

c++ qt--信号与槽(二) (第四部分)

c qt–信号与槽(二) &#xff08;第四部分&#xff09; 信号与槽的关系 1.一对一 2.一对多 3.多对一 4.多对多 还可以进行传递 信号->信号->槽 一个信号控制多个槽的例子&#xff08;通过水平滑块控制两个组件&#xff09; 1.应用的组件 注意这里最下面的组件进行…

【Qt学习】06:事件与事件过滤器

OVERVIEW 事件与事件过滤器一、事件1.鼠标事件创建子类MyLabel重写鼠标事件提升Label控件为MyLabel 2.定时器事件timerEventQTimer 3.事件分发器&#xff08;event函数&#xff09;event函数重写event函数深入 二、事件过滤器1.事件过滤器2.事件处理的五个层次 事件与事件过滤器…

Tomcat和Servlet基础知识的讲解(JavaEE初阶系列16)

目录 前言&#xff1a; 1.Tomcat 1.1Tomcat是什么 1.2下载安装 2.Servlet 2.1什么是Servlet 2.2使用Servlet来编写一个“hello world” 1.2.1创建项目&#xff08;Maven&#xff09; 1.2.2引入依赖&#xff08;Servlet&#xff09; 1.2.3创建目录&#xff08;webapp&a…

VR法治警示教育:情景式课堂增强教育效果

VR法治警示教育平台是一款基于虚拟现实技术的在线教育平台&#xff0c;旨在通过模拟真实场景和互动体验&#xff0c;向公众普及法律知识&#xff0c;提高公民的法律意识和素养。该平台采用先进的虚拟现实技术&#xff0c;将用户带入一个逼真的仿真环境&#xff0c;让用户身临其…

python systemrdl 使用实例

今天来看一个具体实例&#xff0c;上一篇传送门&#xff1a;python SystemRDL 包介绍_Bug_Killer_Master的博客-CSDN博客 通常来说&#xff0c;我们验证过程用到的情况大多都是需要提取reg field的路径以及reset 值等信息&#xff0c;所以比较常见的一种方法就是先把rdl compil…

计算机网络文件拆分—视频流加载、断点续传

视频流加载 视频流加载的原理是通过网络传输和播放器解码来实现的。 首先&#xff0c;视频文件会被分成一系列小的数据包&#xff0c;通常是以流的形式传输&#xff0c;这些数据包通过网络传输到用户设备。在传输过程中&#xff0c;可以采用各种协议&#xff0c;如HTTP、RTSP…

简析SCTP开发指南

目录 前言一、SCTP基本概念二、SCTP开发步骤1. **环境配置**&#xff1a;2. **建立Socket**&#xff1a;3. **绑定和监听**&#xff1a;4. **接收和发送数据**&#xff1a;5. **关闭连接**&#xff1a; 三、 C语言实现SCTP3.1SCTP客户端代码&#xff1a;3.2 SCTP服务器端代码&a…

识别图片中的文字

前言 PearOCR 是一款免费无限制网页版文字识别工具。 优点如下&#xff1a; 免费&#xff1a;完全免费&#xff0c;没有任何次数、大小限制&#xff0c;可以无限使用&#xff1b; 安全&#xff1a;全部数据本地运算&#xff0c;所有图片均不会被上传&#xff1b; 智能&#xf…

SQL注入之HTTP头部注入

文章目录 cookie注入练习获取数据库名称获取版本号 base64注入练习获取数据库名称获取版本号 user-agent注入练习获取数据库名称获取版本号 cookie注入练习 向服务器传参三大基本方法:GPC GET方法&#xff0c;参数在URL中 POST&#xff0c;参数在body中 COOKIE&#xff0c;参数…

OpenSIPS 通话中 UPDATE 请求导致没有声音问题

文章目录 1. 问题现象2. 抓包排查3. 问题分析及解决方案 1. 问题现象 在 SIP 应用的开发中&#xff0c;通话一端听不到声音是比较常见的问题。一般来说&#xff0c;没有声音意味着 RTP 传输存在障碍&#xff0c;追根究底就是网络不通或者端口未开放等原因。但在实践中&#xf…

Kotlin Flow 转换以及上下游处理

本片文章主要介绍Flow上下游处理&#xff0c;上游一个Flow使用map&#xff0c;上游两个Flow使用zip&#xff0c;上游三个Flow及以上使用combine 1、下面代码展示了upStreamFlow作为上游&#xff0c;downStreamFlow作为下游&#xff0c;通过对upStreamFlow使用map操作符函数将…

13.Oracle中nvl()与nvl2()函数详解

Oracle中nvl()与nvl2()函数详解&#xff1a; 函数nvl(expression1,expression2)根据参数1是否为null返回参数1或参数2的值&#xff1b; 函数nvl2(expression1,expression2,expression3)根据参数1是否为null返回参数2或参数3的值 1.nvl&#xff1a;根据参数1是否为null返回参数…

数据结构——图

文章目录 图的基本概念顶点边度无向图和有向图无权图和带权图 图的存储邻接矩阵存储邻接表存储 图的搜索广度优先搜索深度优先搜索 图是一种较为复杂的非线性结构。 为啥说其较为复杂呢&#xff1f; 根据前面的内容&#xff0c;我们知道&#xff1a; 线性数据结构的元素满足唯…

nodejs

文章目录 一、nodejs1.1、npm配置命令别名 一、nodejs 1.1、npm配置命令别名 我们可以使用“node index.js”的方式运行代码 也可以利用npm配置别名的方式运行代码 在package.json中配置别名&#xff0c;利用“npm run 别名”也可以运行 start是特殊的&#xff0c;可以“npm r…