Go Gin源碼學習(三) 參數解析

學習目標

在第一篇中看到了Gin提供了不少的獲取和解析參數的方法:json

// **** 輸入數據
//從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.gopost

//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是如何調用的url

//這個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包中的解析。那麼咱們上面的一系列問題就很輕鬆的解決了code

  • 若是客戶端輸入的是{"user":"TAO","age":10} 沒有輸入password 結果{"Tao",10}
  • 若是客戶端輸入的是{"user":"TAO","age":10,"class":"class"}多輸入了一個class的參數 結果{"Tao",10} 若是在login類添加class 這裏就會出現class
  • 若是客戶端輸入的是{} 這邊會不會報錯 結果{0} 只有age有一個默認值0 其他都是空字符串

這些行爲跟json的處理行爲是徹底一致的。orm

簡單模仿實現

老規矩,在瞭解了binding的主體流程以後,咱們能夠本身簡單的實現其中幾個功能 好比json和xml的解析。咱們能夠看到如下代碼,能夠很清晰的看到Gin的實現方式。xml

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主流程能夠徹底的跑起來。結果這裏就不寫了,上面都描述過了。對象

相關文章
相關標籤/搜索