IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Gin框架开发与实践 打开Gin的上手之路 -> 正文阅读

[开发测试]Gin框架开发与实践 打开Gin的上手之路

在这里插入图片描述


用自己的话表达学到的东西是为了节约时间。
别把窘境,迁怒于别人,唯一可以抱怨的,只是不够努力的自己。

0. 了解Gin框架

安装

go get -u github.com/gin-gonic/gin

使用

import "github.com/gin-gonic/gin"

例子:编写HelloWorld
准备工作:
开启go module:

GOPROXY=https://goproxy.cn,direct
然后
go mod init GinWeb
go mod tidy

编写HelloWorld。
先创建GinWeb/gindemo/helloworld.go,代码如下:
编程目的:使用 gin.Default()和Run方法

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func main(){
	router := gin.Default()
    router.GET("/",func(c *gin.Context){
    	c.String(http.StatusOK,"Hello World \n")
	})
	router.Run(":8000")
}

这样就实现了Gin实现HelloWorld,创建了一个router(路由),
然后执行了Run方法。
右击运行程序,使用postman访问指定的ip和端口:http://127.0.0.1:8000
在这里插入图片描述
或者我们在cmd终端通过curl命令访问,输入:

curl http://127.0.0.1:8000

在这里插入图片描述
分析代码

1.router := gin.Default()
这是默认的服务器。
使用gin的Default方法创建一个路由Handle。
2.然后通过HTTP方法GET绑定路由规则和路由函数。
不同于net/http的路由函数,gin进行了封装,把request和response都封装到了gin.Context的上下文环境中
3.最后启动路由的方法监听端口,还可以使用http.ListenAndServe(":8000",router)

在这里插入图片描述
要知道一次请求处理的大体流程,只要能找到web框架的入口即可。
点击Run方法,可以看到关键的ListenAndServer,
在这里插入图片描述
Engine结构体实现了ServeHTTP接口。
入口就是Engine实现的ServeHTTP接口。
在这里插入图片描述
几行代码就能实现web服务。
使用gin的Default方法创建了一个路由handler,
然后通过HTTP方法绑定路由规则和路由函数,
不同于net/http库的路由函数,gin进行了封装,
把request和response都封装到了gin.Context的上下文环境中,
最后是启动路由Run方法监听端口。

Gin API文档

继续分析:

1. Gin-Router

1.1 默认服务器

router.Run()

1.2 Http服务器

除了使用默认的服务器中的router.Run方法,
还可以使用http.ListenAndServe方法。
编程目的:使用http.ListenAndServe方法

func main(){
	router := gin.Default()
	
	router.GET("/",func(c *gin.Context){
		c.String(http.StatusOK,"Hello CSDN!")
	})
	
	http.ListenAndServe(":8080",router)
}

测试效果:
在这里插入图片描述

1.3 路由

1.3.1 基本路由

基本路由gin框架采用的路由库是httprouter。

// 创建带有默认中间件的路由

// 日志与恢复中间件
router := gin.Default()
// 创建不带中间件的路由
// r := gin.New()

router.GET("/someGet",getting)
router.POST("somePost",posting)
router.PUT("/somePut",putting)
router.PATCH("/somePatch",patching)
router.DELETE("/someDelete",deleting)

1.3.2 路由参数

gin不支持路由的正则表达式

1.3.2.1 API参数

router.GET("/user/:name",func(c *gin.Context){
	name := c.Param("name")
	c.String(http.StatusOK,name)
})

运行后postman输入 http://127.0.0.1:8000/user/keegan
结果:
在这里插入图片描述
冒号:加上一个参数名组成路由参数。可以使用c.Param的方法读取其值。获取的是string。
比如/user/yjg可以匹配,而/user/和/user/yjg/不会被匹配。
代码:
编程目的:使用 c.Param方法

func main(){
	router := gin.Default()

	router.GET("/user/:name",func(c *gin.Context){
		name:= c.Param("name")
		c.JSON(http.StatusOK,gin.H{
			"name":name,
		})
	})

	http.ListenAndServe(":8080",router)
}

结果:
在这里插入图片描述

除了:,gin还提供了*号处理参数,*号能匹配的规则就更多。
代码:
编程目的:使用 c.Param方法

func main(){
	router := gin.Default()

	router.GET("/user/:name/*action",func(c *gin.Context){
		name := c.Param("name")
		action := c.Param("action")
		message := name + " is " + action
		c.String(http.StatusOK,message)
	})

	http.ListenAndServe(":8080",router)
}

结果:
在这里插入图片描述

1.3.2.2 URL参数

客户端向服务器发送请求,除了路由参数,其他的参数无非两种,查询字符串query string和报文体body参数。
所谓query string,即路由用?以后连接的key1=value2&key2=value2的形式的参数。当然这个key-value是经过urlencode编码。
URL 参数通过 DefaultQuery 或 Query 方法获取。

对于参数的处理,经常会出现参数不存在的情况,对于是否提供默认值,gin也考虑了,并且给出了一个优雅的方案,
使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值。
使用c.Query方法读取正常参数,当参数不存在的时候,返回空字串。
代码:
编程目的:使用 c.Query和c.DefaultQuery方法

func main(){
	router := gin.Default()

	router.GET("/user",func(c *gin.Context){
		// 使用c.Query方法获取url参数,参数存在就正常获取,否则返回空字符串
		nickname:= c.Query("nickname")
		// 使用c.DefaultQuery方法获取url参数,参数存在就正常获取,否则提供默认的字符串
		name := c.DefaultQuery("name","anonymous")
		//c.String(http.StatusOK,fmt.Sprintf("nickname:%s\n",name))
		c.JSON(http.StatusOK,gin.H{
			"name":name,
			"nickname":nickname,
		})
	})

	http.ListenAndServe(":8080",router)
}

结果:
在这里插入图片描述
一个字符一个字符地边看边输入,有效降低错误。当然还要明确练习的目标。

1.3.2.3 表单参数

了解表单参数是什么:
http的报文体传输数据 常见的格式 有四种:

application/json —— 前后端数据传输的格式为json
application/x-www-form-urlencoded —— 把query string的内容,放到了body体里,需要 urlencode(解决中文乱码)
application/xml —— 前后端数据传输的格式为xml
multipart/form-data —— 用于图片上传

使用:
编程目的:表单参数通过 PostForm 方法获取
代码:

func main(){
	router := gin.Default()
    // form
	router.POST("/loginForm",func(c *gin.Context){
		type1 := c.DefaultPostForm("type","alert")
		username := c.PostForm("username")
		password := c.PostForm("password")
		hobbys := c.PostFormArray("bobby")

		c.JSON(http.StatusOK,gin.H{
			"type":type1,
			"username":username,
			"password":password,
			"hobby":hobbys,
		})
	})

	http.ListenAndServe(":8080",router)
}

我们还需要提供一个html页面(login.html),来进行post请求:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="http://127.0.0.1:8080/loginForm" method="post" enctype="application/x-www-form-urlencoded">
    用户名:<input type="text" name="username">
    <br>
    密&nbsp&nbsp&nbsp码:<input type="password" name="password">
    <br>
    兴&nbsp&nbsp&nbsp趣:
    <input type="checkbox" value="girl" name="hobby">金钱
    <input type="checkbox" value="game" name="hobby">金钱
    <input type="checkbox" value="money" name="hobby">金钱
    <br>
    <input type="submit" value="登录">
</form>
</body>
</html>

注意前台的请求接口地址是:http://127.0.0.1:8080/loginForm
后台要设计的一样。
然后先运行后台程序,
然后在浏览器运行login.html文件,
结果如下:在这里插入图片描述
分析:
username和password数据我们可以获取,type获取不到就使用默认值alert。
使用PostForm形式,注意必须要设置Post的type,同时此方法中忽略URL中带的参数,所有的参数需要从Body中获得(html body标签)。

1.3.2.4 文件上传

1.3.2.4.1 上传单个文件

表单参数回顾:
multipart/form-data —— 用于图片上传
Windows查看端口—关闭端口占用

netstat -aon|findstr "8000"
taskkill /PID 8000 /F

管理员身份运行:
在这里插入图片描述

问题:
在这里插入图片描述
解决:这两个go文件用文件夹隔离。
在这里插入图片描述
在这里插入图片描述

使用gin实现文件上传。
先创建go文件:

func main(){
	router := gin.Default()
	router.POST("/upload", func(c *gin.Context) {
		file, _ := c.FormFile("file")
		log.Println(file.Filename)
		c.SaveUploadedFile(file, file.Filename)
		c.JSON(http.StatusOK, gin.H{
			"uploaded": file.Filename,
		})
	})
	router.Run(":8080")

}

再创建file.html页面:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>文件上传</title>
</head>
<body>
<form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">
  头像:
  <input type="file" name="file">
  <br>
  <input type="submit" value="提交">
</form>

</body>
</html>

结果:
运行程序后,打开浏览器选择文件—提交
在这里插入图片描述
在这里插入图片描述

1.3.2.4.2 上传多个文件

了解 上传多个文件:
所谓多个文件,无非就是多一次遍历文件,然后一次copy数据存储即可。

使用 c.Request.MultipartForm得到文件句柄,再获取文件数据,然后遍历读写。

go文件代码:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main(){
	router := gin.Default()
	router.POST("/upload", func(c *gin.Context) {
	    form,err := c.MultipartForm()
	    if err != nil {
	    	c.JSON(http.StatusBadRequest,gin.H{
	    		"get form err":err.Error(),
			})
		}

		files := form.File["files"]

		for _,file := range files {
			if err := c.SaveUploadedFile(file,file.Filename);err != nil{
				c.String(http.StatusBadRequest,fmt.Sprintf("upload file err:%s\n",err.Error()))
				return
			}
		}
		c.String(http.StatusOK,fmt.Sprintf("Upload successfully %d files",len(files)))
	})
	router.Run(":8080")

}

files.html文件代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>文件s</title>
</head>
<body>
<h1>上传多个文件</h1>

<form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">
  Files: <input type="file" name="files" multiple><br><br>
  <input type="submit" value="提交">
</form>
</body>
</html>

结果:
在这里插入图片描述
在这里插入图片描述

1.3.2.5 路由分组

了解路由分组:
是方便管理一部分相同的URL。

用到了:

c.DefaultQuery

URL请求参数。
代码:

func main(){
	router := gin.Default()
	v1 := router.Group("/v1")
	{
		v1.GET("/login",loginEndpoint)
		v1.GET("/submit",submitEndpoint)
		v1.POST("/read",readEndpoint)
	}
	router.Run(":8080")
}

func loginEndpoint(c *gin.Context){
	name := c.DefaultQuery("name","Guest")
	c.String(http.StatusOK,fmt.Sprintf("Hello %s \n",name))
}

func submitEndpoint(c *gin.Context){
	name := c.DefaultQuery("name","Guest")
	c.String(http.StatusOK,fmt.Sprintf("Hello %s \n",name))
}

func readEndpoint(c *gin.Context){
	name := c.DefaultQuery("name","Guest")
	c.String(http.StatusOK,fmt.Sprintf("Hello %s \n",name))
}

结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. Gin-Model

2.1 数据解析绑定

目前Gin支持JSON、XML、YAML和标准表单值的绑定。简单来说,,就是根据Body数据类型,将数据赋值到指定的结构体变量中 (类似于序列化和反序列化) 。
在这里插入图片描述

2.2 JSON绑定

了解JSON绑定:
JSON的绑定 就是将request中的Body中的数据按照JSON格式进行解析,解析后存储到结构体对象中。
代码:

type Login struct {
	User     string `form:"user" json:"user" xml:"user"  binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
	router := gin.Default()
	//1.binding JSON
	router.POST("/loginJSON", func(c *gin.Context) {
		var json Login
		//其实就是将request中的Body中的数据按照JSON格式解析到json变量中
		if err := c.ShouldBindJSON(&json); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		if json.User != "CSDN" || json.Password != "ZXCzxc123"{
			c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
	})

	router.Run("192.168.8.102:8082")  // 在cmd中 ipconfig获取ip,然后在centos7虚拟机运行 以下 curl命令
}

在centos7虚拟机中运行:

curl -v -X POST http://192.168.8.102:8082/loginJSON -H 'content-type:application/json' -d '{"user":"CSDN","password":"ZXCzxc123"}'

在这里插入图片描述
主力开发还得在linux上开发。
Windows 的 cmd 就不行:
在这里插入图片描述

2.3 Form表单

了解Form表单:
就是将c中的request中的body数据解析到form中。首先我们先看一下绑定普通表单的例子。
编写go文件:
【注意提交的接口地址是"http://192.168.8.102:8082/loginForm】

type Login struct {
	UserName        string   `form:"username" json:"username" xml:"username" binding:"required"`
	PassWord    string    `form:"password" json:"password" xml:"password" binding:"required"`
}
func main(){
	router := gin.Default()
	router.POST("/loginForm",func(c *gin.Context){
		var form Login
		if err := c.Bind(&form);err!=nil{
			c.JSON(http.StatusBadRequest,gin.H{"error":err.Error()})
			return
		}

		if form.UserName != "CSDN" || form.PassWord != "ZXCzxc123"{
			c.JSON(http.StatusUnauthorized,gin.H{
				"status":"unauthorized",
			})
			return
		}
		c.JSON(http.StatusOK,gin.H{
			"status":"you are logger in",
		})
	})
	router.Run("192.168.8.102:8082")
}

编写login.html文件:
【注意提交的接口地址是"http://192.168.8.102:8082/loginForm】

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="http://192.168.8.102:8082/loginForm" method="post" enctype="application/x-www-form-urlencoded">
    用户名:<input type="text" name="username">
    <br>
    密&nbsp&nbsp&nbsp码:<input type="password" name="password">
    <br>
    兴&nbsp&nbsp&nbsp趣:
    <input type="checkbox" value="girl" name="hobby">金钱
    <input type="checkbox" value="game" name="hobby">金钱
    <input type="checkbox" value="money" name="hobby">金钱
    <br>
    <input type="submit" value="登录">
</form>
</body>
</html>

启动go程序;

运行login.html代码;

结果:
在这里插入图片描述
在这里插入图片描述

2.4 URI绑定

代码:

type Login struct {
	UserName     string `form:"username" json:"username" uri:"username" xml:"username" binding:"required"`
	PassWord string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main(){
	router := gin.Default()
	router.GET("/:username/:password",func(c *gin.Context){
		var login Login
		if err := c.ShouldBindUri(&login);err!=nil{
			c.JSON(http.StatusBadRequest,gin.H{
				"msg":err,
			})
			return
		}
		c.JSON(http.StatusOK,gin.H{
			"username":login.UserName,
			"password":login.PassWord,
		})
	})
	router.Run("192.168.8.102:8081")
}

结果:
在这里插入图片描述
如果是这样写的,那么结果是:
uri参数名称不要写错了。根据事先设计好的参数名称来写代码。
在这里插入图片描述

3. 响应

3.1 JSON/XML/YAML渲染

代码:

type Message struct {
	Name    string `json:"user"`
	Message string
	Number  int
}
func main(){
	r := gin.Default()

	r.GET("/someJSON",func(c *gin.Context){
		c.JSON(http.StatusOK,gin.H{
			"message":"CSDN代码写注释",
			"status":http.StatusOK,
		})
	})

	r.GET("/moreJSON",func(c *gin.Context){

		msg := Message{"CSDN","代码写注释",9527}
		// 注意Name字段变成了user字段
		c.JSON(http.StatusOK,msg)
	})

	r.GET("/someXML",func(c *gin.Context){
		c.XML(http.StatusOK,gin.H{
			"user":"CSDN",
			"message":"代码写注释",
			"status":http.StatusOK,
		})
	})

	r.GET("/someYAML",func(c *gin.Context){
		c.YAML(http.StatusOK,gin.H{
			"message":"CSDN-代码写注释",
			"status":http.StatusOK,
		})
	})

	r.GET("/someProtoBuf",func(c *gin.Context){
        resp := []int64{int64(1),int64(2)}
        label := "test_protobuf"
        data := &protoexample.Test{
        	Label:&label,
        	Reps: resp,
		}
		c.ProtoBuf(http.StatusOK,data)
	})

	r.Run(":8081")
}

结果:
在这里插入图片描述

3.2 HTML模板渲染

gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据。

func main(){
	router := gin.Default()
	// 加载模板
	router.LoadHTMLGlob("templates/*")
	// 定义路由
	router.GET("/index",func(c *gin.Context){
		// 根据完整的文件名渲染模板,并传递参数
		c.HTML(http.StatusOK,"index.tmpl",gin.H{
			"title":"为CSDN代码写注释点赞",
		})
	})
	router.Run(":8081")
}

先要使用 LoadHTMLGlob() 或者 LoadHTMLFiles()方法来加载模板文件。

创建一个目录:templates,然后在该目录下创建一个模板文件:templates/index.tmpl

<html>
    <h1>
        {{ .title }}
    </h1>
</html>

结果:
在这里插入图片描述
不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径。
在这里插入图片描述

想要得到xxx效果,背后需要用到xxx知识,而xxx知识,是需要自己预先了解的。
go文件代码:

func main(){
	router := gin.Default()
	// 加载模板
	router.LoadHTMLGlob("templates/**/*")
	// 定义路由
	router.GET("/posts",func(c *gin.Context){
		// 根据完整的文件名渲染模板,并传递参数
		c.HTML(http.StatusOK,"posts/index.tmpl",gin.H{
			"title":"为CSDN代码写注释点赞",
		})
	})
	router.GET("/users",func(c *gin.Context){
		c.HTML(http.StatusOK,"users/index.tmpl",gin.H{
			"title":"为CSDN代码有力量点赞",
		})
	})
	router.Run(":8081")
}

templates/posts/index.tmpl 代码:

{{define "posts/index.tmpl"}}
<html>
    <h1>
        {{ .title }}
    </h1>
</html>
{{end}}

templates/users/index.tmpl 代码:

{{define "users/index.tmpl"}}
<html>
    <h1>
        {{ .title }}
    </h1>
</html>
{{end}}

结果:
在这里插入图片描述
在这里插入图片描述

3.3 文件响应

了解文件响应是什么
会使用文件响应的相关操作

可以通过阅读源码的注释了解这个函数function实现了什么功能 / 是什么东西。

代码:

func main(){
	router := gin.Default()

	// 配置静态文件服务(FTP) 第一个参数是api 第二个参数是文件夹路径 显示当前文件夹下的所有文件/指定文件
	router.StaticFS("/showDir",http.Dir("."))
	// 定义多文件的路径 使用的是系统的路径(绝对路径/相对路径都可以)
	// To use the operating system's file system implementation,
	// use :
	//      router.Static("/static", "/var/www")
	router.Static("/static","D:\\GinWeb\\templates")
	// router.StaticFile("favicon.ico", "./resources/favicon.ico")
	router.StaticFile("/imgs/a.png","D:\\GinWeb\\imgs\\a.png")

	router.Run(":8081")
}

访问当前项目的目录内容截图:
StaticFS的工作方式就像Static()一样,但是可以使用自定义的http.FileSystem来代替。
在这里插入图片描述

浏览器上获取服务器上的文件:
Static从给定的文件系统根目录提供文件
在这里插入图片描述
如果你在浏览器里访问http://localhost:8081/static/file.html,你会得到404的错误,这是因为Gin做了安全措施,防止第三方恶意罗列获取你服务器上的所有文件。
在本地的postman中通过测试:
在这里插入图片描述
在本地的cmd中通过测试:
在这里插入图片描述

StaticFile注册了一个路由,以便访问本地文件系统的一个文件。
在这里插入图片描述

参考链接:
https://www.flysnow.org/2020/07/21/golang-gin-static-files.html
https://learnku.com/docs/gin-gonic/1.5/examples-serving-static-files/6199

3.4 重定向

比如在一个网站上,你注册成功之后会跳转到(重定向到)网站的首页。
代码:

func main(){
	router := gin.Default()
	router.GET("/redirect",func(c *gin.Context){
		// 支持内部和外部的重定向
		c.Redirect(http.StatusMovedPermanently,"https://www.csdn.net")
	})
	router.Run(":8081")
}

在浏览器上输入:http://127.0.0.1:8081/redirect

会跳转到:
在这里插入图片描述

3.5 同步异步

goroutine 机制可以方便地实现异步处理
当在中间件或处理程序中启动新的Goroutines时,你不应该在原始上下文使用它,你必须使用只读的副本。
同步异步的最简单的理解:

想象一下,在开发中,服务器发送邮件,在这段时间中服务器呈现一个阻塞的状态,也就是等到邮件发送完毕后,服务器才会响应浏览器,用户才能继续浏览网页。

异步就是当发送邮件、或者文件上传,图像处理等等一些比较耗时的操作,我们可将耗时的任务放到后台异步执行,在这段时间内,服务器会正常响应浏览器,用户可以继续浏览网页,这样用户不需要等待很久,提高用户体验。
比如百度网盘上传文件的同时,你可以把这项上传文件的任务晾着放后台不管,后台任务会自行完成所需的文件上传,在这个过程中,你可以在百度网盘里继续做其他事情,比如看海贼王。

代码:

func main(){
	router := gin.Default()
	// 0. 异步
	router.GET("long_async",func(c *gin.Context){
		// goroutine中只能使用只读的上下文
		cp := c.Copy()
		go func(){
			time.Sleep(5*time.Second)
			// 注意使用只读上下文
			log.Println("Done in path" + cp.Request.URL.Path)  // c.Copy()
		}()
	})
	// 1. 同步
	router.GET("long_sync",func(c *gin.Context){
		time.Sleep(5 * time.Second)
		// 注意使用原始上下文
		log.Println("Done in path" + c.Request.URL.Path) // c
	})

	router.Run(":8081")
}

4. 中间件

golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。中间件分为全局中间件,单个路由中间件和群组中间件。

Context 是 Gin 的核心, 它的构造如下:

type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain
    index    int8

    engine   *Engine
    Keys     map[string]interface{}
    Errors   errorMsgs
    Accepted []string
}

在这里插入图片描述
handlers 我们通过源码可以知道它的类型就是 []HandlerFunc,而它的签名正是:
在这里插入图片描述
在这里插入图片描述

4.1 全局中间件

我们怎么写 HandlerFunc 就可以怎么写一个中间件。
代码:

func MiddleWare() gin.HandlerFunc{
	return func(c *gin.Context){
		t := time.Now()
		fmt.Println("before middleware......")
		// 设置request变量到Context的key中,通过Get等函数可以获得/取得
		c.Set("request","client_request")
		// 发送request之前

		// 发送request之后
		c.Next()

		// 这个c.Write是ResponseWriter,我们可以获得状态等信息

		status := c.Writer.Status()
		fmt.Println("after middlewares,",status)
		t2 := time.Since(t)
		fmt.Println("time:",t2)
	}
}
func main(){
	router := gin.Default()
	// 中间件只会对以下代码生效。
	router.Use(MiddleWare())
	{
		router.GET("/middleware",func(c *gin.Context){
			// 获取gin上下文中的变量
			request := c.MustGet("request").(string)
			req,_ := c.Get("request")
			fmt.Println("request:",request)
			c.JSON(http.StatusOK,gin.H{
				"middle_request":request,
				"request":req,
			})
		})

		router.GET("/before",func(c *gin.Context){
			request := c.MustGet("request").(string)
			c.JSON(http.StatusOK,gin.H{
				"middle_request":request,
			})
		})
	}

	router.Run(":8081")
}

结果:
在这里插入图片描述
在这里插入图片描述

先定义一个中间件函数,
该函数很简单,只会给c上下文添加一个属性并赋值。
后面的路由处理器,可以根据被中间件装饰后提取其值。
需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。
只有注册了中间件以下代码的路由函数规则,才会被中间件装饰。

使用router装饰中间件,然后在/middlerware即可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。

使用花括号包含被装饰的路由函数只是一个代码规范,即使没有被包含在内的路由函数,只要使用router进行路由,都等于被装饰了。
想要区分权限范围,可以使用组返回的对象注册中间件。

4.2 Next()方法

怎么解决一个请求和一个响应经中间件呢?
就是 c.Next(),所有中间件都有 Request 和 Response 的分水岭, 就是 c.Next()。

接下来就要剖析gin源码了。
摘抄自
gin简单剖析
api服务创建

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}

3步创建了一个api服务:
1)gin.Default()获得一个Engine实例
2)engine.GET()添加一个Get请求的路由的逻辑
3)engine.Run()启动服务

gin.Default 剖析

func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		AppEngine:              defaultAppEngine,
		UseRawPath:             false,
		UnescapePathValues:     true,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{"{{", "}}"},
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

gin.Default 通过 New 创建了 Engine 实例,
在这里插入图片描述
并 Use 了 Logger Recovery 两个 HandlerFunc 中间件。
注释也介绍了 默认返回一个引擎实例,其中包含日志记录器和崩溃恢复中间件。

func (c *Context) Next() {
	c.index++
	s := int8(len(c.handlers))
	for ; c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}

代码案例:

func MiddleWare() gin.HandlerFunc{
	return func(c *gin.Context){
		t := time.Now()
		fmt.Println("before middleware......")
		// 设置request变量到Context的key中,通过Get等函数可以获得/取得
		c.Set("request","client_request")
		// 发送request之前

		// 发送request之后
		c.Next() // Gin中间件Context.Next() 一定要明白这个c这个对象是啥对象

		// 这个c.Write是ResponseWriter,我们可以获得状态等信息

		status := c.Writer.Status()
		fmt.Println("after middlewares,",status)
		t2 := time.Since(t)
		fmt.Println("time:",t2)
	}
}
func main(){
	router := gin.Default()
	// 中间件只会对以下代码生效。
	router.Use(MiddleWare())
	{
		router.GET("/middleware",func(c *gin.Context){
			// 获取gin上下文中的变量
			request := c.MustGet("request").(string)
			req,_ := c.Get("request")
			fmt.Println("request:",request)
			c.JSON(http.StatusOK,gin.H{
				"middle_request":request,
				"request":req,
			})
		})

		router.GET("/before",func(c *gin.Context){
			request := c.MustGet("request").(string)
			c.JSON(http.StatusOK,gin.H{
				"middle_request":request,
			})
		})
	}

	router.Run(":8081")
}

gin.Default()获得一个Engine实例,
Engine实例中engine.Use(Logger(), Recovery())已经使用了Logger()函数,
服务端使用了Use方法导入了middleware
当请求/middleware来的时候,会执行MiddleWare()
并且我们知道在GET注册的时候,同时注册了匿名函数,在gin.Context对象中会有Next()方法:

在这里插入图片描述
Next()方法
在这里插入图片描述
摘抄自
从Next()方法我们可以看到它会遍历执行全部handlers(中间件也是handler),
一个请求过来, Gin 会主动调用 c.Next() 一次。因为 handlers 是 slice ,所以后来者中间件会追加到尾部。这样就形成了形如 m1(m2(f())) 的调用链。
正如下面数字① ② 标注的一样, 我们会依次执行如下的调用:

m1① -> m2② -> f -> m2② -> m1①

在这里插入图片描述

所以中间件中调不调用Next()方法并不会影响后续中间件的执行。

既然中间件中没有Next()不影响后续中间件的执行,那么在当前中间件中调用c.Next()的作用又是什么呢?
结论是:
在当前中间件中调用c.Next()方法时会中断当前中间件中后续的逻辑,
转而执行后续的中间件和handlers,
等到它们全部执行完毕之后再回来执行当前中间件/本函数的后续代码。

代码案例执行截图:
在这里插入图片描述
在本案例中,代码执行到了c.Next()的时候,下一步执行注册的匿名函数,然后再回到本函数(包含c.Next()的函数)继续执行,
所以本案例的Println的输出顺序是:

fmt.Println("request:",request)  // 注册的匿名函数
fmt.Println("after middlewares,",status) // 本函数(包含`c.Next()`的函数)c.Next()的位置之下
fmt.Println("time:",t2) // 本函数(包含`c.Next()`的函数)c.Next()的位置之下

如果将c.Next()放到fmt.Println("after middlewares,",status)后面,那么输出顺序就是:

fmt.Println("after middlewares,",status) // 本函数(包含`c.Next()`的函数)c.Next()的位置之上
fmt.Println("request:",request)  // 注册的匿名函数
fmt.Println("time:",t2) // 本函数(包含`c.Next()`的函数)c.Next()的位置之下

所以一切都取决于c.Next()执行的位置。

4.3 单个路由中间件

gin提供了对指定的路由函数进行注册。
部分代码:将部分代码放到router.Use(MiddleWare())之前,同样也能看到/before被装饰了中间件,

router.GET("/before",MiddleWare(),func(c *gin.Context){
	request := c.MustGet("request").(string)
	c.JSON(http.StatusOK,gin.H{
		"middle_request":request,
	})
})

通过浏览器访问如图所示:
在这里插入图片描述
后台输出:一切由c.Next()位置决定的 + 还有请求了哪些接口。
在这里插入图片描述

完整代码:

func MiddleWare() gin.HandlerFunc{
	return func(c *gin.Context){
		t := time.Now()
		fmt.Println("before middleware......")
		// 设置request变量到Context的key中,通过Get等函数可以获得/取得
		c.Set("request","client_request")
		// 发送request之前

		// 发送request之后
		c.Next() // Gin中间件Context.Next() 一定要明白这个c这个对象是啥对象

		// 这个c.Write是ResponseWriter,我们可以获得状态等信息

		status := c.Writer.Status()
		fmt.Println("after middlewares,",status)
		t2 := time.Since(t)
		fmt.Println("time:",t2)
	}
}
func main(){
	router := gin.Default()
	router.GET("/before",MiddleWare(),func(c *gin.Context){
		request := c.MustGet("request").(string)
		c.JSON(http.StatusOK,gin.H{
			"middle_request":request,
		})
	})
	// 中间件只会对以下代码生效。
	router.Use(MiddleWare())
	{
		router.GET("/middleware",func(c *gin.Context){
			// 获取gin上下文中的变量
			request := c.MustGet("request").(string)
			req,_ := c.Get("request")
			fmt.Println("request:",request)
			c.JSON(http.StatusOK,gin.H{
				"middle_request":request,
				"request":req,
			})
		})
	}

	router.Run(":8081")
}

4.4 中间件实践

中间件最大的作用,莫过于用于一些记录log,错误handler,还有就是对部分接口的鉴权。下面就实现一个简易的鉴权中间件。
简单认证BasicAuth
先定义私有数据;

然后使用 gin.BasicAuth中间件,设置授权用户;

最后定义路由;
完整程序代码:

// 模拟私有数据
var secrets = gin.H{
	"keagan":    gin.H{"email":"keagan@163.com","phone":"18888888888"},
}

func main(){
	router := gin.Default()

	// 使用gin.BasicAuth中间件,设置授权用户
	authorized := router.Group("/admin",gin.BasicAuth(gin.Accounts{
		"keagan":"admin123",
	}))

    // 定义路由
    authorized.GET("/secrets",func(c *gin.Context){
    	// 获取提交的用户名(AuthUserKey)
    	user := c.MustGet(gin.AuthUserKey).(string)
    	if secret,ok := secrets[user];ok{
    		c.JSON(http.StatusOK,gin.H{
    			"user":user,
    			"secret":secret,
			})
		} else{
			c.JSON(http.StatusOK,gin.H{
				"user":user,
				"secret":"NO SECRET",
			})
		}
	})
	router.Run(":8081")
}

运行程序,在浏览器输入网址 http://127.0.0.1:8081/admin/secrets,

然后会弹出一个登录框,需要你输入正确的用户名和密码:
在这里插入图片描述
登录成功之后:
在这里插入图片描述

在这里插入图片描述
…我也是有底线的…

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:52:24  更:2022-03-08 22:53:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 2:33:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码