【声明】

非完全原创,部分内容来自于学习其他人的理论和B站视频。如果有侵权,请联系我,可以立即删除掉。
参考文章:
(1)Gin框架(一):基础概览
(2)gin框架剖析(一)

一、简介、安装及快速入手

1、gin简介

Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json

它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。中文文档较为齐全

2、下载安装

要求golang的版本在1.15及以上,下载:
go get -u github.com/gin-gonic/gin

问题

(1)连接超时:开启gomodule,设置代理

go env -w GO111MODULE="on"
go env -w GOPROXY="https://goproxy.cn","https://goproxy.io"

(2)go mod的错误:解决方案

vscode的gopath和go mod无法共存

  • 原因:go mod会将依赖包下载在pkg/mod下面,vscode默认导包路径是GOROOT/srcGOPATH/src,因此在导包时,vscode自动检查机制会显示找不到包
  • 解决方法:
    (1)vscode设置中搜索go.useLanguageServer并关闭,该方法虽然不报告警但没有代码提示了
    (2)将整个项目移到GOPATH路径外面,然后项目下面敲go mod init xxxgo install github.com/gin-gonic/gin@lastest,即可完成导包

在这里插入图片描述

3、快速使用

3.1、官方快速入手的demo

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

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

3.2、运行结果

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

gin快速入手

4、REST

4.1、概念

REST,即Representational State Transfer的缩写,它是一种架构风格,跟编程语言无关,跟平台无关,采用HTTP做传输协议。“表现层状态转化”。如果一个架构符合REST原则,就称它为RESTful架构。

REST强调用URL定位资源、用HTTP动词(GET,POST,PUT,DELETE)描述操作

设计风格:

  • 资源:是网络上的一个实体,如图片,数据,歌曲等。资源可以通过载体表示,如图片可以用JPG格式,文本可以用TXT格式,目前最流行的表示载体是JSON
  • 统一接口:数据操作CRUD(create, read, update, delete),对应到HTTP方法:GET获取资源,POST新建资源(或更新资源),PUT更新资源,DELETE删除资源。通过上述HTTP方法便统一了对数据的操作
  • URIUniform Resourse identifier,统一资源位置。一个URI是某一个资源的地址或识别符,一个资源可以有多个URL
  • 无状态:所有的资源都可以通过URL直接定位,不依赖于其他资源或状态。例如,想要接收微信好友的消息,就必须先登录上微信,此时就构成了依赖关系,因此是有状态的
  • 过滤信息:为了限定信息的多少或者获取特定的信息,可以通过前端参数过滤返回结果。

4.2、示例代码

4.2.1、代码
func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "get",
		})
	})
	r.POST("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "post",
		})
	})
	r.PUT("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "put",
		})
	})
	r.DELETE("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "delete",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
4.2.2、postman测试

get/post/put/delete测试

二、gin渲染

1、html文件渲染

1.1、定义两个html文件

(1)html_file/index.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<body>
    this is {{.name}} page
</body>

(2)html_file/home.html

{{define "gets/home.html"}}
<!DOCTYPE html>
<html lang="en">
<body>
    this is {{.title}} page
</body>
</html>
{{end}}

(3)html_file/demo.tmpl

<!DOCTYPE html>
<html lang="en">
<body>
    <div>
        {{/* 将'.text'对应的value作为参数传给htmlparse函数 */}}
        {{ .text | htmlparse }}
    </div>
</body>
</html>

1.2、gin服务端的代码

func main() {
	//1. 设置默认的路由引擎
	r := gin.Default()
	//自定义函数"htmlparse", 将传入的字符串解析为网页要显示的内容
	r.SetFuncMap(template.FuncMap{
		"htmlparse": func(str string) template.HTML {
			return template.HTML(str)
		},
	})
	
	//2. 加载html文件
	//r.LoadHTMLFiles("html_file/index.tmpl", "html_file/home.html")
	r.LoadHTMLGlob("html_file/*")
	
	//3. 接收客户端的GET请求并处理
	r.GET("/index", func(c *gin.Context) {
		//方法1:通过文件名来查找html文件
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"name": "index",
		})
	})
	
	r.GET("/home", func(c *gin.Context) {
		//方法2:通过文件中define的名字来查找html文件
		c.HTML(http.StatusOK, "gets/home.html", gin.H{
			"title": "home",
		})
	})
	
	m := map[string]string{"text": "<h1><a href='www.baidu.com'>百度一下</a></h1>"}
	r.GET("/demo", func(c *gin.Context) {
		c.HTML(http.StatusOK, "demo.tmpl", m)
	})
	
	//4. 设置端口及监听服务端的连接
	r.Run(":8088")
}

1.3、浏览器客户端测试

index
home
demo

2、静态文件渲染

静态资源一般包括:css、js、image等文件

2.1、创建文件夹放置静态文件

三种静态文件的放置目录

(1)staic/css/style.css

*{
    color:coral;
    background-color: darkturquoise;
}

(2)staic/js/demo.js

alert("demo test")

(3)staic/img/swim.jpeg

2.2、index.html文件

<!DOCTYPE html>
<head>
    <link rel="stylesheet" href="demo/css/style.css">
    <title>index</title>
</head>
<body>
    <div><h1>Hello, this is a {{.}} page</h1></div>
    <img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fzhongces3.sina.com.cn%2Fproduct%2F20210316%2F6637448af6e94692a1b7c95cd40e70f3.jpeg&refer=http%3A%2F%2Fzhongces3.sina.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1663868287&t=f770b159851a8d2dcf538cdc58d58329">
    <img src="demo/img/swim.jpeg"/>
    <script src="demo/js/demo.js">       
    </script>
</body>

2.3、gin服务端代码

func main() {
	r := gin.Default()
	//将html文件中的'demo'指向到'./static'目录
	r.Static("/demo", "./static")
	r.LoadHTMLFiles("index.html")
	r.GET("/demo", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", "demo_index")
	})
	r.Run(":9000")
}

2.4、浏览器的展示效果

在这里插入图片描述

2.5、下载网站上的前端模板文件进行渲染

(1)下载前端模板
下载的路径:https://sc.chinaz.com/moban/190107489630.htm
(2)文件结构
将下载下来的文件放置在web/front目录下,并且将目录下的index.htmlcss/images/js文件的引用加上前缀demo/。文件的目录结构如下:
在这里插入图片描述
(3)服务端代码

func main() {
	r := gin.Default()
	//将html文件中的'demo'指向到'./static'目录
	r.Static("/demo", "./front")
	r.LoadHTMLFiles("front/index.html")
	r.GET("/demo", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})
	r.Run(":9000")
}

(4)效果
在这里插入图片描述

3、其他类型数据的渲染

3.1、json、xml、yaml

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

	//1. json渲染
	r.GET("/json", func(c *gin.Context) {
		//gin.H是map[string]interface{}类型,也可以自定义map进行传参
		c.JSON(http.StatusOK, gin.H{"Name": "Hello, golang"})
	})
	r.GET("/json_struct", func(c *gin.Context) {
		type MSG struct {
			Name string `json:usr`
			Age  int
		}
		msg := MSG{"go", 20}
		c.JSON(http.StatusOK, msg)
	})

	//2. xml渲染
	r.GET("/xml", func(c *gin.Context) {
		//gin.H是map[string]interface{}类型,也可以自定义map进行传参
		c.XML(http.StatusOK, gin.H{"Name": "Hello, golang"})
	})
	r.GET("/xml_struct", func(c *gin.Context) {
		type MSG struct {
			Name string `json:usr`
			Age  int
		}
		msg := MSG{"go", 20}
		c.XML(http.StatusOK, msg)
	})

	//3. yaml渲染
	r.GET("/yaml", func(c *gin.Context) {
		c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
	})

	r.Run(":8088")
}

渲染的结果

三、gin获取参数

1、url和表单参数

1.1、GET url参数获取的相关方法

Query方法适用于/path?id=1234&name=Manu&value=的URL参数解析
Param方法适用于/user/id/name/addr格式的URL参数解析,并且解析的参数个数需要全部匹配上,否则返回404

1.1.1、Query方法
// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
//     GET /path?id=1234&name=Manu&value=
// 	   c.Query("id") == "1234"
// 	   c.Query("name") == "Manu"
// 	   c.Query("value") == ""
// 	   c.Query("wtf") == ""
func (c *Context) Query(key string) (value string)

// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
//     GET /?name=Manu&lastname=
//     c.DefaultQuery("name", "unknown") == "Manu"
//     c.DefaultQuery("id", "none") == "none"
//     c.DefaultQuery("lastname", "none") == ""
func (c *Context) DefaultQuery(key, defaultValue string) string

// GetQuery is like Query(), it returns the keyed url query value
// if it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns `("", false)`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
//     GET /?name=Manu&lastname=
//     ("Manu", true) == c.GetQuery("name")
//     ("", false) == c.GetQuery("id")
//     ("", true) == c.GetQuery("lastname")
func (c *Context) GetQuery(key string) (string, bool) 
1.1.2、Param方法
// Param returns the value of the URL param.
// It is a shortcut for c.Params.ByName(key)
//     router.GET("/user/:id", func(c *gin.Context) {
//         // a GET request to /user/john
//         id := c.Param("id") // id == "john"
//     })
func (c *Context) Param(key string) string

1.2、POST提交的参数数据获取的相关方法

1.2.1、PostForm方法
// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`.
func (c *Context) PostForm(key string) (value string)

// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns the specified defaultValue string.
// See: PostForm() and GetPostForm() for further information.
func (c *Context) DefaultPostForm(key, defaultValue string) string

// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded
// form or multipart form when it exists `(value, true)` (even when the value is an empty string), otherwise it returns ("", false).
// For example, during a PATCH request to update the user's email:
//     email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
// 	   email=     -->  ("", true) := GetPostForm("email") // set email to ""
//                -->  ("", false) := GetPostForm("email") // do nothing with email
func (c *Context) GetPostForm(key string) (string, bool)

// GetPostFormMap returns a map for a given form key, plus a boolean value
// whether at least one value exists for the given key.
func (c *Context) GetPostFormMap(key string) (map[string]string, bool)

// GetPostFormArray returns a slice of strings for a given form key, plus
// a boolean value whether at least one value exists for the given key.
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) 
1.2.2、GetRawData方法

一般先获取数据流,然后反序列为json格式的map或者struct

// GetRawData returns stream data.
func (c *Context) GetRawData() ([]byte, error)

1.3、url和表单参数示例

1.3.1、gin代码
func main() {
	r := gin.Default()
	r.GET("/get/query", func(c *gin.Context) {
		//获取query参数:URL中?后面携带的参数
		id := c.Query("id")                      //未获取到返回""
		name := c.DefaultQuery("name", "golang") //未获取到返回"golang"
		addr, ok := c.GetQuery("addr")           //ok表示是否获取到,未获取到返回""
		if !ok {
			addr = "nil"
		}
		c.JSON(http.StatusOK, gin.H{
			"id":   id,
			"name": name,
			"addr": addr,
		})
	})

	r.GET("path/:name/:age", func(ctx *gin.Context) {
		//客户侧请求的参数通过URL路径自动匹配,若匹配失败返回""
		name, str_age := ctx.Param("name"), ctx.Param("age")
		age, _ := strconv.Atoi(str_age)
		m := map[string]interface{}{"name": name, "age": age}
		ctx.JSON(http.StatusOK, m)
	})

	r.POST("/post/form", func(c *gin.Context) {
		//获取客户端POST提交的表单数据
		id := c.PostForm("id")
		name := c.DefaultPostForm("name", "none")
		passwd, _ := c.GetPostForm("passwd")
		m, _ := c.GetPostFormMap("map")
		arr, _ := c.GetPostFormArray("arr")
		//获取post表单中的所有数据
		for k, v := range c.Request.PostForm {
			fmt.Printf("key: %v\tvalue: %v\n", k, v)
		}
		c.JSON(http.StatusOK, gin.H{
			"id":     id,
			"name":   name,
			"passwd": passwd,
			"map":    m,
			"arr":    arr,
		})
	})

	r.POST("post/json", func(c *gin.Context) {
		//获取客户端POST提交的json数据
		data, err := c.GetRawData()
		if err != nil {
			c.JSON(http.StatusBadRequest, "Get Data failed")
		} else {
			m := make(map[string]interface{})
			err = json.Unmarshal(data, &m)
			if err != nil {
				c.JSON(http.StatusBadRequest, "Data Unmarshal failed")
			} else {
				c.JSON(http.StatusOK, m)
			}
		}
	})

	r.Run(":8088")
}
1.3.2、运行结果

(1)Query方法
query
(2)Param方法
param
(3)PostForm方法
PostForm
(4)GetRawData方法
GetRawData方法

2、参数绑定

2.1、相关方法

// ShouldBind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example:
//     "application/json" --> JSON binding
//     "application/xml"  --> XML binding
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
func (c *Context) ShouldBind(obj any) error 

为了能够更方便的获取请求相关参数,可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中Query、form表单、JSON、XML等参数到结构体中

ShouldBind 会按照下面的顺序解析请求中的数据完成绑定:

  • 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  • 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)

2.2、测试

2.2.1、gin代码
// Binding from JSON、form
type User struct {
	User     string `form:"user" json:"user" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

func main() {
	rout := gin.Default()
	var usr User
	//可以是: json?user=cat&password=20220826
	//但不能是:  json/cat/20220826
	rout.GET("/usr/json", func(ctx *gin.Context) {
		usr = User{}
		if err := ctx.ShouldBind(&usr); err == nil {
			ctx.JSON(http.StatusOK, gin.H{
				"status": http.StatusOK,
				"user":   usr,
			})
		} else {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	rout.POST("/usr/post", func(ctx *gin.Context) {
		usr = User{}
		if err := ctx.ShouldBind(&usr); err == nil {
			ctx.JSON(http.StatusOK, gin.H{
				"status": http.StatusOK,
				"user":   usr,
			})
		} else {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	rout.Run(":8088")
}
2.2.2、运行结果

(1)GET请求
get
(2)POST请求
post:表单数据或者query的方法

四、gin文件上传

1、相关方法

特别注意:保存文件到服务端本地时,要求目录已存在,否则会报错

// FormFile returns the first file for the provided form key.
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)

// MultipartForm is the parsed multipart form, including file uploads.
func (c *Context) MultipartForm() (*multipart.Form, error)

// SaveUploadedFile uploads the form file to specific dst. 
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error

2、gin代码(包括单文件和多文件上传)

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

	//加载html文件并接收GET请求
	r.LoadHTMLFiles("./index.html")
	r.GET("/up_page", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	//接收客户侧上传文件后的请求
	r.POST("/file", func(ctx *gin.Context) {
		file, err := ctx.FormFile("f1")
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"status": http.StatusBadRequest,
				"err":    err,
			})
			return
		}
		dst := fmt.Sprintf("./tmp/%s", file.Filename)
		if err = ctx.SaveUploadedFile(file, dst); err != nil {
			ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("file save err[%s]", err))
		} else {
			ctx.JSON(http.StatusOK, "file upload and save success")
		}
	})

	r.POST("/mltifile", func(ctx *gin.Context) {
		form, err := ctx.MultipartForm()
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"status": http.StatusBadRequest,
				"err":    err,
			})
			return
		}
		files := form.File["f1"]
		for index, file := range files {
			dst := path.Join("./multi", strconv.Itoa(index)+"_"+file.Filename)
			if err = ctx.SaveUploadedFile(file, dst); err != nil {
				ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("file save err[%s]", err))
				return
			}
		}
		ctx.JSON(http.StatusOK, "all file upload and save success")
	})
	r.Run(":9000")
}

3、文件上传结果

3.1、单文件上传

(1)使用html上传文件

<!DOCTYPE html>
<html lang="zh-CN">
    <body>
        <!-- 一定要指定action(待跳转的POST网页路径)、enctype(传输数据为二进制类型) -->
        <form action="/file", method="post" enctype="multipart/form-data">
            <input type="file" name="f1">
            <input type="submit" value="upload">
        </form>
    </body>
</html>

html上传
(2)postman直接上传
postman上传

3.2、多文件上传

<!DOCTYPE html>
<html lang="zh-CN">
    <body>
        <!-- 一定要指定action(待跳转的POST网页路径)、enctype(传输数据为二进制类型)、multiple(多文件选择) -->
        <form action="/mltifile", method="post" enctype="multipart/form-data">
            <input type="file" name="f1" multiple>
            <input type="submit" value="upload">
        </form>
    </body>
</html>

在这里插入图片描述

五、gin路由和重定向

1、路由(组)

路由的基本原理就是构造一个路由地址的前缀树。Gin框架中的路由使用的是httprouter库

1.1、普通路由

1.1.1、相关方法

除了指定的GET/POST/PUT等方法之外,还有一个匹配所有请求集合的万能处理函数Any,一个为没有配置处理函数的路由添加自定义处理的函数NoRoute,其默认返回404

// Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
	for _, method := range anyMethods {
		group.handle(method, relativePath, handlers)
	}
	return group.returnObj()
}

// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
func (engine *Engine) NoRoute(handlers ...HandlerFunc)

// anyMethods for RouterGroup Any method
anyMethods = []string{
	http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
	http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
	http.MethodTrace,
}
1.1.2、测试
func main() {
	r := gin.Default()
	r.Any("/usr", func(ctx *gin.Context) {
		switch ctx.Request.Method {
		case http.MethodGet:
			ctx.JSON(http.StatusOK, gin.H{"method": "GET"})
		case http.MethodPost:
			ctx.JSON(http.StatusOK, gin.H{"method": "POST"})
		case "TRACE":
			ctx.JSON(200, gin.H{"method": "TRACE"})
		default:
			ctx.JSON(http.StatusOK, gin.H{"method": "other"})
		}
	})
	r.NoRoute(func(ctx *gin.Context) {
		ctx.JSON(http.StatusOK, gin.H{"err": "page not found"})
		//ctx.HTML(http.StatusNotFound, "demo_404.html", nil)
	})
	r.Run(":8099")
}

普通路由

1.2、路由组

将拥有共同URL前缀的路由划分为一个路由组,代码看起来更直观

1.2.1、相关方法
// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup
1.2.2、示例代码
func main() {
	r := gin.Default()
	g := r.Group("/usr") //将拥有共同URL前缀的路由划分为一个路由组
	{
		g.GET("/index", func(ctx *gin.Context) {
			ctx.JSON(http.StatusOK, gin.H{"url": ctx.Request.URL.Path})
		})
		g.GET("/login", func(ctx *gin.Context) {
			ctx.JSON(http.StatusOK, gin.H{"url": ctx.Request.URL.Path})
		})
		g.POST("/login", func(ctx *gin.Context) {
			ctx.JSON(http.StatusOK, gin.H{"mthod": ctx.Request.Method})
		})

		//路由组支持嵌套, 此处可以是`shop`, 也可以是`/shop`
		s := g.Group("/shop")
		{
			s.GET("/index", func(ctx *gin.Context) {
				ctx.JSON(http.StatusOK, gin.H{"mthod": ctx.Request.Method, "url": ctx.Request.URL.Path})
			})
			s.POST("/index", func(ctx *gin.Context) {
				ctx.JSON(http.StatusOK, gin.H{"mthod": ctx.Request.Method, "url": ctx.Request.URL.Path})
			})
		}
	}
	r.Run(":9099")
}
1.2.3、运行结果

路由组和嵌套

2、重定向

2.1、HTTP重定向

// Redirect returns an HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string)

2.2、路由重定向

// HandleContext re-enters a context that has been rewritten.
// This can be done by setting c.Request.URL.Path to your new target.
// Disclaimer: You can loop yourself to deal with this, use wisely.
func (engine *Engine) HandleContext(c *Context) 

2.3、测试

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

	r.GET("/http", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/") // 重定向到http的网址
	})

	r.GET("/real", func(c *gin.Context) {
		c.JSON(http.StatusOK, "real url")
	})
	r.GET("/show", func(c *gin.Context) {
		c.Request.URL.Path = "/real" //指定重定向的URL
		r.HandleContext(c)
	})
	r.Run(":9099")
}

重定向

六、gin中间件

开发者在处理请求的过程中,有时候需要加入自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

1、gin.Default默认的中间件

gin.Default默认注册了中间件LoggerRecovery,其中Logger默认将log写入到控制台;Recovery捕获panic并返回500

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

// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
// By default, gin.DefaultWriter = os.Stdout.
func Logger() HandlerFunc

// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() HandlerFunc

2、中间件注册

2.1、相关函数

(1)Next和Abort函数

  • Next函数将 执行[下一个]处理函数,剩余的处理函数按照顺序入栈,剩余处理函数依次出栈后继续执行Next所在处理函数剩余的代码
  • Abort函数将 不执行剩余的处理函数,直接跳过剩余的处理函数,继续执行Next所在处理函数剩余的代码
  • 根据该特性,可以将登录的处理函数注册到全局路由中,登录成功后调用Next,登录失败则调用Abort

(2)Set和Get函数

  • 在处理函数的上下文中设置值,在后面的处理函数中可取值,以达到数据共享的目的
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() 

// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
// Let's say you have an authorization middleware that validates that the current request is authorized.
// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
// for this request are not called.
func (c *Context) Abort()

// Set is used to store a new key/value pair exclusively for this context.
// It also lazy initializes  c.Keys if it was not used previously.
func (c *Context) Set(key string, value any)

// Get returns the value for the given key, ie: (value, true).
// If the value does not exist it returns (nil, false)
func (c *Context) Get(key string) (value any, exists bool)

// MustGet returns the value for the given key if it exists, otherwise it panics.
func (c *Context) MustGet(key string) any

2.2、中间件注册

2.2.1、gin代码
var cnt = 0

func middle() func(c *gin.Context) {
	return func(c *gin.Context) {
		cur := cnt
		cnt++
		fmt.Println("[", cur, "]code in middle")
		start := time.Now()
		//上下文中设置值, 后续可取值
		c.Set("hello", "golang")
		c.Next() //执行[下一个]处理函数,处理函数按照顺序入栈
		c.JSON(http.StatusOK, gin.H{"spend": strconv.FormatInt(int64(time.Since(start)), 10) + " ns"})
		fmt.Println("[", cur, "]code out middle")
	}
}

func abort() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("code in abort")
		start := time.Now()
		c.Abort() //不执行剩余的处理函数
		c.JSON(http.StatusOK, gin.H{"spend": time.Since(start)})
		fmt.Println("code out abort")
	}
}

func main() {
	r := gin.New()
	r.Use(middle()) //注册全局中间件

	r.GET("/demo", func(ctx *gin.Context) {
		val, _ := ctx.Get("hello") //获取上下文的值
		para, _ := ctx.GetQuery("name")
		fmt.Println("get url ", ctx.Request.URL.Path)
		ctx.JSON(http.StatusOK, gin.H{"get hello": val, "get query": para})
	})

	//给"/index"路由单独添加1个中间件
	r.GET("/index", middle(), func(ctx *gin.Context) {
		val := ctx.MustGet("hello").(string) //获取上下文的值
		fmt.Println("get url ", ctx.Request.URL.Path)
		ctx.JSON(http.StatusOK, gin.H{"url": ctx.Request.URL.Path, "get hello": val})
	})

	g := r.Group("/login", middle()) //给路由组添加中间件
	{
		g.GET("/usr", func(ctx *gin.Context) {
			fmt.Println("get url ", ctx.Request.URL.Path)
			ctx.JSON(http.StatusOK, gin.H{"url": ctx.Request.URL.Path})
		}, abort(), func(ctx *gin.Context) {
			fmt.Println("get method ", ctx.Request.Method)
			ctx.JSON(http.StatusOK, gin.H{"method": ctx.Request.Method})
		})
	}
	r.Run(":8088")
}
2.2.2、测试/demo

执行顺序:全局路由注册的middle中间件 ==> 自定义的demo处理函数
demo

2.2.3、测试/index

执行顺序:全局路由注册的middle中间件 ==> index路由注册的middle中间件 ==> 自定义的index处理函数
index

2.2.4、测试/login/usr

执行顺序:全局路由注册的middle中间件 ==> login路由组注册的middle中间件 ==> 自定义的url处理函数 ==> login/usr路由注册的abort中间件(调用Abort函数,因此自定义的method处理函数不执行)
在这里插入图片描述

更多推荐