大家好,我是渔夫子。
在gin框架中,我们知道用bind函数(或bindXXX函数)能够将请求体中的参数绑定到对应的结构体上。同时,你也会发现在gin中有很多bind或bindXXX函数,比如ShouldBind、ShouldBindQuery、ShouldBindHeader、ShouldBindJSON等等。那么,他们之间有什么不同呢?本文带你深入了解这些bind函数的使用。
一、bind的基本作用
在gin框架或其他所有web框架中,bind或bindXXX函数(后文中我们统一都叫bind函数)的作用就是将请求体中的参数值绑定到对应的结构体上,以方便后续业务逻辑的处理。
接下来我们看一个简单的使用例子,该实例是期望客户端发送一个JSON格式的请求体,然后通过JSON标签绑定到LoginRequest结构体上。如下:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
g := gin.New()
g.POST("/login", func(ctx *gin.Context) {
r := &LoginRequest{}
ctx.ShouldBind(r)
fmt.Printf("login-request:%+v\n", r)
})
g.Run(":9090")
}
运行上述示例代码,并在postman中或使用curl给http://localhost:9090/login发送请求,请求体是:
curl -X POST -H "Content-Type:application/json" http://localhost:9090/login -d '{"username": "yufuzi", "password": "123456}'
在代码中,我们通过ctx.ShouldBind(r)函数,将请求体的内容绑定到了LoginRequest类型的r变量上。
我们通过ShouldBind函数的源代码可以梳理到绑定函数的一般流程:
1、调用ctx.ShouldBind函数
2、ShouldBind函数根据请求的方法(POST还是GET)以及Content-Type获取具体的bind实例。如是POST请求且请求体是JSON格式,那么就返回jsonBinding结构体实例。
3、调用ctx.ShouldBindWith函数
4、ShouldBindWith函数调用具体的绑定实例的Bind方法。例如jsonBinding.Bind函数
5、将request中的Body(或Form、Header、Query)中的请求值绑定到对应的结构体上。
其大致流程如下:
二、请求数据来源
由第一节我们了解到,数据来源于客户端发来的请求。那么,在一次http请求中,都可以通过哪里来携带参数呢?根据http协议的标准,可以通过url中的查询参数,请求头、请求体等途径将参数传递给服务端。
在请求体中参数可以是不同的格式,比如JSON格式、XML格式、YAML格式、TOML格式、Protobuf message等。也可以是form表单的形式。
有了来源,接下来看看各个bind函数是如何把不同数据源的数据绑定到结构体上的。
三、bind及其bindXXX函数
为了能够方便解析不同来源的请求数据及不同格式的数据,在gin框架中就对应了不同的bind及bindXXX函数来解析对应的请求数据。以下就是对应的数据来源及不同格式的函数。
ShouldBindQuery函数
首先是来源于url地址中的查询参数,对应的解析函数是ShouldBindQuery
,结构体中通过给字段增加query
标签即可关联。如下:
ShouldBindHeader函数
其次是来源于请求头中的参数,对应的解析函数是ShouldBindHeader,结构体中通过给字段增加header
标签即可关联。如下:
ShouldBindXXX函数
然后是来源于请求体中的参数,这个略微复杂。若请求体是普通的文本格式的话,可以是JSON、XML、TOML、YAML或者protobuf、msgpack格式。可以对应ShouldBindXXX函数,如下:
若请求体是以表单形式发送数据的,会有formBinding、formPostBinding以及formMultipartBinding三个结构体。那这三个binding有什么区别呢?要想搞清楚三个结构体之间的区别,就要从form的enctype属性说起。
form的enctype属性
在html中,我们发送表单时一般会用<form>标签,但form标签有一个enctype
属性,该属性一般有两个值:multipart/form-data和application/x-www-form-urlencoded。这两个值什么意思呢?
属性为application/x-www-form-urlencoded
enctype为该属性时,代表将form中的值在发送给服务端时,会将form中的值组织成key1=value1&key2=value2这样的类型发送。如下:
<form action="http://localhost:9090/login?utm_source=login" method="POST" enctype="application/x-www-form-urlencoded">
<input type="text" name="username" value="yufuzi" />
<input type="text" name="password" />
<input type="file" name="f" />
<input type="submit" value="submit" />
</form>
当我们提交订单时,浏览器发送给服务端的请求参数会被编码成如下形式:
属性值为multipart/form-data
该属性值代表表达是可以发送二进制的数据,比如文件。如下:
<form action="http://localhost:9090/login?utm_source=login" method="POST" enctype="multipart/form-data">
<input type="text" name="username" value="yufuzi" />
<input type="text" name="password" />
<input type="file" name="f" />
<input type="submit" value="submit" />
</form>
同时,我们还发现在post的表单中,action的地址还可以带查询参数,即?utm_source=login参数。所以一个表单中能够携带参数的地方有:
- url地址中的查询参数。
- 表单的值域。即input控件。
根据发送时的编码方式又可以将值域参数分为按url查询参数编码的方式和混合方式。
gin请求中的Form、PostForm、MultipartForm结构体
根据请求参数来源的不同,在gin中也有对应的Form对象来承载对应的值。在go的net/http包的Request结构体中,我们发现有Form、PostForm、MultipartForm对象。这些对象就是分别承载不同来源的请求参数的。
- Form对象:其值来源于url地址中的查询参数和表单中的值域两部分。以上述login的表单为例,Form中的值则是utm_source=login, username=yufuzi,password=123456
- PostForm对象:其值来源于表单中的值域。以上述login的表单为例,PostForm中的值则是username=yufuzi,password=123456
- MultipartForm对象:其值来源于表单中的文件的值。以上述login的表单为例,MultipartForm中的值分为两部分,一部分是Values值,保存的是username=yufuzi,password=123456的值。一部分是文件的值,保存的是f中的文件句柄。
当然,在绑定请求参数的时候也有对应的bind方法。
在gin中对应的方法为ctx.ShouldBindWith(obj, binding.Form)
。当然,在使用ctx.ShouldBind方法时,默认也是绑定request.Form中的数据到结构体。
通过ctx.ShouldBindWith(obj, binding.FormPost)函数,可以将request.PostForm中的请求参数值绑定到对应的结构体上,如下:
通过ctx.ShouldBindWith(obj, binding.MIMEMultipartPOSTForm)函数,可以将request.PostForm中的请求参数值绑定到对应的结构体上,如下:
gin中bind函数的完整层级结构
在gin中,要将请求体绑定到结构体的操作的入口是从context包的函数开始的,然后是通过ShoudBindWith函数对接binding包中的具体的解析对象。最后,通过不同的函数将请求中不同的参数解析到结构体上。如下图所示:
四、总结
本文讲解了在gin框架中请求体的内容是如何绑定到对应结构体上的。同时分析了在gin中不同的bind函数以及bindXXX函数之间的差异。在其他框架中其实也类似,因为在底层的http包中是按标准协议传递参数的,上层只是实现不同而已。
特别推荐:一个专注go项目实战、项目中踩坑经验及避坑指南、各种好玩的go工具的公众号:「Go学堂」,专注实用性,非常值得大家关注。关注送《100个go常见的错误》pdf文档。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。