Gee教程5.中间件

鉴权认证、日志记录等这些保障和支持系统业务属于全系统的业务,和具体的系统业务没有关联,对于系统中的很多业务都适用。

因此,在业务开发过程中,为了更好的梳理系统架构,可以将上述描述所涉及的一些通用业务单独抽离并进行开发,然后以插件化的形式进行对接。这种方式既保证了系统功能的完整,同时又有效地将具体业务和系统功能进行解耦,还可以达到灵活配置的目的。

这种通用业务独立开发并灵活配置使用的组件,一般称之为"中间件"。其就相当于在请求和具体的业务逻辑处理之间增加某些操作,这种以额外添加的方式不会影响编码效率,也不会侵入到框架中。

每个用户都有自己的业务,Web框架不可能实现所有的功能。因此,框架需要有一个插口,允许用户自己定义通用功能,嵌入到框架中,仿佛这个功能是框架原生支持的一样。这个也就是我们所说的中间件。

通俗点理解,中间件就是个函数。比如前缀是/admin来访问的都需要进行鉴权认证,而Web框架又没有实现这个功能。

这时可能想到直接在该路由的处理函数加上这个功能就行啦。但是要是有很多前缀是/admin的路由,那就要写很多很多,这是很繁琐的。

中间件的使用

那我们就想有这种效果,这种就写的很舒服了,gin框架就是这样使用的。

//鉴权中间件
func AuthMiddleWare() gee.HandlerFunc {........
}func main() {r := gee.New()v2 := r.Group("/admin")	v2.Use(AuthMiddleWare()) //该路由组使用鉴权中间件,也只有该组使用这个中间件v2.GET("/home", func(c *gee.Context) {//AuthMiddleWare()c.JSON(http.StatusOK, gin.H{"msg": "home路由",})})v2.GET("/ok", func(c *gee.Context) {//AuthMiddleWare()c.JSON(http.StatusOK, gin.H{"msg": "ok路由",})})
}

中间件的设计

前面说了中间件是个函数,那可以设置成映射的 Handler 一致,处理的输入是Context对象。

type HandlerFunc func(*Context)

一个路由组可以会使用多个中间件的,上一节中已在路由组的结构中添加了中间件数组。那来实现下添加中间件方法Use。该方法可以一次添加多个中间件。

RouterGroup struct {prefix      stringmiddlewares []HandlerFunc // support middlewareparent      *RouterGroup  // support nestingengine      *Engine       // all groups share a Engine instance
}func (group *RouterGroup) Use(middlewares ...HandlerFunc) {group.middlewares = append(group.middlewares, middlewares...)
}

简单想法:一条直线处理

我们想的简单的版本,前后添加了中间件A,中间件B。那处理的流程就是:中间件A -----> 中间件B -----> 路由处理函数。

回到我们的执行路由处理函数部分,那么ServeHTTP就需要改动下。

//之前的
//func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//	c := newContext(w, req)
//	engine.router.handle(c)
//}//需要执行中间件的做法
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := newContext(w, req)for _, group := range engine.groups {if strings.HasPrefix(req.URL.Path, group.prefix) {group.middlewares(c)    //执行中间件}}engine.router.handle(c)
}

这种实现起来是很简单,但却不实用的。就像是要鉴权认证,假如认证失败后,那肯定是不执行路由处理函数的,而这个做法却是继续执行的。

还有有些中间件想在路由处理函数执行后做一些操作,例如计算响应时长之类的。 那就要等路由处理函数执行后才可以计算,当前实现的做法是不妥的,不能实现这样功能的。所以需要继续改善。

我们想要的中间件是支持用户在请求被处理的前后,做一些额外的操作

改善

现在来看一个中间件,日志中间件。

假如我们要计算响应时长,那就是要等路由Handler处理完之后才能执行第八行代码。所以需要做些操作。

那在第六行中,c.Next()表示执行其他的中间件或用户的Handler。

等执行完其他中间件和用户的Handler后,再执行第八行代码,这样就可以符合我们想记录响应时长的要求了。

func Logger() HandlerFunc {return func(c *Context) {// Start timert := time.Now()// Process requestc.Next()// Calculate resolution timelog.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t))}
}//鉴权中间件
func AuthMiddleWare() HandlerFunc {........
}
func main() {r := gee.New()    r.Use(Logger())v2 := r.Group("/admin")	v2.Use(AuthMiddleWare()) //该路由组使用鉴权中间件,也只有该组使用这个中间件v2.GET("/home", func(c *gee.Context) {c.JSON(http.StatusOK, gin.H{"msg": "home路由",})})}

 那么重点就是在c.Next()中了。c.Next()可以执行中间件,那说明Context保存了该中间件。所以,Context结构体添加了中间件切片midHandlers和index。

type Context struct {// origin objectsWriter http.ResponseWriterReq    *http.Request// request infoPath   stringMethod stringParams map[string]string// response infoStatusCode int// middlewaremidHandlers []HandlerFunc        //这节新添加的index    int8        //这节新添加的
}    func newContext(w http.ResponseWriter, req *http.Request) *Context {return &Context{Wrtier: w,Req:    req,Path:   req.URL.Path,Method: req.Method,index:  -1, //这节新添加的}
}func (c *Context) Next() {c.index++for c.index < int8(len(c.midHandlers)) {c.midHandlers[c.index](c) //执行中间件c.index++}
}

index是记录当前执行到第几个中间件,当在中间件中调用Next方法时,控制权交给了下一个中间件,直到调用到最后一个中间件,然后再从后往前,调用每个中间件在Next方法之后定义的部分。

如果我们将用户在映射路由时定义的Handler添加到c.handlers列表中,结果会怎么样呢?

func A(c *Context) {part1        //可以是记录开始时间c.Next()part2        //可以是记录整个响应时长
}
func B(c *Context) {part3c.Next()part4
}

假设我们应用了中间件 A 和 B,和路由映射的 Handler。那么c.handlers是这样的:[A, B, Handler],c.index初始化为-1。

先执行中间件A,那就是先执行到part1。之后到c.Next(),这时候就到执行c.handlers中的下一个,即是中间件B,那就是执行part3。之后执行c.Next(),这时就执行到路由Hander,跟着就到了part4。part4结束后,即是中间件中的c.Next()结束了,最终执行part2.

index是怎么变化的要到后面才好说明白。

最终的顺序是part1 -> part3 -> Handler -> part 4 -> part2。恰恰满足了我们对中间件的要求。

接着就看回到ServeHTTP方法。

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {// c := newContext(w, req)// engine.router.handle(c)var middlewares []HandlerFuncfor _, group := range engine.gorups {if strings.HasPrefix(req.URL.Path, group.prefix) {middlewares = append(middlewares, group.middlewares...) //添加该路由组的中间件}}c := newContext(w, req)c.midHandlers = middlewares    //路由组中间件都赋值给Context结构体的midHandlers engine.router.handle(c)
}

那之后肯定是在engine.router.handle(c)中有些变化的了。

最后的c.Next()是很重要的,这样操作后,所有的中间件和路由Handler都统一在Next()中执行的了。

这里可以把之前例子里的index的疑惑讲明白了。c.index默认初始化是-1的,这里执行C.Next(),c.index就变成0了,就可以执行c.handler[0],即是中间件A。

func (r *router) handle(c *Context) {n, params := r.getRoute(c.Method, c.Path)if n != nil {key := c.Method + "-" + n.pathc.Params = paramsc.midHandlers = append(c.midHandlers, r.handers[key]) //添加路由Handler到midHandlers中} else {c.midHandlers = append(c.midHandlers, func(c *Context) {c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)})}c.Next() //从这里开始执行第一个中间件,要是没有中间件就直接执行路由Handler//上一节的做法//n, params := r.getRoute(c.Method, c.Path)// if n != nil {// 	c.Params = params// 	// key := c.Method + "-" + c.Path// 	key := c.Method + "-" + n.path// 	fmt.Println(key)// 	r.handers[key](c)// } else {// 	c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)// }
}

 说完了c.Next()后,还有c.Abort()方法。

在进行鉴权认证不通过后,后面的中间件和路由Handler也就不需要执行的了。

在c.Next()中是通过c.index来进行判断执行哪个中间件的。首先我们定义AbortIndex常量,主要是用于控制框架处理请求链路的长度,即是中间件和路由Handler总个数。这时候设置c.index=AbortIndex,那么c.Next()就不会执行中间件了。那么我们在之前使用Use添加中间件的时候,就需要判断中间件个数是否超过了最大个数,这里就不展示了。

const AbortIndex = math.MaxInt8 >> 1func (c *Context) Abort() {c.index = AbortIndex
}

测试

场景:/admin的使用鉴权认证中间件,全局的使用日志中间件。

鉴权失败的话,调用c.Abort()

func authMiddleWare() gee.HandlerFunc {return func(c *gee.Context) {fmt.Println("start 鉴权中间件")token := c.Req.Header.Get("token")if token == "" || token != "123" {c.JSON(http.StatusUnauthorized, gee.H{"message": "身份验证失败"})c.Abort()//return //不使用return的话,后面的fmt.Println("hello")会执行,使用return的话,后面的不会执行} else {fmt.Println("鉴权成功")}//使用Abort()后要是不使用return,打印hello还是会执行的//fmt.Println("hello")}
}func main() {r := gee.New()r.Use(gee.Logger())r.GET("/home", func(c *gee.Context) {c.HTML(http.StatusOK, "<h1>Index home</h1>")})v1 := r.Group("/admin")v1.Use(authMiddleWare()){v1.GET("/myname", func(c *gee.Context) {c.String(http.StatusOK, "Hello li")})}r.Run("localhost:10000")
}

结果

完整代码:https://github.com/liwook/Go-projects/tree/main/gee-web/5-Middlewares

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

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

相关文章

「C++」类和对象2

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;C启航 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 文章目录 &#x1f349;前言&#x1f349;构造函数&#x1f34c;参数&#x1f34c;默认构造函数&#x1f95d;两种类型&#x1f95d;编译…

C++基础 -28- 友元

友元用于访问类中的所有数据成员 类中的私有成员&#xff0c;类外不可访问 定义友元的格式&#xff08;友元函数必须要在类内&#xff0c;声明&#xff09; friend void show(person &b); 使用友元访问类的所有成员 #include "iostream"using namespace std…

深入理解Docker容器核心技术

文章目录 1. Linux命名空间&#xff08;Namespaces&#xff09;1.1 示例&#xff1a;PID命名空间 2. 控制组&#xff08;cgroups&#xff09;2.1 示例&#xff1a;内存控制组 3. 联合文件系统&#xff08;UnionFS&#xff09;3.1 示例&#xff1a;查看镜像的分层结构 4. Docker…

二、ZooKeeper集群搭建

目录 1、概述 2、安装 2.1 第一步&#xff1a;下载zookeeeper压缩包 2.2 第二步&#xff1a;解压 ​​​​​​​2.3 第三步&#xff1a;修改配置文件 ​​​​​​​2.4 第四步&#xff1a;添加myid配置 ​​​​​​​2.5 第五步&#xff1a;安装包分发并修改myid的值…

2023_Spark_实验二十四:SparkStreaming读取Kafka数据源:使用Direct方式

SparkStreaming读取Kafka数据源&#xff1a;使用Direct方式 一、前提工作 安装了zookeeper 安装了Kafka 实验环境&#xff1a;kafka zookeeper spark 实验流程 二、实验内容 实验要求&#xff1a;实现的从kafka读取实现wordcount程序 启动zookeeper zk.sh start# zk.sh…

【方案】智慧林业:如何基于EasyCVR视频能力搭建智能林业监控系统

随着人类进程的发展。城市化范围的扩大&#xff0c;森林覆盖率越来越低&#xff0c;为保障地球环境&#xff0c;保护人类生存的净土&#xff0c;森林的保护与监管迫在眉睫。TSINGSEE青犀智慧林业智能视频监控系统方案的设计&#xff0c;旨在利用现代科技手段提高林业管理的效率…

2023年AI时代中小企业智能化发展报告

今天分享的是AI系列深度研究报告&#xff1a;《2023年AI时代中小企业智能化发展报告》。 &#xff08;报告出品方&#xff1a;创业邦&#xff09; 报告共计&#xff1a;47页 AI——中小企业的智能化增长利器 继蒸汽机、电气化、信息化时代之后&#xff0c;由第四次工业革命开…

[HTML]Web前端开发技术6(HTML5、CSS3、JavaScript )DIV与SPAN,盒模型,Overflow——喵喵画网页

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

【U8+】用友U8修改凭证提示:外部凭证在总账系统中不能修改。

【问题描述】 用友U8中&#xff0c;在总账模块中修改凭证的时候&#xff0c;提示&#xff1a; 外部凭证在总账系统中不能修改。 【原因分析】 在软件中&#xff0c;使用其他模块的情况下&#xff0c; 其他模块生成的凭证都会传到总账模块中&#xff0c;进而这些由其他模块生成…

【开源】基于JAVA语言的考研专业课程管理系统

项目编号&#xff1a; S 035 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S035&#xff0c;文末获取源码。} 项目编号&#xff1a;S035&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高…

Tkinter 面向对象框架《二》

一、说明 Tkinter 教程 开发完整的 Tkinter 面向对象应用程序开发完整的 Tkinter 面向对象应用程序。 即使OOP的高手&#xff0c;也未必对面向对象全部掌握。至于 Tkinter的OOP编程&#xff0c;其实高手们也是在摸索实践中。 为了面向对象和Tkinter参与本教程。如果你来这里纯…

测评补单助力亚马逊,速卖通,国际站卖家抢占市场,提升转化和评分

想要快速提升商品的销量&#xff0c;测评补单这种方法见效是最快的。特别是新品上线&#xff0c;缺少用户评价&#xff0c;转化率不好&#xff0c;很多商家新品上线都会做测评补单&#xff0c;搞些商品好评&#xff0c;不但可以提升转化&#xff0c;同时在平台也可以获得更多展…