1. Gin框架初识
1.1 Gin的安装
直接参考官方的文档
1.1.1 安装过程
- 下载并安装 gin:
$ go get -u github.com/gin-gonic/gin
- 将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
1.1.2 解决安装失败
设置代理:https://goproxy.cn
失败的原因大概率是因为没有设置代理(GOPROXY)。两种方式设置:
① 创建项目时设置
② 在设置里修改
1.2 Gin的简单使用
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 返回默认的路由引擎
r.GET("/hello", sayHello)
r.Run() // 默认8080端口,可以自行修改
}
func sayHello(c *gin.Context) {
c.JSON(200, gin.H{
"massage": "hello golang!",
})
}
确实看起来简单。
1.3 RESTful API
1.3.1 浅说一下
一种http编程的代码风格,比较好的东西,之后可以了解一下,其中有提到的一点:
GET 来查询记录
POST 来创建记录
PUT 来更新记录
DELETE 来删除记录
1.3.2 测试一下
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 返回默认的路由引擎
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "GET",
})
})
r.POST("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "POST",
})
})
r.PUT("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "PUT",
})
})
r.DELETE("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "DELETE",
})
})
r.Run() // 默认8080,可自行修改
}
启动之后,通过postman发送请求,得到的结果如下:
2. Gin框架模板渲染
2.1 单个模板
有了之前用标准库的经验,用gin也更好理解了:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 模板解析
r.LoadHTMLFiles("./templates/index.tmpl")
// 模板渲染
r.GET("/index", func(c *gin.Context) {
// http请求
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "firecar",
})
})
r.Run(":8080")
}
2.2 多个模板
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 模板解析
//r.LoadHTMLFiles("./templates/index.tmpl")
r.LoadHTMLGlob("templates/**/*")
// 模板渲染
r.GET("posts/index", func(c *gin.Context) {
// http请求
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "posts",
})
})
r.GET("users/index", func(c *gin.Context) {
// http请求
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "users",
})
})
r.Run(":8080")
}
-
模板解析的时候()内是要解析tmpl文件的相对路径
-
r.GET()
的第一个参数,代表的是需要访问的url相对路径 -
c.HTML()
的第二个参数是要渲染的模板的文件名(有重复的需要再模板内用{{define xxx}} {{end}}
来重命名)2.3 自定义模板函数
使用:
r.SetFuncMap(template.FuncMap{ "xxx" : func(xx)xx { return xx } })
例如:
package main import ( "github.com/gin-gonic/gin" "html/template" "net/http" ) func main() { r := gin.Default() // gin框架中给模板添加自定义函数 r.SetFuncMap(template.FuncMap{ "safe": func(str string) template.HTML { return template.HTML(str) }, }) // 模板解析 r.LoadHTMLGlob("templates/**/*") // 模板渲染 r.GET("posts/index", func(c *gin.Context) { // http请求 c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ "title": "<h1>wooowowowow</h1>", }) }) r.GET("users/index", func(c *gin.Context) { // http请求 c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ "title": "users", }) }) r.Run(":8080") }
{{define "posts/index.tmpl"}} <!DOCTYPE html> <html> <body> {{safe .title}} posts </body> </html> {{end}}
2.4 静态文件
html页面上用到的样式文件.css js文件 图片等
把这些静态文件存在了statics里面。
在main函数里渲染模板之前添加上r.Static("/statics", "./statics")
就可以了,第一个参数是之后调用时用的相对位置(可以是不和文件目录相同),第二个参数是文件所在的目录。
① 为什么.css不调用
我在这么运行的时候发现,网页上是没有使用.css的:
body {
background-color: cadetblue;
}
在终端上看一下发现确实没有调用这个.css的信息。
OK发现原来是我他妈的href写成了herf……标记的这么明显了我竟然没发现。
修改之后发现就调用成功了。
2.5 前端模板使用
直接在网上扒了一个单页前端模板(不然html太多了太麻烦了)
2.6 返回json
2.6.1 通过map传数据
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/json", func(c *gin.Context) {
data := map[string]any{
"name": "firecar",
"message": "hello golang",
"age": 18,
}
c.JSON(http.StatusOK, data)
})
r.Run()
}
网页上显示{"age":18,"message":"hello golang","name":"firecar"}
gin的作者考虑到大伙喜欢用map[string] interface{}
所以直接做了一个快捷调用gin.H
代表的就是这个类型。因此可以写成下面的样子:
c.JSON(200, gin.H{
"name": "firecar",
"message": "hello golang",
"age": 18,
})
实际上的效果是一样的。
2.6.2 通过结构体传数据
3. Gin框架获取参数
3.1 获取querystring参数
querystring
就是URL中?
后面的参数,比如下面的链接:
?
前面是请求的URL,后面是通过GET查询的信息参数。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/web", func(c *gin.Context) {
// 获取浏览器携带的querystring参数
name := c.Query("query")
c.JSON(http.StatusOK, gin.H{
"name": name,
})
})
r.Run()
}
3.1.1 Query
直接通过c.Query()
来查询,后面跟着的就是参数的名字。
3.1.2 DefaultQuery
这个就是添加了一个默认值,如果找不到这个名字的参数,就返回默认值:name := c.DefaultQuery("query","hello")
如果搜索的时候?
后面没有关键词query
那么name最后得到的参数就是hello
。
3.1.3 GetQuery
name,ok := c.GetQuery("query")
这个就是能顺便带个判断是否有关键词query
。没有ok就是false呗,有就是true,和上面也没啥特别的。
3.1.4 多个参数
其实和取一个也没啥差别,就是在访问的时候记得用&
分隔开就好了。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/web", func(c *gin.Context) {
// 获取浏览器携带的querystring参数
name := c.Query("query")
age := c.Query("age")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age" : age,
})
})
r.Run()
}
3.2 获取form参数
请求的数据通过form表单来提交。
3.2.1 同一个URL不同返回页面
写了一个html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post" novalidate autocomplete="off">
<div>
<label for="username">username:</label>
<input type="text" name="username" id="username">
</div>
<div>
<label for="password">username:</label>
<input type="text" name="password" id="password">
</div>
<div>
<input type="submit" value="登录">
</div>
</form>
</body>
</html>
点击submit之后会向/login
发送post请求。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 一定要记得解析html!!否则打不开
r.LoadHTMLFiles("./login.html")
r.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", nil)
})
r.Run()
}
但是我这里只写了获得GET请求的处理方法,没有写POST请求的处理方法。
当你填写完表单之后点击提交,就会POST请求/login
,无法响应请求导致了404,这也就是为什么同一个URL会有不同的结果,关键在于请求的方式不同。
一次请求对应一次响应!一次请求对应一次响应!一次请求对应一次响应!
3.2.2 调用form参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.LoadHTMLFiles("./login.html", "./index.html")
r.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", nil)
})
r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
c.HTML(http.StatusOK, "index.html", gin.H{
"name": username,
"password": password,
})
})
r.Run()
}
可以看到通过c.PostForm()
来获取的元素内的信息,()
里面的是元素的name
标签而不是id
标签。
下面是post请求后的网页。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>INDEX</title>
</head>
<body>
<h1>Hello, {{ .name }}</h1>
<p>你的密码是{{.password}}</p>
</body>
</html>
3.2.3 DefaultPostForm
带默认值的……类似于之前说过的。但是大概率是没填(默认“”)而不是不存在。
username := c.DefaultPostForm("username","somebody")
同理GetPostForm也是如此,多了一个判断存不存在的。
3.3 获取path参数
path参数也可以称为URI参数
统一资源标志符(英語:Uniform Resource Identifier,縮寫:URI)
请求的参数通过URL路径传递,例如user/search/火焰车/大炮
。
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/:name/:age", func(c *gin.Context) {
// 获取path参数
name := c.Param("name")
age := c.Param("age")
c.JSON(200, gin.H{
"name": name,
"age": age,
})
})
r.Run()
}
在GET
标记的地方用/:aaa/:bbb
来表示要获取参数的一个标识,之后通过c.Param()
来获取参数值。
3.3.1 解决冲突
多用于博客的一些信息汇总的页面上,如下面的代码:
r.GET("/blog/:year/:month", func(c *gin.Context) {
year := c.Param("year")
month := c.Param("month")
c.JSON(200, gin.H{
"year": year,
"month": month,
})
})
但是!但是!但是!注意一个问题,如果同时还存在一个GET(/:name/:age)
就会产生问题,他会同样把/blog/:year/:month
给匹配了。
所以,在设置这样的匹配时,如果有多个,需要前面有唯一的标识来区分,比如把/:name/:age
改成/userGET/:name/:age
就可以解决冲突了。
3.4 参数绑定
如果每次按照以下方式来调用参数会,在参数量很大的时候会显得很麻烦:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type UserInfo struct {
username string
password string
}
func main() {
r := gin.Default()
r.GET("user", func(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
u := UserInfo{
username: username,
password: password,
}
fmt.Printf("%#v\n", u)
c.JSON(200, gin.H{
"message": "ok",
})
})
r.Run()
}
3.4.1 如何绑定
关键的代码是err := c.ShouldBind(u)
,这里绑定的是一个地址,因为结构体是值传递,因此我在声明结构体的时候直接声明的是一个指针u := new(UserInfo)
(通过new获得的是指针),不然的话需要加&
来取地址。
第二点,结构体里面的成员首字母大写否则无法访问到。
第三点,因为绑定的时候肯定不会对应,所以说要添加反射,也就是在成员后加tag标签
,如form:"password"
。可以通过这个标签来找到该成员。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type UserInfo struct {
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
}
func main() {
r := gin.Default()
r.GET("user", func(c *gin.Context) {
u := new(UserInfo)
err := c.ShouldBind(u) // 传入的是地址
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"username": u.Username,
"password": u.Password,
})
}
})
r.Run()
}
3.4.2 测试一下POST请求
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type UserInfo struct {
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
}
func main() {
r := gin.Default()
r.POST("/form", func(c *gin.Context) {
u := new(UserInfo)
err := c.ShouldBind(u) // 传入的是地址
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"username": u.Username,
"password": u.Password,
})
}
})
r.Run()
}
4. Gin框架文件上传
4.1 单个文件上传
还是,遇到问题就先写注释来理清思路,前端的内容不多说,术业有专攻大体了解一下就好了。当我们收到了一份上传文件的请求,首先我们要从请求中获取文件(二进制之类的数据),之后再将数据保存到本地(即服务器端)。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.LoadHTMLFiles("./index.html")
r.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", nil)
})
r.POST("upload", func(c *gin.Context) {
// 从请求中读取获得的文件
f, err := c.FormFile("f1")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err,
})
} else {
// 将获取的文件保存在本地(服务器端)
dst := fmt.Sprintf("./%s", f.Filename)
//dst := path.Join("./",f.Filename)
_ = c.SaveUploadedFile(f, dst)
}
c.JSON(200, gin.H{
"status": "ok",
})
})
r.Run()
}
其中index.html如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f1">
<input type="submit" value="上传">
</form>
</body>
</html>
就是一个很简单的文件上传表单,请求方式是POST,enctype
一定要设置为multipart/form-data
以上传图片、音频之类的文件。
注意以下几点:
- 不要忘记解析html文件
r.LoadHTMLFiles("./index.html")
。 - 从请求中获取文件的方法就是
f, err := c.FormFile("f1")
dst := fmt.Sprintf("./%s", f.Filename)
//dst := path.Join("./",f.Filename)
_ = c.SaveUploadedFile(f, dst)
两种方式都可以组合成文件的名称(用来存放接收到的文件的地址)。第一种方式的Sprintf
是将后面格式的内容做为string类型的返回值给dst。
4.2 多个文件上传
在前端html界面上也需要修改一点,在上传文件的input
后面要加上multiple="multiple"
。
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f1" multiple="multiple">
<input type="submit" value="上传">
</form>
之后就是最主要的代码了:
package main
import (
"github.com/gin-gonic/gin"
"path"
)
func main() {
r := gin.Default()
r.LoadHTMLFiles("./index.html")
r.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", nil)
})
// 默认为 32MiB
// r.MaxMultipartMemory = 8 << 20 // 8MiB
r.POST("upload", func(c *gin.Context) {
// 从请求中读取获得的文件
form, _ := c.MultipartForm()
files := form.File["f1"]
for _, file := range files {
dst := path.Join("./" + file.Filename)
c.SaveUploadedFile(file, dst)
}
c.JSON(200, gin.H{
"status": "ok",
})
})
r.Run()
}
其实就是从上传单个文件的一个form变成了很多个form,因此先声明了一个c.MultipartForm
,之后再靠文件的name="f1"
找到每一个文件form.File["f1"]
。之后就跟之前一样保存文件就可以了。
5. Gin框架请求重定向
5.1 HTTP重定向
很简单,只使用一个方法即可func (c *Context) Redirect(code int, location string)
Redirect + 状态码 + 重定向的地址
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("index", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.bilibili.com")
})
r.Run()
}
5.2 路由重定向
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/a", func(c *gin.Context) {
// 跳转到 /b 对应的路由处理函数
c.Request.URL.Path = "/b"
r.HandleContext(c)
})
r.GET("/b", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "b",
})
})
r.Run()
}
此时访问/a
,会出现{"message":"b"}
,但是网页仍然是/a
,请求是先运行的/b
再运行/a
。
通过修改c.Request.URL.Path = "/b"
然后再处理请求r.HandleContext(c)
。
常见的就是访问网站未登录点击某些页面会跳转到登录页面,登陆之后会跳转回刚才访问的界面。
6. Gin框架路由和路由组
6.1 普通路由
就是我们一直用的那些方法,常见的:
r.GET("/home",func(c *gin.Context{...}))
r.POST("/home",func(c *gin.Context{...}))
r.PUT("/home",func(c *gin.Context{...}))
r.DELETE("/home",func(c *gin.Context{...}))
同时还有一个可以匹配所有方法的Any
方法:
r.Any("/home",func(c *gin.Context{...}))
里面包含了常用的方法。
此外,为没有配置处理函数的路由添加处理程序,默认情况下返回404代码,下面的代码为没有匹配到路由的请求都返回404.html
界面。
r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusOK,"404.html",nil)
})
别忘了要解析html。
6.2 路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。
习惯性{}
包裹同路组的路由,只是为了增加可读性,实际上是否包裹对功能没有影响。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
videoGroup := r.Group("/video")
{
videoGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/video/index",
})
})
videoGroup.GET("/xx", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/video/index",
})
})
videoGroup.GET("/oo", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/video/index",
})
})
}
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/shop/index",
})
})
}
r.Run()
}
7. Gin框架中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
7.1 定义中间件
如果每次在路由时添加识别、认证或者其他功能会导致代码冗余,那么就可以在经过路由前就进行特定的操作,因为夹在中间,所以也被称为中间件。
写一个计时的中间件:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
fmt.Println("index in ...")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
fmt.Println("index out ...")
}
// 定义一个中间件m1
func m1(c *gin.Context) {
// 计时
fmt.Println("m1 in ...")
start := time.Now()
c.Next() // 调用后续的处理函数(代码中的indexHandler)
// c.Abort() // 阻止调用后续的函数
cost := time.Since(start)
fmt.Printf("cost : %v\n", cost)
fmt.Println("m1 out ...")
}
func main() {
r := gin.Default()
r.GET("/home", m1, indexHandler)
r.GET("/shop", m1, func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{
"msg": "shop",
})
})
r.Run()
}
/*
m1 in ...
index in ...
index out ...
程序运行耗时: 0s
m1 out ...
*/
看一下标准库func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc)
实际上这个GET
方法是个变长的方法(可以放多个HandlerFunc)。
他是按照顺序来执行的,那么我在真正意义上的/index
(indexHandler)出现之前先运行另一个Handler
Func就是一个中间件(m1),并且因为不再使用匿名函数来定义HandlerFunc,可以很多个路由都使用这个中间件,实现了代码的复用。
7.2 全局注册中间件函数
我所有的网页都要用的话就可以搞一个全局注册,这样就不需要每次都再额外加上这个HandlerFunc的标识了(极致的简约)。
就提前加一句r.Use()
就可以了,参数是HandlerFunc类型。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
fmt.Println("index in ...")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
fmt.Println("index out ...")
}
// 定义一个中间件m1
func m1(c *gin.Context) {
// 计时
fmt.Println("m1 in ...")
start := time.Now()
c.Next() // 调用后续的处理函数(代码中的indexHandler)
// c.Abort() // 阻止调用后续的函数
cost := time.Since(start)
fmt.Printf("cost : %v\n", cost)
fmt.Println("m1 out ...")
}
func main() {
r := gin.Default()
r.Use(m1) // 全局注册中间件函数m1
r.GET("/home", indexHandler)
r.GET("/shop", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.Run()
}
这里的(上一个代码也有)c.Next
指的是调用后续的HandlerFunc()从输出顺序上来看就容易知道,先开始运行m1,然后运行index,然后index结束,最后m1结束。
如果不使用c.Next
就会顺序执行,即运行完m1之后再运行index,在这里因为想要给index计时所以要用c.Next
。
7.3 多个中间件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
fmt.Println("index in ...")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
fmt.Println("index out ...")
}
// 定义一个中间件m1
func m1(c *gin.Context) {
// 计时
fmt.Println("m1 in ...")
start := time.Now()
c.Next() // 调用后续的处理函数(代码中的indexHandler)
cost := time.Since(start)
fmt.Printf("cost : %v\n", cost)
fmt.Println("m1 out ...")
}
func m2(c *gin.Context) {
fmt.Println("m2 in ...")
c.Next()
fmt.Println("m2 out ...")
}
func main() {
r := gin.Default()
r.Use(m1, m2) // 全局注册中间件函数m1
r.GET("/home", indexHandler)
r.GET("/shop", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.Run()
}
/*
m1 in ...
m2 in ...
index in ...
index out ...
m2 out ...
cost : 0s
m1 out ...
*/
上面就属于有点嵌套的意思了。
想想一个登陆状态查询的中间件如何来写:
func authMiddleware(c *gin.Context) {
// 如果登陆c.Next,如果没有登陆c.Abort.
}
实际运用中可能要把这些中间件做成闭包的形式:
func authMiddleware() gin.HandlerFunc{
// 连接数据库……
// 其他的准备工作……
return func(c *gin.Context) {
// 如果登陆c.Next,如果没有登陆c.Abort.
}
}
7.4 路由组注册中间件
两种方法,效果是相同的。
shopGroup := r.Group("/shop",m1)
{
shopGroup.GET("XX",func(c *gin.Context){...})
shopGroup.GET("oo",func(c *gin.Context){...})
...
}
或者
shopGroup := r.Group("/shop")
shopGroup.Use(m1)
{
shopGroup.GET("XX",func(c *gin.Context){...})
shopGroup.GET("oo",func(c *gin.Context){...})
...
}
7.5 跨中间件取参数
使用c.Set()
来设置,之后用的时候通过c.GET
或者c.MustGet
来获取(后者没有error)。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
fmt.Println("index in ...")
name, _ := c.Get("name")
c.JSON(http.StatusOK, gin.H{
"msg": name,
})
fmt.Println("index out ...")
}
// 定义一个中间件m1
func m1(c *gin.Context) {
fmt.Println("m1 in ...")
c.Next() // 调用后续的处理函数(代码中的indexHandler)
fmt.Println("m1 out ...")
}
// 定义一个中间件m2
func m2(c *gin.Context) {
fmt.Println("m2 in ...")
c.Set("name", "Firecar")
c.Next()
fmt.Println("m2 out ...")
}
func main() {
r := gin.Default()
r.Use(m1, m2)
r.GET("/home", indexHandler)
r.Run()
}
7.6 注意事项
7.6.1 gin默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.Defaultwriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。
7.6.2 gin中间件中使用goroutine
当在中间件或 handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context
),必须使用其只读副本(c.copy()
)。