思考并回答以下问题:
实验介绍
本次实验重点讲解Gin框架中的核心知识,控制器(Controller),或处理器(Handler),帮助学员们深刻理解如何在Gin框架中编写GET、POST等方法的控制器函数,以及请求中的数据绑定(Binding)、如何处理HTTP响应。
知识点
- 上下文
- 请求
- 表单
- 响应
控制器
咱们首先回顾一下,在前面实验的第一个Gin应用中,我们通过一个极简的Gin应用简单提及了控制器(Controller)。它的参数包含路由、处理函数。类似下面这样。1
2
3app.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!")
})
上面是个非常简单的HTTP GET请求的Hello World控制器定义,当请求时会返回Hello World给客户端。
我们不会在这里讲解第一个参数,URL路径或路由(Router),这个在后面的实验中会详细介绍。而接下来,我们会稍微重点讲解第二个参数,处理函数(Handler Function),以及其包含的唯一参数c *gin.Context
,上下文(Context)。
这里其实还有一个重要概念,也就是路由方法(Routing Method),在这里是以app.GET
的方式被调用。它对应的是定义一个接收HTTP GET请求的路由。那么其他HTTP请求方法,例如POST、PUT、DELETE呢?没错,它们分别就是直接调用对应的大写形式的方法名,即app.POST
、app.PUT
、app.DELETE
。对于一些特殊的HTTP请求方法,例如HEAD、PATCH、OPTIONS等,也同样如此。我们将这些方法都统称为路由方法,它们在Gin框架中对应的接口是IRoutes,定义如下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21type IRoutes interface {
// 定义中间件
Use(...HandlerFunc) IRoutes
// 路由方法
Handle(string, string, ...HandlerFunc) IRoutes
Any(string, ...HandlerFunc) IRoutes
GET(string, ...HandlerFunc) IRoutes
POST(string, ...HandlerFunc) IRoutes
DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) IRoutes
// 静态文件
StaticFile(string, string) IRoutes
StaticFileFS(string, string, http.FileSystem) IRoutes
Static(string, string) IRoutes
StaticFS(string, http.FileSystem) IRoutes
}
而app对应的gin.Engine
类,其实最终继承了这个接口,因此可以调用这些方法。其他的关于中间件、静态文件的方法我们目前暂时不用关心,后面会详细讲解。
我们可以在路由方法对应的接口IRoutes中发现有另一个类型HandlerFunc,其实这就是处理函数对应的接口。它的定义如下。1
type HandlerFunc func(*Context)
这也就是处理函数的基本形式,只带唯一参数*gin.Context
,也就是上下文。
好了,理清楚关于控制器及其相关的概念之后,我们需要重点讲解Gin框架中的重要概念,上下文。
上下文
上下文在Gin框架中是非常重要的概念,因为Web框架中大部分概念都与它相关,包括HTTP请求、HTTP响应、表单绑定、中间件等。我们会看到,上下文在处理函数中是核心般的存在:要解析请求,得用上下文;要返回响应,得用上下文;要验证用户令牌,得用上下文。因此,了解上下文的基本原理以及如何使用,对我们学习Gin框架是很有必要的。
上下文主要是通过其中包含的公有方法(大写开头的方法)来进行操作的,其方法有几十个,很多。其方法总结下来有几大类,如下表。
其中,我们目前主要需要了解请求和响应相关的方法类别,即前4类。对所有方法感兴趣的学员可以参考上下文官方文档,以加深对该概念的掌握。
现在,为了加深对上下文概念的理解,我们将在前面实验的的代码基础上添加一部分代码。
同样,打开文件main.go,在其基础上添加一个新的控制器,代码如下。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
43package main
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建 Gin 引擎
app := gin.New()
// 定义控制器
app.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!")
})
app.GET("/:id", func(c *gin.Context) {
// 请求路径参数 id
id := c.Param("id")
// 请求 URL 参数 query
query := c.Query("query")
// 如果参数为空,返回错误
if query == "" {
c.AbortWithError(http.StatusBadRequest, errors.New("query is empty"))
return
}
// 响应 JSON 数据
res := map[string]interface{}{
"id": id,
"query": query,
}
// 返回 200 响应
c.AbortWithStatusJSON(http.StatusOK, res)
})
// 运行 Gin 引擎
app.Run(":8080")
}
除了之前创建的Hello World控制器以外,我们在下面添加了一个新的控制器,其中:id
表示URL路径动态参数,后面讲路由的时候会详细讲解。
输入完毕后,咱们打开终端,运行go run main.go
,可以看到日志如下。
然后,我们在URL后面添加/hello?query=world
并敲回车,可以得到如下符合预期的结果。
接下来,咱们试试不带URL参数query的情况。只在URL后面设置为/hello
,会返回如下400报错页面。这也是意料之中的。
现在,咱们已经对Gin框架的上下文有一定了解,咱们可以进一步探索如何操作HTTP请求了。
请求
URL参数
在上一小节介绍上下文的内容中,我们都涉及了请求这个概念。现在我们将稍微详细讲解一下。
通常来说,很多HTTP请求(特别是GET)是会带着参数的,例如http://example.com?query=hello
中的query=hello就是URL参数。就像前面展示的一样,我们可以通过c.Query
来获取对应的URL参数,例如query := c.Query("query")
,这是一个string类型。
当然,有些时候我们的URL参数不止是一两个,甚至可能作为数组或字典出现,这时候,就需要用到另一个Query开头的方法,QueryArray或QueryMap。
总结一下,对于URL参数,我们获取它们的方法如下。1
2
3
4
5
6
7
8// 获取 URL 参数(字符串)
func (c *Context) Query(key string) (value string)
// 获取 URL 参数(数组)
func (c *Context) QueryArray(key string) (values []string)
// 获取 URL 参数(字典)
func (c *Context) QueryMap(key string) (dicts map[string]string)
当然,对于开发过Gin应用的Go开发者来说,咱们还有更有效的获取URL参数的方式,也就是下面我们会介绍的数据绑定。
数据绑定
请求数据绑定(Binding)在Web框架中是非常常见的功能,对于Gin框架也不例外。Gin框架中关于表单绑定的主要关键词是Bind。上下文方法中,带有Bind的方法都是跟表单绑定相关的。
其中,又有两个关键词来表示绑定的,Must(严格绑定)和Should(按需绑定)。其区别在于:严格绑定会在绑定错误后直接返回400错误;而按需绑定则不会,只会将绑定的字段设置为空。以Bind开头的绑定方法都是严格绑定。
下面将常用的数据绑定方法列出来。
BindHeader | 严格绑定Header |
BindJSON | 严格绑定JSON数据 |
BindQuery | 严格绑定URL参数 |
ShouldBindHeader | 按需绑定Header |
ShouldBindJSON | 按需绑定JSON数据 |
ShouldBindQuery | 按需绑定URL参数 |
下面我们做一个实战练习,体会一下关于Gin框架请求的知识。
在项目的main.go中添加另一个控制器,如下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16app.GET("/query", func(c *gin.Context) {
// 数组参数
queryArray := c.QueryArray("array")
// 字典参数
queryMap := c.QueryMap("map")
// 响应 JSON 数据
res := map[string]interface{}{
"array": queryArray,
"map": queryMap,
}
// 返回 200 响应
c.AbortWithStatusJSON(http.StatusOK, res)
})
然后,在终端中运行go run main.go
。成功启动后,在URL后面输入/query?array=1&array=2&map=a&map[a]=1&map[b]=2
。可以看到如下响应数据。
可以看到,数组和字典形式的URL参数都获取到了。
接下来,我们要开始数据绑定了,这里我们将练习如何进行URL参数绑定以及POST请求的JSON绑定。
同样,在main.go中添加两个控制器,代码如下。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
30app.GET("/query-binding", func(c *gin.Context) {
// URL 参数
var bindingData struct {
Id int `form:"id" json:"id"`
Key string `form:"key" json:"key"`
}
// 绑定
if err := c.Bind(&bindingData); err != nil {
return
}
// 返回 200 响应
c.AbortWithStatusJSON(http.StatusOK, bindingData)
})
app.POST("/body-binding", func(c *gin.Context) {
// URL 参数
var bindingData struct {
Id int `json:"id"` // 没有form
Key string `json:"key"`
}
// 绑定
if err := c.Bind(&bindingData); err != nil {
return
}
// 返回 200 响应
c.AbortWithStatusJSON(http.StatusOK, bindingData)
})
打开终端,运行go run main.go
。成功启动后,在URL后面输入/query-binding?id=1&key=alpha
。可以看到如下响应数据。
绑定成功!
接下来,我们需要进行POST请求。
在URL中输入http://localhost:8080/body-binding
。
在Body标签中输入如下内容:1
2
3
4{
"id": 1,
"key": "alpha"
}
执行完毕后,会有相应的HTTP响应数据,如下图。
跟预期的结果一致,太棒了!
响应
概述
关于响应(Response)部分,我们需要了解的并不多,但它也非常重要,因为响应决定着最后用户或前端看到的东西。这里我们稍微讲解一下。
对于一个Web框架来说,会有很多种响应形式,可以是HTML返回给浏览器,也可以是JSON数据返回给前端(就像刚才展示的一样),或者是一个静态的文件(例如文件下载)等等。Gin框架也支持所有这些响应形式。我们在课程中将会讲解一些比较重要的响应形式。
JSON
对于RESTful API来说,JSON格式就是其响应形式。Gin中代码很简单,可以是单独的c.JSON
或者在最后调用AbortWithStatusJSON
。这些在前面的部分都介绍过,就不详细展开了。
HTML
HTML形式的响应主要是通过加载HTML模版,然后调用c.HTML
方法来完成前端页面渲染。这个主要是用于传统的MVC架构,由后端直接渲染模版成HTML传给浏览器将页面展现给用户。我们在后面有专门的实验来详细讲解Gin框架中的模版渲染。
实验总结
本次实验是整个Gin框架基础章节的第一个实验,也是Gin框架的重点,在后面的章节中会反复用到。我们通过实验讲解了控制器的基础概念,以及上下文的定义和如何使用,另外还介绍了请求和响应的相关要点。通过本次实验,同学们应该可以开始做很多事情了,比如构建一个可以传参的API,或者渲染一个HTML页面。接下来,我们将介绍Gin框架中的路由模块。