gin框架学习入门
Gin
是一个用 Go 语言编写的 web 框架。它是一个类似于martini
但拥有更好性能的 API 框架, 由于使用了httprouter
,速度提高了近 40 倍。 如果你是性能和高效的追求者, 你会爱上Gin
。
# 1,Gin 框架介绍
Go 世界里最流行的 Web 框架,Github (opens new window)上有32K+
star。 基于httprouter (opens new window)开发的 Web 框架。 中文文档 (opens new window)齐全,简单易用的轻量级框架。
# 2,Gin 框架安装与使用
# 1,安装
下载并安装Gin
:
go get -u github.com/gin-gonic/gin
# 2,第一个 Gin 示例:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// GET:请求方式;/hello:请求的路径
// 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
r.GET("/hello", func(c *gin.Context) {
// c.JSON:返回JSON格式的数据
c.JSON(200, gin.H{
"message": "Hello world!",
})
})
// 启动HTTP服务,默认在0.0.0.0:8080启动服务
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello
就能看到一串 JSON 字符串。
# 3,RESTful API
REST 与技术无关,代表的是一种软件架构风格,REST 是 Representational State Transfer 的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
推荐阅读阮一峰 理解 RESTful 架构 (opens new window)
简单来说,REST 的含义就是客户端与 Web 服务器之间进行交互的时候,使用 HTTP 协议中的 4 个请求方法代表不同的动作。
GET
用来获取资源POST
用来新建资源PUT
用来更新资源DELETE
用来删除资源。
只要 API 程序遵循了 REST 风格,那就可以称其为 RESTful API。目前在前后端分离的架构中,前后端基本都是通过 RESTful API 来进行交互。
例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们 Web 服务端交互的方式和路径。按照经验我们通常会设计成如下模式:
请求方法 | URL | 含义 |
---|---|---|
GET | /book | 查询书籍信息 |
POST | /create_book | 创建书籍记录 |
POST | /update_book | 更新书籍信息 |
POST | /delete_book | 删除书籍信息 |
同样的需求我们按照 RESTful API 设计如下:
请求方法 | URL | 含义 |
---|---|---|
GET | /book | 查询书籍信息 |
POST | /book | 创建书籍记录 |
PUT | /book | 更新书籍信息 |
DELETE | /book | 删除书籍信息 |
Gin 框架支持开发 RESTful API 的开发。
func main() {
r := gin.Default()
r.GET("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "GET",
})
})
r.POST("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "POST",
})
})
r.PUT("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "PUT",
})
})
r.DELETE("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "DELETE",
})
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
开发 RESTful API 的时候我们通常使用Postman (opens new window)来作为客户端的测试工具。
# 4,gin 渲染
# 1,返回 json
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 方法一:通过map返回
//data := map[string]interface{}{
// "name": "eryajf",
// "age": 20,
//}
// gin提供了一个提前定义好了的map H,方便我们直接调用
data := gin.H{"name": "eryajf", "age": 20} // type H map[string]interface{}
r.GET("/json", func(c *gin.Context) {
c.JSON(http.StatusOK, data)
})
// 方法二:通过结构体 注意,结构体当中字段名称必须大写,因为下边c.JSON需要对结构体序列化,如果小写,则读不到数据
// 如果仍旧想返回小写的字段,可以通过tag来定义
type msg struct {
Name string `json:"name"`
Message string `json:"message"`
Age int `json:"age"`
}
data1 := msg{
Name: "二丫讲梵",
Message: "hello golang",
Age: 22,
}
r.GET("/otherjson", func(c *gin.Context) {
c.JSON(http.StatusOK, data1)
})
r.Run(":9090")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 5,获取参数
# 1,获取 querystring 参数
querystring
指的是 URL 中?
后面携带的参数,例如:/user/search?username=小王子&address=沙河
。 获取请求的 querystring 参数的方法如下:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// GET请求URL ?后面的是query string参数
// key=value格式,多个用 & 连接
r.GET("web", func(c *gin.Context) {
// 获取浏览器那边发请求携带的 query string 参数
//方式一 http://127.0.0.1:9090/web?query=eryajf&age=20
name := c.Query("query") // 通过 query获取请求中携带的query string
age := c.Query("age")
//方式二
//name := c.DefaultQuery("query", "somequery") //取不到就用指定的默认值
//方式三
//name, ok := c.GetQuery("query") //取不到query就返回false
//if !ok {
// name = "somequery"
//}
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.Run(":9090")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
此时请求 http://127.0.0.1:9090/web?query=eryajf&age=20
# 2,获取 form 表单
请求的数据通过 form 表单来提交,例如向/user/search
发送一个 POST 请求,获取请求数据的方式如下,此处通过两个 html 文件简单模拟一下登陆页面。
创建一个登陆页面:login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="/login" method="post">
<div>
<label for="username">username:</label>
<input type="text" name="username" id="username">
</div>
<div>
<label for="password">password:</label>
<input type="text" name="password" id="password">
</div>
<div>
<input type="submit" value="登陆">
</div>
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
创建一个 index.html 用于返回用户登陆成功之后的页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>index</title>
</head>
<body>
<h1>hello {{ .Name }}</h1>
<h1>hello {{ .Pass }}</h1>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
主程序内容如下:
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)
})
// /login post
r.POST("login", func(c *gin.Context) {
// 方法一
//username := c.PostForm("username")
//password := c.PostForm("password")
// 方法二
//username := c.DefaultPostForm("username", "somev")
//password := c.DefaultPostForm("password", "***")
// 方法三
username, ok := c.GetPostForm("username")
if !ok {
username = "somev"
}
password, ok := c.GetPostForm("password")
if !ok {
password = "***"
}
c.HTML(http.StatusOK, "index.html", gin.H{
"Name": username,
"Pass": password,
})
})
r.Run(":9090")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 3,获取 path 参数
请求的参数通过 URL 路径传递,例如:/user/search/小王子/沙河
。 获取请求 URL 路径中的参数的方式如下。
package main
// 获取请求的path (URI) 返回的都是字符串类型
// 注意URL的匹配不要冲突了
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/user/:name/:age", func(c *gin.Context) { // eg: curl 127.0.0.1:9090/user/eryajf/20
name := c.Param("name")
age := c.Param("age") //string类型
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
// 模拟查询博客某年某月的文章列表
r.GET("blog/:year/:month", func(c *gin.Context) { // eg: curl 127.0.0.1:9090/blog/2019/10
year := c.Param("year")
month := c.Param("month")
c.JSON(http.StatusOK, gin.H{
"year": year,
"month": month,
})
})
r.Run(":9090")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 4,参数绑定
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type
识别请求数据类型并利用反射机制自动提取请求中QueryString
、form表单
、JSON
、XML
等参数到结构体中。 下面的示例代码演示了.ShouldBind()
强大的功能,它能够基于请求自动提取JSON
、form表单
和QueryString
类型的数据,并把值绑定到指定的结构体对象。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type userInfo struct {
Username string `form:"username" json:"user"`
Password string `form:"password" json:"pwd"`
}
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,
}
c.JSON(http.StatusOK, gin.H{
"user": u.Username,
"pass": u.Password,
})
})
// 利用 绑定参数方式可以方便的获取到浏览器请求参数 querystring
r.GET("/bindquery", func(c *gin.Context) {
var u userInfo
if err := c.ShouldBind(&u); err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{
"user": u.Username,
"pass": u.Password,
})
}
})
// 利用 绑定参数方式可以方便的获取到浏览器表单参数 提交普通的参数
r.POST("/bindform", func(c *gin.Context) {
var u userInfo
if err := c.ShouldBind(&u); err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{
"user": u.Username,
"pass": u.Password,
})
}
})
// 利用 绑定参数方式可以方便的获取到浏览器表单参数 提交json内容
r.POST("/bindjson", func(c *gin.Context) {
var u userInfo
if err := c.ShouldBind(&u); err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{
"user": u.Username,
"pass": u.Password,
})
}
})
r.Run(":9090")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
其中发起 post 请求的时候,上边两种方法有所不同,请求 /bindform
,方法如下:
请求 /bindjson
,方法如下:
# 6,路由重定向
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("index", func(c *gin.Context) {
//c.JSON(http.StatusOK, gin.H{
// "msg": "ok",
//})
// 将 /index 请求跳转到 其他域名
c.Redirect(http.StatusMovedPermanently, "http://eryajf.net")
})
r.GET("/a", func(c *gin.Context) {
// 将 /a 跳转到 /b
c.Request.URL.Path = "/b" //把请求的URL修改
r.HandleContext(c) //继续后续的处理
})
r.GET("/b", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "b",
})
})
r.Run(":9090")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 7,路由组
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "GET",
})
})
r.POST("index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "POST",
})
})
r.PUT("index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "PUT",
})
})
r.DELETE("index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "DELETE",
})
})
// Any 可以接收任意方法
r.Any("user", func(c *gin.Context) {
switch c.Request.Method {
case "GET":
c.JSON(http.StatusOK, gin.H{"method": "GET"})
case http.MethodPost:
c.JSON(http.StatusOK, gin.H{"method": "POST"})
}
})
// NoRoute 可以接收任意未定义的路由请求
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"msg": "来到了荒漠"})//也可以指向固定的404.html页面
})
// 定义一个视频首页以及详情
//r.GET("vedio/indxe", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{"msg": "/vedio/index"})
//})
//r.GET("vedio/xx", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{"msg": "/vedio/xx"})
//})
//r.GET("vedio/oo", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{"msg": "/vedio/oo"})
//}) 可以用如下路由组方式定义
vedioGroup := r.Group("vedio")
{ //通常用一个大括号作为代码块包起来
vedioGroup.GET("index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/vedio/index"})
})
vedioGroup.GET("xx", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/vedio/xx"})
})
vedioGroup.GET("oo", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/vedio/oo"})
})
}
r.Run(":9090")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
路由嵌套:
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
// 嵌套路由组
xx := shopGroup.Group("xx")
xx.GET("/oo", func(c *gin.Context) {...})
}
2
3
4
5
6
7
8
9
# 8,中间件
Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
# 1,定义中间件
Gin 中的中间件必须是一个gin.HandlerFunc
类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //阻止调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost:%v\n", cost)
fmt.Println("m1 out...")
}
2
3
4
5
6
7
8
9
10
# 2,注册中间件
在 gin 框架中,我们可以为每个路由添加任意数量的中间件。
# 1,为某个路由单独注册
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("index...")
c.JSON(http.StatusOK, gin.H{
"msg": "index...",
})
}
func shopHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop...",
})
}
func userHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user...",
})
}
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //阻止调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost:%v\n", cost)
fmt.Println("m1 out...")
}
func main() {
r := gin.Default()
// GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", m1, indexHandler)
r.GET("/shop", m1, indexHandler)
r.GET("/user", indexHandler)
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
同一个中间件可以注册给指定的路由,不过如果想要把这个中间件授权给所有路由组,就需要用如下方式了。
# 2,为全局路由注册
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("index...")
c.JSON(http.StatusOK, gin.H{
"msg": "index...",
})
}
func shopHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop...",
})
}
func userHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user...",
})
}
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
//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("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
r.GET("/shop", shopHandler)
r.GET("/user", userHandler)
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 3,为路由组注册中间件
为路由组注册中间件有以下两种写法。
写法 1:
shopGroup := r.Group("/shop", m1)
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
2
3
4
5
写法 2:
shopGroup := r.Group("/shop")
shopGroup.Use(m1)
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
2
3
4
5
6
# 3,中间件执行顺序
# 1,c.Next()
c.Next()
:表示执行下一个函数
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("index...")
c.JSON(http.StatusOK, gin.H{
"msg": "index...",
})
}
func shopHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop...",
})
}
func userHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user...",
})
}
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
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) //全局注册中间件函数 m1
r.Use(m1, m2) //可以注册多个中间件,注意中间件执行的顺序
r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
r.GET("/shop", shopHandler)
r.GET("/user", userHandler)
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
如果请求 index,将打印:
m1 in...
m2 in...
index...
m2 out...
cost:232.264µs
m1 out...
2
3
4
5
6
执行图示如下:
具体函数流程,如下:
# 2,c.Abort()
c.Abort
:表示取消执行后边的函数
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("index...")
c.JSON(http.StatusOK, gin.H{
"msg": "index...",
})
}
func shopHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop...",
})
}
func userHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user...",
})
}
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
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()
c.Abort() //阻止调用后续的处理函数
fmt.Println("m2 out...")
}
func main() {
r := gin.Default()
//r.Use(m1) //全局注册中间件函数 m1
r.Use(m1, m2) //可以注册多个中间件,注意中间件执行的顺序
r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
r.GET("/shop", shopHandler)
r.GET("/user", userHandler)
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
执行如上代码,将会看到如下输出:
m1 in...
m2 in...
m2 out...
cost:232.264µs
m1 out...
2
3
4
5
进入到 m2 in 之后不再执行响应请求的函数,而是直接来到 m2 out。图示如下:
# 3,return
return
:随时从某个地方跳出
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("index...")
c.JSON(http.StatusOK, gin.H{
"msg": "index...",
})
}
func shopHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop...",
})
}
func userHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user...",
})
}
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
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()
c.Abort() //阻止调用后续的处理函数
return
fmt.Println("m2 out...")
}
func main() {
r := gin.Default()
//r.Use(m1) //全局注册中间件函数 m1
r.Use(m1, m2) //可以注册多个中间件,注意中间件执行的顺序
r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
r.GET("/shop", shopHandler)
r.GET("/user", userHandler)
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
如上代码执行结果如下:
m1 in...
m2 in...
cost:232.264µs
m1 out...
2
3
4
# 4,c.Set()和 c.Get()
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("index...")
name, ok := c.Get("name")
if !ok {
name = "匿名用户"
}
c.JSON(http.StatusOK, gin.H{
"msg": name,
})
}
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //阻止调用后续的处理函数
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()
c.Set("name", "eryajf")
fmt.Println("m2 out...")
}
func main() {
r := gin.Default()
r.Use(m1, m2) //可以注册多个中间件,注意中间件执行的顺序
r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
这里在 m2 这个中间件里边传递了一个 name 给后边的函数,然后 indexHandler 接收这个变量来使用。
# 4,登陆中间件实例
通常我们采用如下方式定义一个判断用户是否登陆的一个中间件:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("index...")
c.JSON(http.StatusOK, gin.H{
"msg": "index...",
})
}
func shopHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop...",
})
}
func userHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user...",
})
}
// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //阻止调用后续的处理函数
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 authMiddleware(doCheck bool) gin.HandlerFunc {
// 可以连接数据库
// 或者一些其他的准备工作
return func(c *gin.Context) {
if doCheck {
// 存放具体的逻辑
// 判断是否登陆
// if 是登陆用户
// c.Next()
// else
// c.Abort()
} else {
c.Next()
}
}
}
func main() {
r := gin.Default()
r.Use(m1, m2) //可以注册多个中间件,注意中间件执行的顺序
r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
r.GET("/shop", authMiddleware(true), shopHandler)
r.GET("/user", userHandler)
r.Run()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 3,中间件注意事项
# 2,gin 默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会 recover 任何panic
。如果有 panic 的话,会写入 500 响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
# 3,gin 中间件中使用 goroutine
当在中间件或handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy()
)。