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")
}
源碼
- 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)
- 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方法。
- 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)
}
- mapForm
mapForm接收一個interface{}的指針和一個map[string][]string類型的參數
func mapForm(ptr interface{}, form map[string][]string) error {
return mapFormByTag(ptr, form, "form")
}
調用mapFormByTag方法
- 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方法
- 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{}
- 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)
}
- 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