Go-Gin源码学习三

8次阅读

共计 4276 个字符,预计需要花费 11 分钟才能阅读完成。

学习目标

在第一篇中看到了 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 主流程可以完全的跑起来。结果这里就不写了,上面都描述过了。

正文完
 0