Gee教程5.中间件

news2024/12/31 4:34:15

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

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

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

每个用户都有自己的业务,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      string
	middlewares []HandlerFunc // support middleware
	parent      *RouterGroup  // support nesting
	engine      *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 timer
		t := time.Now()
		// Process request
		c.Next()
		// Calculate resolution time
		log.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 objects
	Writer http.ResponseWriter
	Req    *http.Request
	// request info
	Path   string
	Method string
	Params map[string]string
	// response info
	StatusCode int
	// middleware
	midHandlers []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) {
    part3
    c.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 []HandlerFunc
	for _, 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.path
		c.Params = params
		c.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 >> 1

func (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.coloradmin.cn/o/1276885.html

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

相关文章

1.2 Ubauntu 使用

一、完成VMware Tools安装 双击 VMwareTool 打开 Ubuntu 终端快捷键 AltControlT 切换汉语的快捷键是Alt空格 ls 打印出当前所在目录中所有文件和文件夹 cd 桌面 进入桌面文件夹 sudo ./vmware-install.pl 安装tool&#xff0c;输入之前设置的密码。 地址默认&#xff0c;按…

matlab simulink 永磁同步电机PI调速控制

1、内容简介 略 27-可以交流、咨询、答疑 2、内容说明 永磁同步电机调速控制 永磁同步电机PI调速控制 永磁同步电机PI调速控制、PMSM 3、仿真分析 略 4、参考论文 略 链接&#xff1a;https://pan.baidu.com/s/1AAJ_SlHseYpa5HAwMJlk1w 提取码&#xff1a;rvol 路…

程序猿无烦恼:让养生专家来写代码!!!

自己的经验&#xff0c;也是看旁边焦虑的开发总结的一些经验&#xff0c;讲道理不一定有用&#xff0c;但是道理本身一定是对的。 文章目录 持续学习少烦恼明确需求少问题少盯荧幕多冥想少吃奶茶多锻炼亲近自然要放空 持续学习少烦恼 C、JAVA、python、数据库…… 唯有持续学…

HarmonyOs 4 (三) ArkTS语言

目录 一 认识ArkTs语言1.1 ArkTs1.2 基本结构 二 基本语法2.1 声明式UI2.1.1 创建组件2.1.1.1 无参数2.1.1.2 有参数2.1.1.3 组件样式2.1.1.4 组件方法2.1.1.5 组件嵌套 2.1.2 自定义组件2.1.2.1 基本结构2.1.2.2 成员函数/变量2.1.2.3 自定义组件的参数规定2.1.2.4 Build函数2…

Android RatingBar实现五星好评

属性 isIndicatorRatingBar 是否为指示器&#xff0c;为true时&#xff0c;用户将无法交互操作&#xff0c;默认为false。 numStars 显示的星型数量&#xff0c;必须是一个整形值&#xff0c;像“50”&#xff0c;虽然可以设置很大&#xff0c;但一般…

每日一题:NowCower-JZ64.求1+2+3+...+n

每日一题系列&#xff08;day 10&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

Git——分支应用进阶

主要内容包括以下几个方面&#xff1a; 长期分支和短期分支的类型以及用途。多种分支模型&#xff0c;其中包括基于工作流的主题分支。不同分支模型的发布流程。在多个预览版程序中使用分支修复安全问题。远程跟踪分支和refspecs规范&#xff0c;以及默认远程版本库配置。拉取…

人工智能基础创新的第二增长曲线

编者按&#xff1a;2023年是微软亚洲研究院建院25周年。借此机会&#xff0c;我们特别策划了“智启未来”系列文章&#xff0c;邀请到微软亚洲研究院不同研究领域的领军人物&#xff0c;以署名文章的形式分享他们对人工智能、计算机及其交叉学科领域的观点洞察及前沿展望。希望…

C陷阱与缺陷——第8章建议

不要说服自己相信”皇帝的新装“&#xff0c;有些看上去正确的语句实际是有问题的直接了当地表明意图&#xff0c;当你写的代码有可能被误解成其他含义时&#xff0c;可以通过加注释等方式让你的意图更加清晰&#xff1b;有些错误可以做到事先预防 4. 考察最简单的特例&#xf…

Hdoop学习笔记(HDP)-Part.11 安装Kerberos

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

牛客小白月赛82(A~C)

目录 A.谜题&#xff1a;质数 输入描述 输出描述 输入 输出 解析 B.Kevin逛超市 2 (简单版本) 输入描述 输出描述 输入 输出 思路 C.被遗忘的书籍 题目描述 输入描述 输出描述 输入 输出 输入 输出 思路 比赛链接 牛客小白月赛82_ACM/NOI/CSP/CCPC/ICPC算…

C#和MySQL技巧分享:日期的模糊查询

文章目录 前言一、EF Core 模糊查询二、MySql 日期模糊查询分析和优化2.1 测试环境准备2.1.1 创建数据库2.1.2 查看测试数据 2.2 查询日期的运行效率对比2.3 运行效率优化 三、EF Core 模糊查询优化3.1 字符串转日期3.2 使用日期格式查询 四、优化建议总结 前言 在处理数据库查…

CSS 多主题切换思路

前言 本篇仅提供多主题切换思路&#xff0c;示例简单且清晰。 实现 步骤一&#xff1a;多主题(颜色)定义 定义根伪类 :root&#xff0c;代码第 2 和 7 行。分别定义了默认和带参数的伪类&#xff1b;定义 CSS 变量&#xff0c;注意变量名需要以两个减号&#xff08;--&…

03数据仓库Flume

Flume 功能 Flume主要作用&#xff0c;就是实时读取服务器本地磁盘数据&#xff0c;将数据写入到 HDFS。 Flume是 Cloudera提供的高可用&#xff0c;高可靠性&#xff0c;分布式的海量日志采集、聚合和传输的系统工具。 Flume 架构 Flume组成架构如下图所示&#xff1a; A…

jQuery选择器、操作DOM、事件处理机制、动画、ADJX操作知识点梳理

jQuery 核心理念就是写的更少&#xff0c;做的更多实现的代码更加简洁有效的提高开发效率 jQuery跟JavaScript的用法是不一样的 跟jQuery相继诞生的JavaScript库还有很多&#xff0c;不包括node.js 关于代码$("li").get(0)&#xff0c;获取DOM对象 jQuery对象声…

MMdetection3.0 问题:DETR验证集上AP值全为0.0000

MMdetection3.0 问题&#xff1a;DETR验证集上AP值全为0.0000 条件&#xff1a; 1、选择的是NWPU-VHR-10数据集&#xff0c;其中训练集455张&#xff0c;验证、测试相同均为195张。 2、在Faster-rcnn、YOLOv3模型上均能训练成功&#xff0c;但是在DETR训练时&#xff0c;出现验…

前端学习系列之CSS

目录 CSS 简介 发展史 优势 基本语法 引用方式 内部样式 行内样式 外部样式 选择器 id选择器 class选择器 标签选择器 子代选择器 后代选择器 相邻兄弟选择器 后续兄弟选择器 交集选择器 并集选择器 通配符选择器 伪类选择器 属性选择器 CSS基本属性 优…

pycharm中绘制一个3D曲线

import numpy as np import matplotlib.pyplot as plt # 中文的设置 import matplotlib as mp1 from mpl_toolkits.mplot3d import Axes3D mp1.rcParams["font.sans-serif"] ["kaiti"] mp1.rcParams["axes.unicode_minus"] False # 数据创建 X…

Python过滤掉特定区域内的矩形框

Python过滤掉特定区域内的矩形框 前言前提条件相关介绍实验环境过滤掉特定区域内的矩形框方法一&#xff1a;直接法&#xff08;for循环遍历&#xff09;代码实现输出结果 方法二&#xff1a;列表推导式代码实现输出结果 前言 由于本人水平有限&#xff0c;难免出现错漏&#x…

51k+ Star!动画图解、一键运行的数据结构与算法教程!

大家好&#xff0c;我是 Java陈序员。 我们都知道&#xff0c;《数据结构与算法》 —— 是程序员的必修课。 无论是使用什么编程语音&#xff0c;亦或者是前后端开发&#xff0c;都需要修好《数据结构与算法》这门课&#xff01; 在各个互联网大产的面试中&#xff0c;对数据…