Go: 关于http请求的请求数据和Gin的参数绑定

398 阅读7分钟

一般客户端向服务器发送请求时,需要指定请求的方式、请求的包含内容(数据类型和请求体数据)、目标URL等信息和版本协议等。 今天主要说明请求包含的内容,也就是数据类型和请求数据以及在Gin框架中的对请求数据的参数绑定。

  • func (c *Context) ShouldBind(obj any) error{}
  • func (c *Context) ShouldBindJSON(obj any) error{}
  • func (c *Context) ShouldBindQuery(obj any) error{}

Content-Type

首先了解一下什么是Content-Type,它是HTTP协议中的一个头部字段,用于指示HTTP消息中实体主体的数据类型。它告诉接收方如何解析和处理请求或响应的主体数据。 举几个常见的Content-Type

  • application/json:JSON数据类型,用于传输JSON格式的数据。例如,用于表示API请求和响应中的JSON数据。
  • application/xml:XML数据类型,用于传输XML格式的数据。例如,用于表示Web服务之间的数据交换。
  • application/x-www-form-urlencoded:表单数据类型,常用于HTML表单提交数据。例如,通过POST请求将表单数据发送给服务器。
  • multipart/form-data:多部分表单数据类型,用于上传文件或二进制数据。例如,通过HTML表单上传文件到服务器。
  • text/plain:纯文本数据类型,用于传输普通文本数据。
  • image/jpeg:JPEG图像类型,用于传输JPEG格式的图片。
  • audio/mpeg:MPEG音频类型,用于传输MPEG格式的音频文件。
  • video/mp4:MP4视频类型,用于传输MP4格式的视频文件。

在HTTP请求中,通常由客户端在发送请求时设置Content-Type头部字段,以告知服务器发送的数据类型。在HTTP响应中,服务器会设置Content-Type头部字段,指示客户端接收到的数据类型。


JSON和form-data

接下来对请求体中最常用的两种数据格式做一个简单说明。

  1. JSON
    • 结构:JSON是一种轻量级的数据交换格式,基于键值对的方式组织数据。数据以键值对的形式表示,并使用大括号{}将整个数据包围。键(Key)是字符串,值(Value)可以是字符串、数字、布尔值、数组或其他JSON对象。
    • 编码:在JSON中,键和字符串的值都需要用双引号括起来,例如:"key": "value"。数字和布尔值没有引号,例如:"age": 30。
    • 示例:
    { 
       "name": "whyand666", 
       "age": 18, 
       "email": "1780006511@qq.com",
    }
    
  2. Form
    • 表单数据是通过键值对的形式传递,每个字段都有一个唯一的名称(键),以及一个对应的值。表单数据通常用于提交HTML表单,以便服务器处理用户输入。
    • 编码:表单数据使用URL编码格式,键值对之间用等号(=)连接,多个字段用&符号连接。特殊字符会进行URL编码,例如空格会被编码为+或%20。
    • 示例:
    name=why+and666&age=18&email=john.1780006511%40qq.com
    

总结:Form表单形式和JSON形式的入参在数据传输方式和数据结构上有所不同。Form表单适用于传递用户输入信息和文件上传,而JSON适用于数据交换和存储,具有更灵活和结构化的特点。至于具体的优点缺点自己查。

准备和环境

首先准备一个接口,这里只展示接口代码的一部分。

func (u *HandlerUser) register(c *gin.Context) {
   ......
   var req user.RegisterReq
   if err := c.ShouldBind(&req); err != nil {
      zap.S().Errorf("c.ShouldBind(req) failed ,err:%v", err)
      c.JSON(http.StatusOK, result.Fail(http.StatusBadRequest, "参数格式有误"))
      return
   }
   fmt.Println("req:", req)
   ......
}

参数定义

type RegisterReq struct {
   Email     string `json:"email" form:"email"`
   Name      string `json:"name" form:"name"`
   Password  string `json:"password" form:"password"`
   Password2 string `json:"password2" form:"password2"`
   Mobile    string `json:"mobile" form:"mobile"`
   Captcha   string `json:"captcha" form:"captcha"`
}

环境为go1.19,win10,apifox上打请求,Gin框架

image.png

1. ShouldBind()

// ShouldBind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example:
//
// "application/json" --> JSON binding
// "application/xml"  --> XML binding
//
// 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.
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
func (c *Context) ShouldBind(obj any) error {
   b := binding.Default(c.Request.Method, c.ContentType())
   return c.ShouldBindWith(obj, b)
}

context.ShouldBind() 是一个通用的方法,用于从 HTTP 请求中解析数据,并将其绑定到目标结构体中。这个方法会根据请求的 Content-Type 自动选择合适的解析方式,目前支持 JSON、XML、Form 表单等格式。 参数 obj 是一个指向目标结构体的指针,用于将解析后的数据填充到这个结构体中。

使用 context.ShouldBind() 的好处是不需要在代码中显式指定数据的来源(JSON、XML、Form 等),而是让 Gin 根据请求的 Content-Type 自动识别和解析数据。若无法正确解析数据或填充到目标结构体中,会返回相应的错误。

image.png

测试1. 通过 form-data 提交数据(json相同)

  • 提交成功:至于boundary后面的一堆数字是是用于分隔不同部分的边界字符串。 image.png

测试2. 通过 GET-Query 请求

  • 请求成功:GET请求通常用于向服务器请求数据,而不是向服务器提交数据。因此,GET请求不包含请求体所以在Content-Type头部字段中没有值。GET请求中的参数通常会以查询参数的形式出现在URL中。 image.png

测试3. 错误,通过 form-data 提交数据

注意这里改变了req的定义 image.png

  • 失败:直接报panic,因为在使用c.ShouldBind()时传入了一个未初始化的指针变量!!!
  • 一定要注意这个问题!!! 这里可以这么定义 req := new(user.RegisterReq) image.png

2. ShouldBindJSON()

// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj any) error {
   return c.ShouldBindWith(obj, binding.JSON)
}

测试1. 通过 json 提交数据

image.png

  • 成功: image.png

测试2. 通过 form 提交数据

image.png

  • 失败:这里的200只是请求成功。
  • 看这个报错,结合一下form的编码格式,说明ShouldBindJSON解析不了form。 image.png

3. ShouldBindQuery()

// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj any) error {
   return c.ShouldBindWith(obj, binding.Query)
}

在HTTP请求中,GET请求通常将查询参数(Query参数)附加在URL的末尾,而不是放在请求体中。查询参数是以key=value的形式出现在URL的问号(?)后面,并使用&符号分隔多个参数。

测试1. 通过 GET-Query 打请求

  • 成功:有数据,GET请求不包含请求体,所以在Content-Type头部字段中没有值。 image.png

测试2. 通过 JSON 提交数据

  • 失败:发现Content-Type为 json,绑定的结构体中并没有数据。 image.png

总结

  • context.ShouldBind() 是 Gin 框架中用于将 HTTP 请求数据绑定到目标结构体的通用方法。通过使用该方法,可以简化数据解析和绑定的过程。它会根据请求的 Content-Type 自动选择解析方式,然后将数据填充到结构体中。
  • context.ShouldBindJSON()用于将 HTTP 请求的 JSON 数据绑定到目标结构体的方法。
  • ShouldBindQuery()用于将HTTP请求中的查询参数绑定到目标结构体的方法。

它们的执行效率和数据的类型和大小以及请求的内容有关。 一般我自己用的话,GET请求就用ShouldBindQuery。对于json数据,我会写明是ShouldBindJSON(),form数据就用ShouldBind()了。

结尾:

这是我第一次在掘金上发文章,也是发现自己对这些知识点没有一个归纳,刚好也是字节青训营么,嘻嘻。 各位读者如果对文章有什么疑问或者补充,可以直接在评论区表达您的疑问,私信我也可以。 希望大家能够通过本文章学习到这些知识,谢谢大家!