Gin源码分析 – Context之Bind
•
Gin框架
1 介绍
本小节主要介绍上下文的Bind相关函数。Bind函数的主要目的是自动提取HTTP请求中的各种参数到结构体中。下面给出了一个例子,该列子中调用Context.ShouldBindJSON自动将HTTP的JSON格式的参数绑定到Login结构体中,Gin的Bind存在两大类,一类是ShouldBindXXX,另一类是MustBindXXX,后面分别进行详细介绍。
package main import ( "github.com/gin-gonic/gin" "net/http" ) // 定义可绑定的数据结果,支持form,JSON,XML等格式 type Login struct { User string `form:"user" json:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"` } // binding JSON ({"user": "manu", "password": "123"}) func loginJSONHandler(c *gin.Context) { var json Login if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error":err.Error(), }) return } if json.User != "manu" || json.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{ "status":"unauthorized", }) return } c.JSON(http.StatusOK, gin.H { "status":"you are logged in", }) } func main() { router := gin.Default() router.POST("/loginJSON", loginJSONHandler) router.Run() }
2 MustBind
MustBind如果绑定发生了错误,则请求终止,并响应400状态码,包括一组函数,核心的函数是MustBindWith,函数定义入。
// 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 interface{}, 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 }
内部调用ShouldBindWith,但是当出现异常后,直接调用AbortWithError返回400异常,ShouldBindWith的实现在下一小节将进行详细的介绍。
binding.Binding 是Gin实现的绑定组件,详细注释见本小节附录1。在Context封装了如下的快捷调用函数,包括:BindJSON,BindXML,BindQuery,BindYAML,BindHeader。
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } // BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). func (c *Context) BindXML(obj interface{}) error { return c.MustBindWith(obj, binding.XML) } // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML). func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) } // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). func (c *Context) BindHeader(obj interface{}) error { return c.MustBindWith(obj, binding.Header) }
另外Bind根据ContentType进行判读,然后调用合适的函数,根据Default实际可以实现的有Form、JSON、XML、YAML、ProtoBuf、MsgPack、FormMultipart这样几种。
func (c *Context) Bind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.MustBindWith(obj, b) }
附录1 Binding
本小节对binding.go文件进行详细注释说明,具体各绑定引擎的实现见不同的方法
package binding import "net/http" // Content-Type MIME of the most common data formats. const ( MIMEJSON = "application/json" MIMEHTML = "text/html" MIMEXML = "application/xml" MIMEXML2 = "text/xml" MIMEPlain = "text/plain" MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK2 = "application/msgpack" MIMEYAML = "application/x-yaml" ) // 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. // 可以绑定JSON、XML、Query或者Form type Binding interface { Name() string Bind(*http.Request, interface{}) error } // BindingBody adds BindBody method to Binding. BindBody is similar with Bind, // but it reads the body from supplied bytes instead of req.Body. type BindingBody interface { Binding BindBody([]byte, interface{}) error } // BindingUri adds BindUri method to Binding. BindUri is similar with Bind, // but it reads the Params. // BindUri主要用于绑定URL中的Param参数 type BindingUri interface { Name() string BindUri(map[string][]string, interface{}) error } // StructValidator is the minimal interface which needs to be implemented in // order for it to be used as the validator engine for ensuring the correctness // of the request. Gin provides a default implementation for this using // https://github.com/go-playground/validator/tree/v10.6.1. type StructValidator interface { // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // If the received type is a slice|array, the validation should be performed travel on every element. // If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned. // If the received type is a struct or pointer to a struct, the validation should be performed. // If the struct is not valid or the validation itself fails, a descriptive error should be returned. // Otherwise nil must be returned. ValidateStruct(interface{}) error // Engine returns the underlying validator engine which powers the // StructValidator implementation. Engine() interface{} } // Validator is the default validator which implements the StructValidator // interface. It uses https://github.com/go-playground/validator/tree/v10.6.1 // under the hood. var Validator StructValidator = &defaultValidator{} // These implement the Binding interface and can be used to bind the data // present in the request to struct instances. // 入下的变量将实现各类Binding接口 var ( JSON = jsonBinding{} XML = xmlBinding{} Form = formBinding{} Query = queryBinding{} FormPost = formPostBinding{} FormMultipart = formMultipartBinding{} ProtoBuf = protobufBinding{} MsgPack = msgpackBinding{} YAML = yamlBinding{} Uri = uriBinding{} Header = headerBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method // and the content type. // 根据Content-Type,返回Binding引擎 func Default(method, contentType string) Binding { // Get方法将默认返回Form绑定 if method == http.MethodGet { return Form } switch contentType { case MIMEJSON: return JSON case MIMEXML, MIMEXML2: return XML case MIMEPROTOBUF: return ProtoBuf case MIMEMSGPACK, MIMEMSGPACK2: return MsgPack case MIMEYAML: return YAML case MIMEMultipartPOSTForm: return FormMultipart default: // case MIMEPOSTForm: return Form } } // 对数据进行合法性检查 func validate(obj interface{}) error { if Validator == nil { return nil } return Validator.ValidateStruct(obj) }
附录2 json.go
package binding import ( "bytes" "errors" "io" "net/http" "github.com/gin-gonic/gin/internal/json" ) // EnableDecoderUseNumber is used to call the UseNumber method on the JSON // Decoder instance. UseNumber causes the Decoder to unmarshal a number into an // interface{} as a Number instead of as a float64. var EnableDecoderUseNumber = false // EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method // on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to // return an error when the destination is a struct and the input contains object // keys which do not match any non-ignored, exported fields in the destination. // 使用允许不能识别的字段,当为false时,如果有不能识别的则返回错误。 var EnableDecoderDisallowUnknownFields = false type jsonBinding struct{} func (jsonBinding) Name() string { return "json" } // 将请求的Body绑定到obj中 func (jsonBinding) Bind(req *http.Request, obj interface{}) error { if req == nil || req.Body == nil { return errors.New("invalid request") } return decodeJSON(req.Body, obj) } // 直接将byte数组绑定到obj中 func (jsonBinding) BindBody(body []byte, obj interface{}) error { return decodeJSON(bytes.NewReader(body), obj) } func decodeJSON(r io.Reader, obj interface{}) error { // 创建json解码器 decoder := json.NewDecoder(r) // 设置是否能够使用Number if EnableDecoderUseNumber { decoder.UseNumber() } if EnableDecoderDisallowUnknownFields { decoder.DisallowUnknownFields() } // 进行实际的解码 if err := decoder.Decode(obj); err != nil { return err } // 最后进行合法性检查 return validate(obj) }