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

948 阅读6分钟

大家好,我是渔夫子。

在gin框架中,我们知道用bind函数(或bindXXX函数)能够将请求体中的参数绑定到对应的结构体上。同时,你也会发现在gin中有很多bind或bindXXX函数,比如ShouldBind、ShouldBindQuery、ShouldBindHeader、ShouldBindJSON等等。那么,他们之间有什么不同呢?本文带你深入了解这些bind函数的使用。

「Go学堂」:一个专注go项目实战、项目中踩坑经验及避坑指南、各种好玩的go工具的公众号,「Go学堂」,专注实用性,非常值得大家关注。关注送《100个go常见的错误》pdf文档和go学习资料。

一、bind的基本作用

在gin框架或其他所有web框架中,bind或bindXXX函数(后文中我们统一都叫bind函数)的作用就是将请求体中的参数值绑定到对应的结构体上,以方便后续业务逻辑的处理。

image.png

接下来我们看一个简单的使用例子,该实例是期望客户端发送一个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)中的请求值绑定到对应的结构体上。

其大致流程如下:

image.png

二、请求数据来源

由第一节我们了解到,数据来源于客户端发来的请求。那么,在一次http请求中,都可以通过哪里来携带参数呢?根据http协议的标准,可以通过url中的查询参数,请求头、请求体等途径将参数传递给服务端。

image.png

在请求体中参数可以是不同的格式,比如JSON格式、XML格式、YAML格式、TOML格式、Protobuf message等。也可以是form表单的形式。

有了来源,接下来看看各个bind函数是如何把不同数据源的数据绑定到结构体上的。

三、bind及其bindXXX函数

为了能够方便解析不同来源的请求数据及不同格式的数据,在gin框架中就对应了不同的bind及bindXXX函数来解析对应的请求数据。以下就是对应的数据来源及不同格式的函数。

ShouldBindQuery函数

首先是来源于url地址中的查询参数,对应的解析函数是ShouldBindQuery,结构体中通过给字段增加query标签即可关联。如下:

image.png

ShouldBindHeader函数

其次是来源于请求头中的参数,对应的解析函数是ShouldBindHeader,结构体中通过给字段增加header标签即可关联。如下:

image.png

ShouldBindXXX函数

然后是来源于请求体中的参数,这个略微复杂。若请求体是普通的文本格式的话,可以是JSON、XML、TOML、YAML或者protobuf、msgpack格式。可以对应ShouldBindXXX函数,如下:

image.png

若请求体是以表单形式发送数据的,会有formBindingformPostBinding以及formMultipartBinding三个结构体。那这三个binding有什么区别呢?要想搞清楚三个结构体之间的区别,就要从form的enctype属性说起。

form的enctype属性

在html中,我们发送表单时一般会用

标签,但form标签有一个enctype属性,该属性一般有两个值:multipart/form-dataapplication/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>

当我们提交订单时,浏览器发送给服务端的请求参数会被编码成如下形式: image.png

属性值为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>

image.png

同时,我们还发现在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

image.png

  • PostForm对象:其值来源于表单中的值域。以上述login的表单为例,PostForm中的值则是username=yufuzi,password=123456

image.png

  • MultipartForm对象:其值来源于表单中的文件的值。以上述login的表单为例,MultipartForm中的值分为两部分,一部分是Values值,保存的是username=yufuzi,password=123456的值。一部分是文件的值,保存的是f中的文件句柄。

image.png

当然,在绑定请求参数的时候也有对应的bind方法。

在gin中对应的方法为ctx.ShouldBindWith(obj, binding.Form)。当然,在使用ctx.ShouldBind方法时,默认也是绑定request.Form中的数据到结构体。 image.png

通过ctx.ShouldBindWith(obj, binding.FormPost)函数,可以将request.PostForm中的请求参数值绑定到对应的结构体上,如下: image.png

通过ctx.ShouldBindWith(obj, binding.MIMEMultipartPOSTForm)函数,可以将request.PostForm中的请求参数值绑定到对应的结构体上,如下: image.png

gin中bind函数的完整层级结构

在gin中,要将请求体绑定到结构体的操作的入口是从context包的函数开始的,然后是通过ShoudBindWith函数对接binding包中的具体的解析对象。最后,通过不同的函数将请求中不同的参数解析到结构体上。如下图所示: image.png

四、总结

本文讲解了在gin框架中请求体的内容是如何绑定到对应结构体上的。同时分析了在gin中不同的bind函数以及bindXXX函数之间的差异。在其他框架中其实也类似,因为在底层的http包中是按标准协议传递参数的,上层只是实现不同而已。

本文正在参加「金石计划」