Gin 基础

Hello World

1
2
# 安装
$ go get github.com/gin-gonic/gin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

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

func main() {
// 初始化引擎
engine := gin.Default()
// 注册一个路由和处理函数
engine.Any("/", WebRoot)
// 绑定端口,然后启动应用
engine.Run(":9205")
}

/**
* 根请求处理函数
* 所有本次请求相关的方法都在 context 中,完美
* 输出响应 hello, world
*/
func WebRoot(context *gin.Context) {
context.String(http.StatusOK, "hello, world")
}

路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 省略的代码 ...

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

router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)

// 默认绑定 :8080
router.Run()
}

// 省略的代码 ...

动态路由(参数路由)

1
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
// 省略的代码 ...

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

// 注册一个动态路由
// 可以匹配 /user/joy
// 不能匹配 /user 和 /user/
router.GET("/user/:name", func(c *gin.Context) {
// 使用 c.Param(key) 获取 url 参数
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})

// 注册一个高级的动态路由
// 该路由会匹配 /user/john/ 和 /user/john/send
// 如果没有任何路由匹配到 /user/john, 那么他就会重定向到 /user/john/,从而被该方法匹配到
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)
})

router.Run(":8080")
}

// 省略的代码 ...

路由组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 省略的代码 ...

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

// 定义一个组前缀
// /v1/login 就会匹配到这个组
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}

// 定义一个组前缀
// 不用花括号包起来也是可以的。上面那种只是看起来会统一一点。看你个人喜好
v2 := router.Group("/v2")
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)

router.Run(":8080")
}

// 省略的代码 ...

中间件

通过中间件的方式,验证 Auth 和身份鉴别,集中处理返回的数据等等。Gin 提供了 Middleware 的功能,并与路由紧紧相连。

单个路由中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 省略的代码 ...

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

// 注册一个路由,使用了 middleware1,middleware2 两个中间件
router.GET("/someGet", middleware1, middleware2, handler)

// 默认绑定 :8080
router.Run()
}

func handler(c *gin.Context) {
log.Println("exec handler")
}

// 省略的代码 ...

执行流程控制

用上面的实例代码,我们来看一下中间件是怎么执行的。

1
2
3
4
5
6
7
8
9
10
11
12
// 省略的代码 ...

func middleware1(c *gin.Context) {
log.Println("exec middleware1")

//你可以写一些逻辑代码

// 执行该中间件之后的逻辑
c.Next()
}

// 省略的代码 ...

可以看出,中间件的写法和路由的 Handler 几乎是一样的,只是多调用c.Next()

正是有个c.Next(),我们可以在中间件中控制调用逻辑的变化,看下面的 middleware2 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 省略的代码 ...

func middleware2(c *gin.Context) {
log.Println("arrive at middleware2")
// 执行该中间件之前,先跳到流程的下一个方法
c.Next()
// 流程中的其他逻辑已经执行完了
log.Println("exec middleware2")

//你可以写一些逻辑代码
}

// 省略的代码 ...

middleware2中,执行到 c.Next()时,Gin 会直接跳到流程的下一个方法中,等到这个方法执行完后,才会回来接着执行 middleware2 剩下的代码。

所以请求上面注册的路由 url /someGet ,请求先到达middleware1,然后到达 middleware2,但此时 middleware2调用了 c.Next(),所以 middleware2的代码并没有执行,而是跳到了 handler ,等 handler执行完成后,跳回到 middleware2,执行 middleware2剩下的代码。

所以我们可以在控制台上看到以下日志输出:

1
2
3
4
exec middleware1
arrive at middleware2
exec handler
exec middleware2

路由组使用中间件

路由组使用中间件和单个路由类似,只不过是要把中间件放到 Group 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 省略的代码 ...

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

// 定义一个组前缀, 并使用 middleware1 中间件
// 访问 /v2/login 就会执行 middleware1 函数
v2 := router.Group("/v2", middleware1)
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)

router.Run(":8080")
}

// 省略的代码 ...

参数

URL 查询参数

假定一个 url 为 /welcome?firstname=Jane&lastname=Doe,我们想获取参数 firstname 的内容,可以使用c.Query方法。该方法始终返回一个 string 类型的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 省略的代码 ...

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

// 注册路由和Handler
// url为 /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
// 获取参数内容
// 获取的所有参数内容的类型都是 string
// 如果不存在,使用第二个当做默认内容
firstname := c.DefaultQuery("firstname", "Guest")
// 获取参数内容,没有则返回空字符串
lastname := c.Query("lastname")

c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}

JSON Body 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 省略的代码 ...

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

router.POST("/post", func(c *gin.Context) {
// 获取原始字节
d, err := c.GetRawData()
if err!=nil {
log.Fatalln(err)
}
log.Println(string(d))
c.String(200, "ok")
})
router.Run(":8080")
}

表单参数(Multipart/Urlencoded Form)

典型的如 POST 提交的数据,无论是 multipart/form-data格式还是application/x-www-form-urlencoded格式,都可以使用 c.PostForm获取到参数。该方法始终返回一个 string 类型的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 省略的代码 ...

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

router.POST("/form_post", func(c *gin.Context) {
// 获取post过来的message内容
// 获取的所有参数内容的类型都是 string
message := c.PostForm("message")
// 如果不存在,使用第二个当做默认内容
nick := c.DefaultPostForm("nick", "anonymous")

c.JSON(200, gin.H{
"status": "posted",
"message": message,
"nick": nick,
})
})
router.Run(":8080")
}

上传文件

上传文件的大小有限制,通常是<32MB,你可以使用 router.MaxMultipartMemory更改它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 省略的代码 ...

func main() {
router := gin.Default()
// 设置文件上传大小 router.MaxMultipartMemory = 8 << 20 // 8 MiB
// 处理单一的文件上传
router.POST("/upload", func(c *gin.Context) {
// 拿到这个文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})

// 处理多个文件的上传
router.POST("/uploads", func(c *gin.Context) {
form, _ := c.MultipartForm()
// 拿到集合
files := form.File["upload[]"]
for _, file := range files {
log.Println(file.Filename)
}
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
})
router.Run(":8080")
}

数据绑定

Gin 提供了非常方便的数据绑定功能,可以将用户传来的参数自动跟我们定义的结构体绑定在一起。

绑定 JSON Body 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person struct {
Name string `json:"name"`
Address string `json:"address"`
}

func bindPOST(c *gin.Context) {
var person Person
c.BindJSON(&person)
log.Println(person.Name)
c.JSON(http.StatusOK, gin.H{
"name": person.Name,
})
}

绑定 Url 查询参数(Only Bind Query String)

使用 c.ShouldBindQuery方法,可以自动绑定 Url 查询参数到 struct.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// 定义一个 Person 结构体,用来绑定 url query
type Person struct {
Name string `form:"name"` // 使用成员变量标签定义对应的参数名
Address string `form:"address"`
}

func main() {
route := gin.Default()
route.Any("/testing", startPage)
route.Run(":8085")
}

func startPage(c *gin.Context) {
var person Person
// 将 url 查询参数和person绑定在一起
if c.ShouldBindQuery(&person) == nil {
log.Println("====== Only Bind By Query String ======")
log.Println(person.Name)
log.Println(person.Address)
}
c.String(200, "Success")
}

数据验证

Gin 的数据验证是和数据绑定结合在一起的。只需要在数据绑定的结构体成员变量的标签添加binding规则即可。详细的规则查阅 这里

来看官方给出的一段代码:

1
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
// 省略的代码 ...

// 定义的 Login 结构体
// 该 struct 可以绑定在 Form 和 JSON 中
// binding:"required" 意思是必要参数。如果未提供,Bind 会返回 error
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}

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

// POST 到这个路由一段 JSON, 如 ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
// 验证数据并绑定
if err := c.ShouldBindJSON(&json); err == nil {
if json.User == "manu" && json.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})

// POST 到这个路由一个 Form 表单 (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// 验证数据并绑定
if err := c.ShouldBind(&form); err == nil {
if form.User == "manu" && form.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})

router.Run(":8080")
}

输出响应

Web 应用的目标之一就是输出响应。Gin 为我们提供了多种常见格式的输出,包括 HTML, StringJSONXMLYAML

String

1
2
3
4
5
6
7
8
// 省略的代码 ...

func Handler(c *gin.Context) {
// 使用 String 方法即可
c.String(200, "Success")
}

// 省略的代码 ...

JSON、 XML、 YAML

Gin 输出这三种格式非常方便,直接使用对用方法并赋值一个结构体给它就行了。

你还可以使用gin.Hgin.H 是一个很巧妙的设计,你可以像javascript定义json一样,直接一层层写键值对,只需要在每一层加上 gin.H即可。看代码:

1
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
// 省略的代码 ...

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

// gin.H 本质是 map[string]interface{}
r.GET("/someJSON", func(c *gin.Context) {
// 会输出头格式为 application/json; charset=UTF-8 的 json 字符串
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})

r.GET("/moreJSON", func(c *gin.Context) {
// 直接使用结构体定义
var msg struct {
Name string `json:"user"`
Message string
Number int
}
msg.Name = "Lena"
msg.Message = "hey"
msg.Number = 123
// 会输出 {"user": "Lena", "Message": "hey", "Number": 123}
c.JSON(http.StatusOK, msg)
})

r.GET("/someXML", func(c *gin.Context) {
// 会输出头格式为 text/xml; charset=UTF-8 的 xml 字符串
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})

r.GET("/someYAML", func(c *gin.Context) {
// 会输出头格式为 text/yaml; charset=UTF-8 的 yaml 字符串
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})

r.Run(":8080")
}

// 省略的代码 ...

转载自:deng-dev

------ 本文结束 ------
0%