「Go 框架」bind 函数:gin 框架中是如何绑定请求数据的?
大家好,我是渔夫子。
在 gin 框架中,我们知道用 bind 函数(或 bindXXX 函数)能够将请求体中的参数绑定到对应的结构体上。同时,你也会发现在 gin 中有很多 bind 或 bindXXX 函数,比如 ShouldBind、ShouldBindQuery、ShouldBindHeader、ShouldBindJSON 等等。那么,他们之间有什么不同呢?本文带你深入了解这些 bind 函数的使用。
特别推荐:关注「Go 学堂」,送《100 个 go 常见的错误》pdf 文档及 go 学习资料。
一、bind 的基本作用
在 gin 框架或其他所有 web 框架中,bind 或 bindXXX 函数(后文中我们统一都叫 bind 函数)的作用就是将请求体中的参数值绑定到对应的结构体上,以方便后续业务逻辑的处理。
接下来我们看一个简单的使用例子,该实例是期望客户端发送一个 JSON 格式的请求体,然后通过 JSON 标签绑定到 LoginRequest 结构体上。如下:
运行上述示例代码,并在 postman 中或使用 curl 给 http://localhost:9090/login 发送请求,请求体是:
在代码中,我们通过 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 这样的类型发送。如下:
当我们提交订单时,浏览器发送给服务端的请求参数会被编码成如下形式:
属性值为 multipart/form-data
该属性值代表表达是可以发送二进制的数据,比如文件。如下:
同时,我们还发现在 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 包中是按标准协议传递参数的,上层只是实现不同而已。
版权声明: 本文为 InfoQ 作者【Go学堂】的原创文章。
原文链接:【http://xie.infoq.cn/article/c1ef265df7410c53809560155】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论