抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

接收请求

接收请求

作为一个服务器,最基本的任务就是接收请求,然后是返回结果。

客户端发来的请求可以有多种,如 GET、POST、PUT、DELETE。
最基本的就是 GET、POST 两种,不过这里还是建议遵循 RESTful 规范。

基本处理:Handle()

gin 接收请求有一种最基本的方法,使用 Handle() 方法。

其方法签名如下:

1
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes;

其接受的第一个参数为 HTTP 请求方式,第二个为路由路径,第三个为该请求对应的处理函数。

最基本的栗子,接收一个 GET 请求:

1
2
3
4
5
6
7
8
9
10
11
func main() {
app := gin.Default()

app.Handle("GET", "/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "Hello!",
})
})

app.Run(":9090")
}

第4行调用了 Handle(),请求方式为 GET 方法,路由路径为 /hello,处理方法是返回一串JSON数据。

浏览器访问 http://localhost:9090/hello 可得到下面的结果

接收 POST、PUT 等方式的请求**:

通过 Handle() 第一个参数我们可以接收不同方式的请求,例如 POST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {

r := gin.Default()
// http://127.0.0.1:9090/login
r.Handle("POST", "/login", func(c *gin.Context) { // 处理 POST 请求
user := c.PostForm("user") // 获取表单参数
pass := c.PostForm("pass") // 获取表单参数
// 处理业务逻辑
if user == "Boii" && pass == "123" {
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "登录成功",
})
return
}

c.YAML(http.StatusOK, gin.H{
"code": 4001,
"msg": "登录失败",
})
})

r.Run(":9090")
}

通过 Postman 可以设定表单参数然后发起请求,我们用gin写的代码就可以接收到一个 POST 请求,并进行处理。

可以看到, Postman 中设置了两个参数 userpass,向 http://localhost:9090/login 发清请求。

关于获取参数后面会详解。

快捷方式:gin.GET()、gin.POST()…

像 GET、POST、PUT、DELETE 等这些 HTTP方式的处理函数会很常用,所以 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
25
26
27
28
29
30
31
32
33
34
// POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPost, relativePath, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodDelete, relativePath, handlers)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPatch, relativePath, handlers)
}

// PUT is a shortcut for router.Handle("PUT", path, handle).
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPut, relativePath, handlers)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodOptions, relativePath, handlers)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodHead, relativePath, handlers)
}

通过源码可以看出,gin 很贴心的做了一层封装,提供了对应请求方式的 shortcut,使开发者更方便的调用。
参数上只需要传递 路由路径处理函数 即可,这些“快捷方式”会帮我们调用 handle()

eg:

1
2
3
4
5
6
7
8
func main() {
r := gin.Default()
// http://127.0.0.1:9090/hello
r.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": "Hello"})
})
r.Run(":9090")
}
1
2
3
4
5
6
7
8
func main() {
r := gin.Default()
// http://127.0.0.1:9090/login
r.POST("/login", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": "Hello"})
})
r.Run(":9090")
}

404 页面

1
2
3
4
5
6
7
8
9
func main() {
r := gin.Default()

r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusNotFound, "views/404.html", nil)
})

r.Run(":9090")
}

不需要指定路径,只要用户访问不存在的路径,就会执行此路由。

处理请求参数

获取 GET 请求的参数

GET 请求的参数会显式的携带在URL中。

可以通过 gin.Context.Query()gin.Context.DefaultQuery() 两种方法获取。

他们的区别是 DefaultQuery() 需要填入默认值。

1
2
func (c *Context) Query(key string) string;
func (c *Context) DefaultQuery(key, defaultValue string) string;

eg:

1
2
3
4
5
6
7
8
9
10
11
func main() {
r := gin.Default()

r.GET("/hello", func(c *gin.Context) {
name := c.Query("name")
age := c.DefaultQuery("age")
c.JSON(200, gin.H{"msg": "Hello " + name + ", you're " + age})
})

r.Run(":9090")
}

age 给了值

age 没给值

获取 POST 表单参数

POST 请求的参数不会显式的携带在URL中,而是包裹在请求体 Body 中,是通过表单提交的。

可以通过 gin.Context.Postform()gin.Context.DefaultPostform() 两种方法获取。

同样他们的区别仅在于一个需要填入默认值。

1
2
func (c *Context) PostForm(key string) string;
func (c *Context) DefaultPostForm(key, defaultValue string) string;

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
r := gin.Default()

// http://127.0.0.1:9090/login
r.POST("/login", func(c *gin.Context) {
user := c.DefaultPostForm("user", "admin")
pass := c.PostForm("pass")
if user == "Boii" && pass == "123" {
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "登录成功",
})
return
}
c.YAML(http.StatusOK, gin.H{
"code": 4001,
"msg": "登录失败",
})
})
r.Run(":9090")
}

处理其他格式的请求参数

除了URL中的参数、表单提交的参数,在客户端发起 HTTP 请求时还可以使用 JSON、XML、YAML 等格式。

获取这些格式的参数,可以通过 gin.Context.Bind() 来获取,

Bind() 有一系列的方法:

1
2
3
4
5
6
7
8
func (c *Context) Bind(obj interface{}) error;
func (c *Context) BindJSON(obj interface{}) error;
func (c *Context) BindXML(obj interface{}) error;
func (c *Context) BindQuery(obj interface{}) error;
func (c *Context) BindYAML(obj interface{}) error;
func (c *Context) BindHeader(obj interface{}) error;
func (c *Context) BindUri(obj interface{}) error;
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error;

显然,要解析请求中什么类型的数据就调用什么方法,参数 obj 需要我们传入一个结构体变量,用于装载解析后的数据。

结构体每个字段都需要有 tag,否则会解析失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type UserInfo struct {
User string
Pass string
}

var u UserInfo

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

r.POST("/login", func(c *gin.Context) {
c.BindJSON(&u)
c.JSON(200, gin.H{
"user": u.User,
"pass": u.Pass,
})
})

r.Run(":9090")
}

上面的例子可以解析 HTTP 请求中通过 JSON 携带的数据。

Bind()

如果想根据请求中的 content-type 属性来选择,可以使用 Bind() 方法,该方法会

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
// gin 源码
// context.go

// Bind checks the Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used:
// "application/json" --> JSON binding
// "application/xml" --> XML binding
// otherwise --> returns an error.
// 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.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
}

// gin 源码
// binding.go

// Default returns the appropriate Binding instance based on the HTTP method
// and the content type.
func Default(method, contentType string) Binding {
if method == http.MethodGet {
return Form
}

switch contentType {
case MIMEJSON:
return JSON
case MIMEXML, MIMEXML2:
return XML
case MIMEPROTOBUF:
return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack
case MIMEYAML:
return YAML
case MIMEMultipartPOSTForm:
return FormMultipart
default: // case MIMEPOSTForm:
return Form
}
}

可以看到,context 中的 Bind() 方法调用了 binding.Default() 方法;

binding.Default() 方法中,如果请求方式是 GET 则返回 Form,否则的话,根据请求中的 content-type 属性返回对应的格式。

接着会调用 gin.Context.MustBindWith(obj, b) -> gin.Context.ShouldBindWith(obj, b) -> b.Bind()

b.Bind() 这里的时候,会根据 b 的类型调用各自的 Bind() 方法。

例如 JSON 的话会调用 jsonBinding.Bind() 方法,然后在里面调用 decodeJSON() 方法。

一次解析多个参数

上面登录的栗子中,我们简简单单的获取了两个参数,所以可以用两次 Postform() 方法。

但是当参数多起来的时候,这种方式并不是很好。好在 gin 还提供另外的方法方便我们一次解析多个参数。

要一次解析多个参数,我们需要一个 结构体 来装载这些参数的值,然后将结构体传给 gin.Context.Bind()gin.Context.ShouldBind()gin.Context.ShouldBindQuery() 等方法。

1
2
func (c *Context) ShouldBind(obj interface{}) error;
func (c *Context) ShouldBindQuery(obj interface{}) error;

要注意的是,我们定义的这个结构体每个字段都需要有 tag,否则会解析失败。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
r := gin.Default()

type UserInfo struct {
User string `form:"user"`
Pass string `form:"pass"`
}
// http://127.0.0.1:9090/login
r.POST("/login", func(c *gin.Context) {
var u UserInfo
c.ShouldBind(&u)
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "登录成功",
"user": u.User,
"pass": u.Pass,
})
})
r.Run(":9090")
}


效果是一样的。

解析路由路径中的参数

gin.Context.Param()

路由路径有时候并不固定,而是根据实际情况变化的。

例如下面的栗子,不同的用户有不同的ID,要获取这个ID,需要用到 gin.Context.Param() 方法,填入冒号通配符后面的变量 id 即可。

在使用 PUT、DELETE 等请求方法的时候是修改、删除某条记录的目的,这需要在路径中指定一个关键值(如 id),这里就可以用像下面这样去解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
r := gin.Default()

// http://127.0.0.1:9090/user/10086
r.PUT("/user/:id", func(c *gin.Context) {
userId := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "ID is " + userId,
})
})
r.Run(":9090")
}

gin.Context.Params

除了Param(),还可以使用 Params.Get()Params.ByName() 去获取想要的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
r := gin.Default()

// http://127.0.0.1:9090/Boii/18/1234567
r.GET("/params/:name/:age/:tele", func(c *gin.Context) {
params := c.Params

name, _ := params.Get("name")
age := params.ByName("age")
tele := c.Param("tele")

c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
"tele": tele,
})
})

r.Run(":9090")
}

哔哔