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
一类,自行对错误行为进行处理。
我们先来看一下开始提到的 Bind
和 BindJSON
这两个函数的实现源代码:
// 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
}
从上面的源码可以看到Bind
和BindJSON
其实调用的都是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.Binding
的Bind(...)
方法,我们先看一下这个接口的定义:
// 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 源码学习(二)丨请求体中的参数是如何解析的?