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 这两个函数的实现源代码:

go 复制代码
// 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方法。

go 复制代码
// 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方法内部是怎么实现的?代码如下所示:

go 复制代码
// 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(...)方法,我们先看一下这个接口的定义:

go 复制代码
// 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,如下所示:

go 复制代码
// 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类型,代码如下:

go 复制代码
// 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 源码学习(二)丨请求体中的参数是如何解析的?

相关推荐
Wh1teR0se4 分钟前
[极客大挑战 2019]Secret File--详细解析
前端·web安全·网络安全
ZhaiMou1 小时前
HTML5拖拽API学习 托拽排序和可托拽课程表
前端·javascript·学习·html5
code_shenbing4 小时前
跨平台WPF框架Avalonia教程 三
前端·microsoft·ui·c#·wpf·跨平台·界面设计
白臻5 小时前
使用element-plus el-table中使用el-image层级冲突table表格会覆盖预览的图片等问题
前端·vue.js·elementui
北极糊的狐5 小时前
vue使用List.forEach遍历集合元素
前端·javascript·vue.js
晓看天色*5 小时前
[JAVA]MyBatis框架—获取SqlSession对象
java·开发语言·前端
ZVAyIVqt0UFji5 小时前
Reactflow图形库结合Dagre算法实现函数资源关系图
开发语言·前端·javascript·ecmascript
luckilyil5 小时前
前端—Cursor编辑器
前端·编辑器
Mephisto.java5 小时前
【大数据学习 | flume】flume Sink Processors与拦截器Interceptor
大数据·sql·oracle·sqlite·json·flume
cooldream20096 小时前
快速上手 Vue 3 的高效组件库Element Plus
前端·javascript·vue.js·element plus