Go Gin源码学习(三)

panlei914 · · 861 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

### 学习目标 在第一篇中看到了Gin提供了很多的获取和解析参数的方法: ``` // **** 输入数据 //从URL中拿值,比如 /user/:id => /user/john Param(key string) string //从GET参数中拿值,比如 /path?id=john GetQueryArray(key string) ([]string, bool) GetQuery(key string)(string, bool) Query(key string) string DefaultQuery(key, defaultValue string) string GetQueryArray(key string) ([]string, bool) QueryArray(key string) []string //从POST中拿数据 GetPostFormArray(key string) ([]string, bool) PostFormArray(key string) []string GetPostForm(key string) (string, bool) PostForm(key string) string DefaultPostForm(key, defaultValue string) string // 文件 FormFile(name string) (*multipart.FileHeader, error) MultipartForm() (*multipart.Form, error) SaveUploadedFile(file *multipart.FileHeader, dst string) error // 数据绑定 Bind(obj interface{}) error //根据Content-Type绑定数据 BindJSON(obj interface{}) error BindQuery(obj interface{}) error //--- Should ok, else return error ShouldBindJSON(obj interface{}) error ShouldBind(obj interface{}) error ShouldBindJSON(obj interface{}) error ShouldBindQuery(obj interface{}) error ``` 其中从url中获取 从get参数中获取 从post拿数据相信我们都可以想象的到,基本就是从request中的url或者body中获取数据然后返回 但是其中的数据绑定我自己开始是很疑惑的,到底是怎么实现的。疑惑的是如果object中我客户端少输入了参数 或者多输入的参数会是怎么样。举个例子: ``` type Login struct { User string `json:"user"` Password string `json:"password"` Age int } //伪代码 l := Login{} context.Bind(&l) ``` > 如果客户端输入的是{"user":"TAO","age":10} 没有输入password 得到的对象是什么会不会报错 > 如果客户端输入的是{"user":"TAO","age":10,"class":"class"}多输入了一个class的参数 结果是什么 > 如果客户端输入的是{} 这边会不会报错,会得到什么结果 这些都是需要看源码 ### 源码 先看**gin/binding/binding.go** ``` //Binding 接口 定义了一个返回名字的函数 //还有一个Bind 函数接口 这个是关键函数 //从request中获取数据解析 //所有类型的处理对象都继承这个接口来实现多态 type Binding interface { Name() string Bind(*http.Request, interface{}) error } type BindingBody interface { Binding BindBody([]byte, interface{}) error } //在包中定义了8个对象 可以供给context直接使用 var ( JSON = jsonBinding{} XML = xmlBinding{} Form = formBinding{} Query = queryBinding{} FormPost = formPostBinding{} FormMultipart = formMultipartBinding{} ProtoBuf = protobufBinding{} MsgPack = msgpackBinding{} ) ``` 接下来是继承Binding接口的对象,这样的对象有很多我们就举一个例子JSON,JSON是我们使用的最多的一个方式。 **gin/binding/json.go** ``` //type 是Gin处理json对象使用的类 继承Binding type jsonBinding struct{} func (jsonBinding) Name() string { return "json" } //调用decodeJSON方法处理 func (jsonBinding) Bind(req *http.Request, obj interface{}) error { return decodeJSON(req.Body, obj) } func (jsonBinding) BindBody(body []byte, obj interface{}) error { return decodeJSON(bytes.NewReader(body), obj) } // 可以看到,这个方法就是从request中获取body然后使用json包的decoder来转换成对象 func decodeJSON(r io.Reader, obj interface{}) error { decoder := json.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() } if err := decoder.Decode(obj); err != nil { return err } return validate(obj) } ``` 最后再看一下context是如何调用的 ``` //这个binding.JSON 就是上面binding中定义的那8个类其中的一个 这里可以直接的使用 func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } //这个方法可以接收Binding接口,在Gin中有7 8 个类都是继承了这个接口 都可以调用这个方法来执行解析 func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { if err = c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) } return } //这个方法就是调用上面一段代码定义的json的bind方法 func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { return b.Bind(c.Request, obj) } ``` 看了上面的代码,其实整个binding已经很清晰了。 最终,Gin就是调用了json包中的解析。那么我们上面的一系列问题就很轻松的解决了 - 如果客户端输入的是{"user":"TAO","age":10} 没有输入password 结果{"Tao",10} - 如果客户端输入的是{"user":"TAO","age":10,"class":"class"}多输入了一个class的参数 结果{"Tao",10} 如果在login类添加class 这里就会出现class - 如果客户端输入的是{} 这边会不会报错 结果{0} 只有age有一个默认值0 其余都是空字符串 这些行为跟json的处理行为是完全一致的。 ### 简单模仿实现 老规矩,在了解了binding的主体流程之后,我们可以自己简单的实现其中几个功能 比如json和xml的解析。我们可以看到以下代码,可以很清晰的看到Gin的实现方式。 ``` package mygin import ( "encoding/json" "encoding/xml" "net/http" ) var ( JSON = JsonBinding{} XML = XmlBinding{} ) //bind接口 方便多态 type Binding interface { //返回name Name() string //具体执行 Bind(*http.Request, interface{}) error } type XmlBinding struct { } func (XmlBinding) Name() string { return "XmlBinding" } func (XmlBinding) Bind(r *http.Request, obj interface{}) error { //使用json包中的方法 把string转成对象 decoder := xml.NewDecoder(r.Body) if err := decoder.Decode(obj); err != nil { return err } return nil } type JsonBinding struct { } func (JsonBinding) Name() string { return "jsonBinding" } func (JsonBinding) Bind(r *http.Request, obj interface{}) error { //使用json包中的方法 把string转成对象 decoder := json.NewDecoder(r.Body) if err := decoder.Decode(obj); err != nil { return err } return nil } // context调用 func (c *Context) BindWith(obj interface{}, b Binding) error { //调用binding 的Bind方法 if err := b.Bind(c.Request, obj); err != nil { return err } return nil } func (c *Context) BindJson(obj interface{}) error { return c.BindWith(obj, JSON) } ``` 代码比较简单,几乎不需要加注释就能很清楚的看懂。 配合上一篇文章的Gin主流程可以完全的跑起来。结果这里就不写了,上面都描述过了。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

861 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传