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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值