gin源碼-ShouldBindQuery

gin使用Model binding綁定請求的內容到一個類型上。

使用

使用方法:

package main

import (
	"log"

	"github.com/gin-gonic/gin"
)

type Person struct {
	Name    string `form:"name"`
	Address string `form:"address"`
}

func main() {
	route := gin.Default()
	route.Any("/testing", startPage)
	route.Run(":8085")
}

func startPage(c *gin.Context) {
	var person Person
	if c.ShouldBindQuery(&person) == nil {
		log.Println("====== Only Bind By Query String ======")
		log.Println(person.Name)
		log.Println(person.Address)
	}
	c.String(200, "Success")
}

源碼

  1. c.ShouldBindQuery(&person)
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj interface{}) error {
	return c.ShouldBindWith(obj, binding.Query)
}

ShouldBindQuery 傳入一個接口類型的obj,執行了 c.ShouldBindWith(obj, binding.Query)

  1. c.ShouldBindWith(obj, binding.Query)
// 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)
}

ShouldBindWith 方法接收兩個參數,第一個參數傳一個接口類型的obj,第二個參數傳入一個 binding.Binding的實現。

// 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.
type Binding interface {
	Name() string
	Bind(*http.Request, interface{}) error
}

ShouldBindWith調用binding.Binding的Bind方法。

  1. binding.Query
package binding
var (
	...
	Query         = queryBinding{}
	...
)

binding.Query 初始化了一個queryBinding的結構體

// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package binding

import "net/http"

type queryBinding struct{}

func (queryBinding) Name() string {
	return "query"
}

// Bind方法傳入一個*http.Request和一個interface{}類型
func (queryBinding) Bind(req *http.Request, obj interface{}) error {
	// 獲取到請求的url裏的query的參數
	values := req.URL.Query()
	// 調用mapForm方法,傳入obj 和url.Value
	if err := mapForm(obj, values); err != nil {
		return err
	}
	return validate(obj)
}
  1. mapForm
    mapForm接收一個interface{}的指針和一個map[string][]string類型的參數
func mapForm(ptr interface{}, form map[string][]string) error {
	return mapFormByTag(ptr, form, "form")
}

調用mapFormByTag方法

  1. mapFormByTag
    mapFormByTag 接收一個tag的字符串,第4步的時候第三個參數傳入的是form
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
	return mappingByPtr(ptr, formSource(form), tag)
}

formSource(form) 把form轉換成formSource結構

type formSource map[string][]string

formSource實現了setter接口

// setter tries to set value on a walking by fields of a struct
type setter interface {
	TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
}

var _ setter = formSource(nil)

調用mappingByPtr方法

  1. mapFormByTag
    mappingByPtr 接收三個參數 第一個是interface{}的指針類型,第二個是setter的實現,第三個是tag,這裏傳入的是form
func mappingByPtr(ptr interface{}, setter setter, tag string) error {
	_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
	return err
}

調用mapping方法,傳入ptr反射的Value,空的reflect.SturctField結構emptyField,setter接口實現和tagform

var emptyField = reflect.StructField{}
  1. mapping
// mapping 
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
	// 這裏會判斷傳入的結構體字段類型field,如果標籤form爲-,就跳過。
	if field.Tag.Get(tag) == "-" { // just ignoring this field
		return false, nil
	}
	
	// 獲取到value的類別
	var vKind = value.Kind()
	
		// 這裏的意思是value如果是一個指針類型,並且它是nil,那麼會初始化一個該類型的零值。
	if vKind == reflect.Ptr {
		var isNew bool
		vPtr := value
		
		if value.IsNil() {
			isNew = true
				// New返回一個Value類型值,該值持有一個**指向類型爲typ的新申請的零值的指針**,返回值的Type爲PtrTo(typ)。
				// 這裏的value的Kind必須是是Array、Chan、Map、Ptr或Slice
			vPtr = reflect.New(value.Type().Elem())
		}
		// 取這個指針所對應的值vPtr.Elem(),再對它的值調用mapping,如果vPtr.Elem()是非匿名結構體或其他類別,mapping會嘗試設置值,傳入的field是emptyField,isSetted一定是false
		isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
		if err != nil {
			return false, err
		}
		// 如果指針爲nil,給該指針新申請了一個零值空間,並且指針對應的值已經被設置好了。
		if isNew && isSetted {
			// 那麼把value的值設置成vPtr
			// 到這裏可以理解爲什麼要用vPtr=value,因爲 vPtr.Elem()的值可能會設置失敗,那麼不應該更改原值。(類似於事務)
			value.Set(vPtr)
		}
		// 返回是否設置成功
		return isSetted, nil
	}
	
	// 如果vKind不是結構體,或者field不是匿名字段執行下面的語句(非匿名結構體和其他類別)
	if vKind != reflect.Struct || !field.Anonymous {
		// tryToSetValue 嘗試設置值
		ok, err := tryToSetValue(value, field, setter, tag)
		if err != nil {
			return false, err
		}
		if ok {
			return true, nil
		}
	}
	
	// 如果vKind是結構體(匿名結構體?)
	if vKind == reflect.Struct {
		tValue := value.Type()

		var isSetted bool
		// 拿到結構體的每個字段進行遍歷
		for i := 0; i < value.NumField(); i++ {
			sf := tValue.Field(i)
			// PkgPath是非導出字段的包路徑,對導出字段該字段爲""。
			// 這個字段是非導出字段,並且不是匿名字段就跳過
			if sf.PkgPath != "" && !sf.Anonymous { // unexported
				continue
			}
			// 用結構體的值調用mapping
			ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
			if err != nil {
				return false, err
			}
			// isSetted = (isSetted || ok)
			isSetted = isSetted || ok
		}
		return isSetted, nil
	}
	return false, nil
}

tryToSetValue:

func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
	var tagValue string
	var setOpt setOptions
	// 獲取到tag
	tagValue = field.Tag.Get(tag)
	// head方法用來將tag拿到的值用 ","分割,tagValue爲第一個,opts爲剩餘部分
	tagValue, opts := head(tagValue, ",")
	
	// 如果沒有設置tag,默認tag的值等於字段名
	if tagValue == "" { // default value is FieldName
		tagValue = field.Name
	}
	// field是emptyField的時候返回false(空指針類型tryToSetValue,在這裏返回false)
	if tagValue == "" { // when field is "emptyField" variable
		return false, nil
	}

	var opt string
	// 循環拆分tag
	for len(opts) > 0 {
		opt, opts = head(opts, ",")
		// 如果拆分出的opt是default= xxx這種形式的,那麼就是有默認值,默認值=v
		if k, v := head(opt, "="); k == "default" {
			setOpt.isDefaultExists = true
			setOpt.defaultValue = v
		}
	}
	
	// 調用TrySet方法設置值。
	return setter.TrySet(value, field, tagValue, setOpt)
}
  1. TrySet
    TrySet 試圖把request的值設置到接收對象中
// TrySet tries to set a value by request's form source (like map[string][]string)
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
	return setByForm(value, field, form, tagValue, opt)
}

setByForm:

func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
	vs, ok := form[tagValue]
	if !ok && !opt.isDefaultExists {
		return false, nil
	}

	switch value.Kind() {
	case reflect.Slice:
		if !ok {
			vs = []string{opt.defaultValue}
		}
		return true, setSlice(vs, value, field)
	case reflect.Array:
		if !ok {
			vs = []string{opt.defaultValue}
		}
		if len(vs) != value.Len() {
			return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
		}
		return true, setArray(vs, value, field)
	default:
		var val string
		if !ok {
			val = opt.defaultValue
		}

		if len(vs) > 0 {
			val = vs[0]
		}
		return true, setWithProperType(val, value, field)
	}
}

項目中遇到的一個坑

curl -X GET ‘localhost:8080’

package main

import (
	"fmt"

	"github.com/gin-gonic/gin"
)

func main() {
	s := gin.Default()
	s.GET("", testB)
	s.Run(":8080")
}

func testB(ctx *gin.Context) {
	in := struct {
		Name *uint
		Age  *uint
	}{}
	fmt.Printf("%+v", in)
	ctx.ShouldBindQuery(&in)
	fmt.Printf("%+v", in)
}

同樣的一段代碼,用gin的不同版本執行出來的結果是不一樣的。
之前的版本打印結果是:
{Name:<nil> Age:<nil>}{Name:0xc0000b65d8 Age:0xc0000b65e8}
之後的版本打印結果是:
{Name:<nil> Age:<nil>}{Name:<nil> Age:<nil>}
是因爲gin在19年3月份的時候對這裏的實現進行了修改。
https://github.com/gin-gonic/gin/commit/0d50ce859745354fa83dcf2bf4c972abed25e53b#diff-e1ee2d6085c74d622d08bb3927e7036c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章