目录
- 概述
- binding接口
- context.bind
- cnotext.mustbindwith
- shouldbindwith
- context.bindjson
- context.shouldbindjson
- context.shouldbinduri()
- context.shouldbinduri()
- 运行结果
- 总结
概述
gin框架中,有bind函数可以非常方便的将url的查询参数query parameter、http的header,body中提交上来的数据格式,如form,json,xml等,绑定到go中的结构体中去,这期间binding做了啥事情,这么多个bindding函数,我们该如何选择,一起通过源码来解开其中神秘的面纱吧。
binding接口
type binding interface { name() string bind(*http.request, interface{}) error }
binding是一个接口,在源码中,有10个实现了binding的结构体,以及3个接口
context.bind
// bind checks the content-type to select a binding engine automatically, // depending the "content-type" header different bindings are used: // "application/json" --> json binding // "application/xml" --> xml binding // otherwise --> returns an error. // 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 interface{}) error { b := binding.default(c.request.method, c.contenttype()) return c.mustbindwith(obj, b) }
cnotext.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 }
从注解和源码可以看出,mustbindwith最终也是调用了souldbindwith,并且对shouldbindwith的结果进行了判断,如果有错误,则以http 400的状态码进行退出。
shouldbindwith
// shouldbindwith binds the passed struct pointer using the specified binding engine. // see the binding package. func (c *context) shouldbindwith(obj interface{}, b binding.binding) error { return b.bind(c.request, obj) }
这个方法是所有其他绑定方法的一个基础,基本上所有的绑定方法都需要用到这个方法来对数据结构进行一个绑定
以上为主要的bingding的过程,其他派生出来的如bindjson、shouldbindjson等,为具体的数据类型的快捷方式而已,只是帮我们把具体的bingding的数据类型提前给封装了起来而已,如json格式的bingding函数
context.bindjson
// bindjson is a shortcut for c.mustbindwith(obj, binding.json). func (c *context) bindjson(obj interface{}) error { return c.mustbindwith(obj, binding.json) }
context.bindjson从源码上分析,可以看到,仅仅比bind方法少了一句
b := binding.default(c.request.method, c.contenttype())
这一句是为了判断当前的请求方法和contenttype,来给context.mustbindwith传的一个具体的bingding类型。
json的实现的binding接口如下
func (jsonbinding) bind(req *http.request, obj interface{}) error { if req == nil || req.body == nil { return fmt.errorf("invalid request") } return decodejson(req.body, obj) }
jsonbinding结构体实现了binding接口的bind方法,将请求过来的body数据进行解码,绑定到obj里面去
context.shouldbindjson
// shouldbindjson is a shortcut for c.shouldbindwith(obj, binding.json). func (c *context) shouldbindjson(obj interface{}) error { return c.shouldbindwith(obj, binding.json) }
从源码的注解来看,shouldbindjson其实就是shouldbindwith(obj, binding.json)的快捷方式,简单来说,就是在shouldbindwith(obj, binding.json)上面固定了参数,当我们明确规定,body提交的参数内容为json时,简化了我们的调用和增强了代码的可读性。
context.shouldbinduri()
// shouldbinduri binds the passed struct pointer using the specified binding engine. func (c *context) shouldbinduri(obj interface{}) error { m := make(map[string][]string) for _, v := range c.params { m[v.key] = []string{v.value} } return binding.uri.binduri(m, obj) }
从url绑定采用的方法跟header和body的方式不一样,不需要传入一个实现binding接口的结构体类型
context.shouldbinduri()
// binduri binds the passed struct pointer using binding.uri. // it will abort the request with http 400 if any error occurs. func (c *context) binduri(obj interface{}) error { if err := c.shouldbinduri(obj); err != nil { c.abortwitherror(http.statusbadrequest, err).settype(errortypebind) // nolint: errcheck return err } return nil }
binduri也是对shouldbinduri的一个封装,多了一个对shouldbinduri结果的一个判断 代码实例
代码如下
package main import ( "github.com/gin-gonic/gin" "net/http" ) type queryheader struct { myheader string `header:"myheader"` mydemo string `header:"mydemo"` } type querybody struct { name string `json:"name"` age int `json:"age"` sex int `json:"sex"` } type queryparameter struct { year int `form:"year"` month int `form:"month"` } type queryuri struct { id int `uri:"id"` name string `uri:"name"` } func binduri(context *gin.context){ var q queryuri err:= context.shouldbinduri(&q) if err != nil { context.json(http.statusbadrequest,gin.h{ "result":err.error(), }) return } context.json(http.statusok,gin.h{ "result":"绑定成功", "uri": q, }) } func bindquery(context *gin.context){ var q queryparameter err:= context.shouldbindquery(&q) if err != nil { context.json(http.statusbadrequest,gin.h{ "result":err.error(), }) return } context.json(http.statusok,gin.h{ "result":"绑定成功", "query": q, }) } func bindbody(context *gin.context){ var q querybody err:= context.shouldbindjson(&q) if err != nil { context.json(http.statusbadrequest,gin.h{ "result":err.error(), }) return } context.json(http.statusok,gin.h{ "result":"绑定成功", "body": q, }) } func bindhead(context *gin.context){ var q queryheader err := context.shouldbindheader(&q) if err != nil { context.json(http.statusbadrequest,gin.h{ "result":err.error(), }) return } context.json(http.statusok,gin.h{ "result":"绑定成功", "header": q, }) } func main(){ srv := gin.default() srv.get("/binding/header",bindhead) srv.get("/binding/body",bindbody) srv.get("/binding/query",bindquery) srv.get("/binding/:id/:name",binduri) srv.run(":9999") }
运行结果
绑定header数据
绑定queryparameter数据
绑定body json数据
绑定uri数据
总结
- 使用gin框架中的bind方法,可以很容易对http请求过来的数据传递到我们的结构体指针去,方便我们代码编程。
- 当参数比较简单,不需要结构体来进行封装时候,此时还需采用context的其他方法来获取对应的值
- gin在bind的时候,未对结构体的数据进行有效性检查,如果对数据有强要求时,需要自己对结构体的数据内容进行判断
- 建议在实践过程中,使用shouldbind
函数