当前位置: 首页 > article >正文

gin源码阅读(2)请求体中的JSON参数是如何解析的?

gin源码阅读(2)请求体中的JSON参数是如何解析的?

这篇文章中我们会来关注一下gin是如何解析请求体中的json参数的。

绑定请求体数据的四种方式

Gin 提供了四种可直接将请求体中的 JSON 数据解析并绑定至相应类型的函数,分别是:BindJSON, Bind, ShouldBindJSON, ShouldBind。下面讲解的不会太涉及具体的 JSON 解析算法,而是更偏向于 Gin 内部的实现逻辑。

其中,可为它们分为两类,一类为 Must Bind,另一类为 Should Bind,前缀为 Should 的皆属于 Should Bind,而以 Bind 为前缀的,则属于 Must Bind。正如其名,Must Bind 一类在对请求进行解析时,若出现错误,会通过 c.AbortWithError(400, err).SetType(ErrorTypeBind) 终止请求,这会把响应码设置为 400,Content-Type 设置为 text/plain; charset=utf-8,在此之后,若尝试重新设置响应码,则会出现警告,如将响应码设置为 200:[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 200;而 Should Bind 一类在对请求进行解析时,若出现错误,只会将错误返回,而不会主动进行响应,所以,在使过程中,如果对产生解析错误的行为有更好的控制,最好使用 Should Bind 一类,自行对错误行为进行处理。

我们先来看一下开始提到的 BindBindJSON 这两个函数的实现源代码:

// gin v1.10.0 context.go

// Bind 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.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj any) error {
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.MustBindWith(obj, b)
}

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

// MustBindWith binds the passed struct pointer using the specified binding engine.
// It will abort the request with HTTP 400 if any error occurs.
// See the binding package.
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
	if err := c.ShouldBindWith(obj, b); err != nil {
		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
		return err
	}
	return nil
}

从上面的源码可以看到BindBindJSON其实调用的都是MustBindWith,而MustBindWith内部调用的又是ShouldBindWith函数。

常用的绑定JSON请求数据的ShouldBindJSON方法

从下面的源码中可以看到,常用的ShouldBindJSON(...)方法也是调用的ShouldBindWith方法。

// gin v1.10.0 context.go

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

接下来我们就详细看看ShouldBindWith方法内部是怎么实现的?代码如下所示:

// gin v1.10.0 context.go

// ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package.
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
	return b.Bind(c.Request, obj)
}

ShouldBindWith方法调用了接口binding.BindingBind(...)方法,我们先看一下这个接口的定义:

// gin v1.10.0 binding/binding.go

// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {
	Name() string
	Bind(*http.Request, any) error
}

然后我们接着看一下ShouldBindJSON(...)方法中传递的binding.JSON,如下所示:

// gin v1.10.0 binding/binding.go

// These implement the Binding interface and can be used to bind the data
// present in the request to struct instances.
var (
	JSON          BindingBody = jsonBinding{}
	XML           BindingBody = xmlBinding{}
	Form          Binding     = formBinding{}
	Query         Binding     = queryBinding{}
	FormPost      Binding     = formPostBinding{}
	FormMultipart Binding     = formMultipartBinding{}
	ProtoBuf      BindingBody = protobufBinding{}
	MsgPack       BindingBody = msgpackBinding{}
	YAML          BindingBody = yamlBinding{}
	Uri           BindingUri  = uriBinding{}
	Header        Binding     = headerBinding{}
	TOML          BindingBody = tomlBinding{}
)

可以看到在gin中定义了很多的binding.Binding接口的实现,其中我们重点关注jsonBinding类型,代码如下:

// gin v1.10.0 binding/json.go

type jsonBinding struct{}

func (jsonBinding) Name() string {
	return "json"
}

func (jsonBinding) Bind(req *http.Request, obj any) error {
	if req == nil || req.Body == nil {
		return errors.New("invalid request")
	}
	return decodeJSON(req.Body, obj)
}

func decodeJSON(r io.Reader, obj any) error {
	decoder := json.NewDecoder(r)
	if EnableDecoderUseNumber {
		decoder.UseNumber()
	}
	if EnableDecoderDisallowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if err := decoder.Decode(obj); err != nil {
		return err
	}
	return validate(obj)
}

简单总结一下上述代码,就是调用golang自带库的json包工具将请求体中的json数据解析成结构体数据。

总结

gin中绑定请求体中数据的方法有两大类共四个方法,分别是强制绑定的Bind(...)BindJson(...),以及非强制绑定的ShouldBind(...)ShouldBindJson(...)

我们详细分析了ShouldBindJson(...)方法,它的内部就是调用go自带库中的json包进行JSON请求数据的解析。

参考文章:

1、Gin 源码学习(二)丨请求体中的参数是如何解析的?


http://www.kler.cn/a/402434.html

相关文章:

  • 枚举类在数据库的存储方式类型对应的是什么
  • 误删了照片,甚至对存储卡进行了格式化 都可以找到丢失的图片,并让您恢复它们 支持一键恢复或永久删除丢失的照片、视频、音乐、文档等-供大家学习研究参考
  • 项目实战:基于深度学习的人脸表情识别系统设计与实现
  • Otter 安装流程
  • [译]Elasticsearch Sequence ID实现思路及用途
  • MATLAB的addpath和rmpath函数增加或删除路径
  • 科技赋能-JAVA发票查验接口、智能、高效的代名词
  • 【springboot】配置文件加载顺序
  • 「四」体验HarmonyOS端云一体化开发模板——工程目录结构与云侧工程一键部署AGC云端
  • 【D01】网络安全概论
  • mySql修改时区完整教程
  • 实战精选|如何使用 OpenVINO™ 在 ElectronJS 中创建桌面应用程序
  • Stable Diffusion核心网络结构——CLIP Text Encoder
  • 修改gitee提交时用户名密码输错导致提交失败的解决方法
  • 第14章 Nginx WEB服务器企业实战
  • 详细描述一下Elasticsearch搜索的过程?
  • 计算机网络安全 —— 对称加密算法 DES (一)
  • Linux TCP 服务器实现双向通信1v1
  • 【系统架构设计师】真题论文: 论企业应用系统的数据持久层架构设计(包括解题思路和素材)
  • Go小记:使用Go实现ssh客户端
  • Golang超详细入门教程
  • android 性能分析工具(04)Asan 内存检测工具
  • ROS2 Humble 机器人建模和Gazebo仿真
  • 【H2O2|全栈】MySQL的云端部署
  • 精通Rust系统教程-过程宏入门
  • Python操作Mysql中cursor的作用