写点什么

「Go 框架」bind 函数:gin 框架中是如何绑定请求数据的?

作者:Go学堂
  • 2023-03-22
    北京
  • 本文字数:2919 字

    阅读完需:约 10 分钟

「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 结构体上。如下:


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 函数,如下:



若请求体是以表单形式发送数据的,会有 formBindingformPostBinding 以及 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 包中是按标准协议传递参数的,上层只是实现不同而已。

发布于: 刚刚阅读数: 7
用户头像

Go学堂

关注

微信公众号:Go学堂 2019-08-06 加入

专注Go编程知识、案例、常见错误及原理分析。意在通过阅读更多优秀的代码,提高编程技能。同名公众号「Go学堂」期待你的关注

评论

发布
暂无评论
「Go框架」bind函数:gin框架中是如何绑定请求数据的?_golang_Go学堂_InfoQ写作社区