echo 源碼分析之?dāng)?shù)據(jù)綁定過(guò)程
我們知道http的參數(shù)傳遞的形式有很多,header、path、query、body,body( json,form)等等,針對(duì)各種形式的參數(shù),通過(guò)bind方式來(lái)解析是比較清晰的方式,但是echo的bind 方式也是在從不完完善到逐漸完善的過(guò)程中,實(shí)踐中你會(huì)發(fā)現(xiàn),不同版本的echo,會(huì)出現(xiàn)詭異的結(jié)果,我將以下面的例子,針對(duì)v3.3.10、v4.1.17、v4.2.1三個(gè)版本的echo進(jìn)行分析。
package mainimport ("fmt""net/http""github.com/labstack/echo/v4")type User struct {Name string `json:"name" xml:"name` //param:"name" query:"name" form:"name" //curl -XGET http://localhost:1323/users/Joe\?email\=joe_emailEmail string `json:"email" form:"email" query:"email"`}func main() {e := echo.New()e.GET("/users/:name", func(c echo.Context) error {u := new(User)u.Name = c.Param("name")if err := c.Bind(u); err != nil {return c.JSON(http.StatusBadRequest, nil)}return c.JSON(http.StatusOK, u)})fmt.Println("--------------------")e.GET("/users/:name/share/:id", func(c echo.Context) error {u := new(User)//u.Name = c.Param("name")if err := c.Bind(u); err != nil {return c.JSON(http.StatusBadRequest, nil)}return c.JSON(http.StatusOK, u)})fmt.Println("--------------------")e.GET("/users/names", func(c echo.Context) error {u := new(User)if err := c.Bind(u); err != nil {return c.JSON(http.StatusBadRequest, nil)}return c.JSON(http.StatusOK, u)})fmt.Println("--------------------")e.GET("/users/names/*", func(c echo.Context) error {u := new(User)if err := c.Bind(u); err != nil {return c.JSON(http.StatusBadRequest, nil)}return c.JSON(http.StatusOK, u)})fmt.Println(e.Start(":1336"))}
如果我們引用
"github.com/labstack/echo"默認(rèn)版本是v3.3.10
如果引用
"github.com/labstack/echo/v4"默認(rèn)是最新版v4.2.1,但是v4.2.1和v4.1.17版本差異比較大,所以分析上述三個(gè)版本。
首先看下路由注冊(cè)的過(guò)程
e.GET("/users/:name", func(c echo.Context) error {u := new(User)u.Name = c.Param("name")if err := c.Bind(u); err != nil {return c.JSON(http.StatusBadRequest, nil)}return c.JSON(http.StatusOK, u)})
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {name := handlerName(handler)e.router.Add(method, path, func(c Context) error {h := handler// Chain middlewarefor i := len(middleware) - 1; i >= 0; i-- {h = middleware[i](h)}return h(c)})r := &Route{Method: method,Path: path,Name: name,}e.router.routes[method+path] = rreturn r}
func (r *Router) Add(method, path string, h HandlerFunc) {// Validate pathif path == "" {panic("echo: path cannot be empty")}if path[0] != '/' {path = "/" + path}pnames := []string{} // Param namesppath := path // Pristine pathfor i, l := 0, len(path); i < l; i++ {if path[i] == ':' {j := i + 1r.insert(method, path[:i], nil, skind, "", nil)for ; i < l && path[i] != '/'; i++ {}pnames = append(pnames, path[j:i])path = path[:j] + path[i:]i, l = j, len(path)if i == l {r.insert(method, path[:i], h, pkind, ppath, pnames)return}r.insert(method, path[:i], nil, pkind, "", nil)} else if path[i] == '*' {r.insert(method, path[:i], nil, skind, "", nil)pnames = append(pnames, "*")r.insert(method, path[:i+1], h, akind, ppath, pnames)return}}r.insert(method, path, h, skind, ppath, pnames)}
這里可以看到,在路由注冊(cè)構(gòu)建前綴樹的過(guò)程中會(huì)把路由解析規(guī)整為三個(gè)類型,路徑參數(shù)類型(:),精確匹配路由(/),正則匹配路由(*)
同時(shí)針對(duì)路徑參數(shù)類型(:),會(huì)將路徑中的參數(shù)名字保存在變量pnames里面,最終存在router的tree上
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {// Adjust max paraml := len(pnames)if *r.echo.maxParam < l {*r.echo.maxParam = l}cn := r.tree // Current node as rootif cn == nil {panic("echo: invalid method")}search := pathfor {sl := len(search)pl := len(cn.prefix)l := 0// LCPmax := plif sl < max {max = sl}for ; l < max && search[l] == cn.prefix[l]; l++ {}if l == 0 {// At root nodecn.label = search[0]cn.prefix = searchif h != nil {cn.kind = tcn.addHandler(method, h)cn.ppath = ppathcn.pnames = pnames}} else if l < pl {// Split noden := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)// Reset parent nodecn.kind = skindcn.label = cn.prefix[0]cn.prefix = cn.prefix[:l]cn.children = nilcn.methodHandler = new(methodHandler)cn.ppath = ""cn.pnames = nilcn.addChild(n)if l == sl {// At parent nodecn.kind = tcn.addHandler(method, h)cn.ppath = ppathcn.pnames = pnames} else {// Create child noden = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)n.addHandler(method, h)cn.addChild(n)}} else if l < sl {search = search[l:]c := cn.findChildWithLabel(search[0])if c != nil {// Go deepercn = ccontinue}// Create child noden := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)n.addHandler(method, h)cn.addChild(n)} else {// Node already existsif h != nil {cn.addHandler(method, h)cn.ppath = ppathif len(cn.pnames) == 0 { // Issue #729cn.pnames = pnames}}}return}}
接著我們看下,請(qǐng)求到來(lái)的時(shí)候,參數(shù)匹配的過(guò)程
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {// Acquire contextc := e.pool.Get().(*context)c.Reset(r, w)h := NotFoundHandlerif e.premiddleware == nil {e.router.Find(r.Method, getPath(r), c)h = c.Handler()for i := len(e.middleware) - 1; i >= 0; i-- {h = e.middleware[i](h)}} else {h = func(c Context) error {e.router.Find(r.Method, getPath(r), c)h := c.Handler()for i := len(e.middleware) - 1; i >= 0; i-- {h = e.middleware[i](h)}return h(c)}for i := len(e.premiddleware) - 1; i >= 0; i-- {h = e.premiddleware[i](h)}}// Execute chainif err := h(c); err != nil {e.HTTPErrorHandler(err, c)}// Release contexte.pool.Put(c)}
其實(shí)就是到router中通過(guò)最長(zhǎng)前綴匹配算法進(jìn)行匹配
e.router.Find(r.Method, getPath(r), c)其中g(shù)etPath函數(shù)定義如下
func getPath(r *http.Request) string {path := r.URL.RawPathif path == "" {path = r.URL.Path}return path}
find是路徑匹配的過(guò)程
func (r *Router) Find(method, path string, c Context) {ctx := c.(*context)ctx.path = pathcn := r.tree // Current node as rootvar (search = pathchild *node // Child noden int // Param counternk kind // Next kindnn *node // Next nodens string // Next searchpvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice)// Search order static > param > anyfor {if search == "" {break}pl := 0 // Prefix lengthl := 0 // LCP lengthif cn.label != ':' {sl := len(search)pl = len(cn.prefix)// LCPmax := plif sl < max {max = sl}for ; l < max && search[l] == cn.prefix[l]; l++ {}}if l == pl {// Continue searchsearch = search[l:]} else {cn = nnsearch = nsif nk == pkind {goto Param} else if nk == akind {goto Any}// Not foundreturn}if search == "" {break}// Static nodeif child = cn.findChild(search[0], skind); child != nil {// Save nextif cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623nk = pkindnn = cnns = search}cn = childcontinue}// Param nodeParam:if child = cn.findChildByKind(pkind); child != nil {// Issue #378if len(pvalues) == n {continue}// Save nextif cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623nk = akindnn = cnns = search}cn = childi, l := 0, len(search)for ; i < l && search[i] != '/'; i++ {}pvalues[n] = search[:i]n++search = search[i:]continue}// Any nodeAny:if cn = cn.findChildByKind(akind); cn == nil {if nn != nil {cn = nnnn = cn.parent // Next (Issue #954)search = nsif nk == pkind {goto Param} else if nk == akind {goto Any}}// Not foundreturn}pvalues[len(cn.pnames)-1] = searchbreak}ctx.handler = cn.findHandler(method)ctx.path = cn.ppathctx.pnames = cn.pnames// NOTE: Slow zone...if ctx.handler == nil {ctx.handler = cn.checkMethodNotAllowed()// Dig further for any, might have an empty value for *, e.g.// serving a directory. Issue #207.if cn = cn.findChildByKind(akind); cn == nil {return}if h := cn.findHandler(method); h != nil {ctx.handler = h} else {ctx.handler = cn.checkMethodNotAllowed()}ctx.path = cn.ppathctx.pnames = cn.pnamespvalues[len(cn.pnames)-1] = ""}return}
可以看到,將匹配到的值和路徑參數(shù)名一一對(duì)應(yīng)保存到了pvalues里面,最終是保存再來(lái)ctx里面
可以看到匹配過(guò)程中,會(huì)根據(jù)路徑參數(shù)類型來(lái)進(jìn)行處理
func (n *node) findChild(l byte, t kind) *node {for _, c := range n.children {if c.label == l && c.kind == t {return c}}return nil}
有沒有簡(jiǎn)單直接的方法來(lái)查看我們最終路由注冊(cè)后pnames的存儲(chǔ)結(jié)果和請(qǐng)求路徑匹配過(guò)程中pvalues的參數(shù)匹配結(jié)果呢?可以在echo中,加下面幾行代碼,進(jìn)行打印
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {e.router.routes[method+path] = rprintTree(e.router.tree)return r}
其中printTree的定義如下
func printTree(tree *node) {err1 := json.Marshal(struct {Kind kindLabel bytePrefix stringParent *nodeChildren childrenChildrenNum intPpath stringPnames []stringMethodHandler *methodHandler}{Kind: tree.kind,Label: tree.label,Prefix: tree.prefix,Parent: tree.parent,Children: tree.children,ChildrenNum: len(tree.children),Ppath: tree.ppath,Pnames: tree.pnames,MethodHandler: tree.methodHandler,})err1)for i, v := range tree.children {fmt.Println(i)printTree(v)}}
可以看到我們的路由注冊(cè)結(jié)果
{"Kind":0,"Label":47,"Prefix":"/users/","Parent":null,"Children":[{}],"ChildrenNum":1,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":1,"Label":58,"Prefix":":","Parent":{},"Children":null,"ChildrenNum":0,"Ppath":"/users/:name","Pnames":["name"],"MethodHandler":{}} <nil>--------------------{"Kind":0,"Label":47,"Prefix":"/users/","Parent":null,"Children":[{}],"ChildrenNum":1,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":1,"Label":58,"Prefix":":","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"/users/:name","Pnames":["name"],"MethodHandler":{}} <nil>0{"Kind":0,"Label":47,"Prefix":"/share/","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":1,"Label":58,"Prefix":":","Parent":{},"Children":null,"ChildrenNum":0,"Ppath":"/users/:name/share/:id","Pnames":["name","id"],"MethodHandler":{}} <nil>--------------------{"Kind":0,"Label":47,"Prefix":"/users/","Parent":null,"Children":[{},{}],"ChildrenNum":2,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":1,"Label":58,"Prefix":":","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"/users/:name","Pnames":["name"],"MethodHandler":{}} <nil>0{"Kind":0,"Label":47,"Prefix":"/share/","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":1,"Label":58,"Prefix":":","Parent":{},"Children":null,"ChildrenNum":0,"Ppath":"/users/:name/share/:id","Pnames":["name","id"],"MethodHandler":{}} <nil>1{"Kind":0,"Label":110,"Prefix":"names","Parent":{},"Children":null,"ChildrenNum":0,"Ppath":"/users/names","Pnames":[],"MethodHandler":{}} <nil>--------------------{"Kind":0,"Label":47,"Prefix":"/users/","Parent":null,"Children":[{},{}],"ChildrenNum":2,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":1,"Label":58,"Prefix":":","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"/users/:name","Pnames":["name"],"MethodHandler":{}} <nil>0{"Kind":0,"Label":47,"Prefix":"/share/","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":1,"Label":58,"Prefix":":","Parent":{},"Children":null,"ChildrenNum":0,"Ppath":"/users/:name/share/:id","Pnames":["name","id"],"MethodHandler":{}} <nil>1{"Kind":0,"Label":110,"Prefix":"names","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"/users/names","Pnames":[],"MethodHandler":{}} <nil>0{"Kind":0,"Label":47,"Prefix":"/","Parent":{},"Children":[{}],"ChildrenNum":1,"Ppath":"","Pnames":null,"MethodHandler":{}} <nil>0{"Kind":2,"Label":42,"Prefix":"*","Parent":{},"Children":null,"ChildrenNum":0,"Ppath":"/users/names/*","Pnames":["*"],"MethodHandler":{}} <nil>
如何看參數(shù)匹配結(jié)果呢?同樣處理
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {e.router.Find(r.Method, getPath(r), c)}
在find函數(shù)里加下面代碼
func (r *Router) Find(method, path string, c Context) {v, err := json.Marshal(struct {//Request *http.Request//Response *ResponsePath stringPnames []stringPvalues []stringQuery url.Values//Handler HandlerFuncStore Map}{//Request: ctx.request,//Response: ctx.response,Path: ctx.path,Pnames: ctx.pnames,Pvalues: ctx.pvalues,Query: ctx.query,//Handler: ctx.handler,Store: ctx.store,})fmt.Println(string(v), err)return}
//{"Path":"/users/:name","Pnames":["name"],"Pvalues":["Joe",""],"Query":null,"Store":null} <nil>//{"Path":"/users/:name/share/:id","Pnames":["name","id"],"Pvalues":["Joe","1"],"Query":null,"Store":null} <nil>
這時(shí)候我們切換不同版本的echo,可以看到不同的結(jié)果
% curl -XGET http://localhost:1336/users/Joe/share\?email\=joe_email{"message":"Not Found"}% curl -XGET http://localhost:1336/users/Joe/share/1\?email\=joe_email{"name":"Joe","email":"joe_email"}
首先看v3.3.10的實(shí)現(xiàn)
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {if req.ContentLength == 0 {if err = b.bindData(i, c.QueryParams(), "query"); err != nil {}}ctype := req.Header.Get(HeaderContentType)switch {case strings.HasPrefix(ctype, MIMEApplicationJSON):if err = json.NewDecoder(req.Body).Decode(i); err != nil {}}}
這個(gè)實(shí)現(xiàn)是有問題的,因?yàn)榧词故莋et請(qǐng)求,ContentLength也不會(huì)是0
% curl -i -XGET http://localhost:1336/users/Joe/share/1\?email\=joe_emailHTTP/1.1 200 OKContent-Type: application/json; charset=UTF-8Date: Tue, 30 Mar 2021 03:40:22 GMTContent-Length: 35{"name":"","email":"joe_email"}
針對(duì)contentlength=0的情況,調(diào)用了bindData方法
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {typ := reflect.TypeOf(ptr).Elem()val := reflect.ValueOf(ptr).Elem()for i := 0; i < typ.NumField(); i++ {inputFieldName := typeField.Tag.Get(tag)// If tag is nil, we inspect if the field is a struct.if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {}}inputValue, exists := data[inputFieldName]}}
里面其實(shí)是應(yīng)用了反射,對(duì)結(jié)構(gòu)體的值進(jìn)行了修改,可以看到,如果header里contentlength不為零,路徑里的參數(shù)根本匹配不上
其中QueryParams()返回的是url里面的值
QueryParams() url.Values我們升級(jí)到v4.1.17看看
% go get -u github.com/labstack/echo/[email protected]go: finding module for package github.com/labstack/echo代碼里引用的地方也要由"github.com/labstack/echo"改成"github.com/labstack/echo/v4"否則會(huì)go: found github.com/labstack/echo in github.com/labstack/echo v3.3.10+incompatible
路徑參數(shù)綁定成功了
% curl -i -XGET http://localhost:1336/users/Joe/share/1\?email\=joe_emailHTTP/1.1 200 OKContent-Type: application/json; charset=UTF-8Date: Tue, 30 Mar 2021 05:21:10 GMTContent-Length: 35{"name":"Joe","email":"joe_email"}
我們發(fā)現(xiàn)參數(shù)綁定方法已經(jīng)重寫了
// Bind implements the `Binder#Bind` function.func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {req := c.Request()names := c.ParamNames()values := c.ParamValues()params := map[string][]string{}for i, name := range names {params[name] = []string{values[i]}}if err := b.bindData(i, params, "param"); err != nil {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}if err = b.bindData(i, c.QueryParams(), "query"); err != nil {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}if req.ContentLength == 0 {return}ctype := req.Header.Get(HeaderContentType)switch {case strings.HasPrefix(ctype, MIMEApplicationJSON):if err = json.NewDecoder(req.Body).Decode(i); err != nil {if ute, ok := err.(*json.UnmarshalTypeError); ok {return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)} else if se, ok := err.(*json.SyntaxError); ok {return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)}return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}}return}
依次會(huì)對(duì)路徑參數(shù)param,query參數(shù)query,以及body進(jìn)行綁定,body綁定依賴http的header
下面是bindData函數(shù)
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {if ptr == nil || len(data) == 0 {return nil}typ := reflect.TypeOf(ptr).Elem()val := reflect.ValueOf(ptr).Elem()// Mapif typ.Kind() == reflect.Map {for k, v := range data {val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))}return nil}// !structif typ.Kind() != reflect.Struct {return errors.New("binding element must be a struct")}for i := 0; i < typ.NumField(); i++ {typeField := typ.Field(i)structField := val.Field(i)if !structField.CanSet() {continue}structFieldKind := structField.Kind()inputFieldName := typeField.Tag.Get(tag)if inputFieldName == "" {inputFieldName = typeField.Name //在4.2.1中刪除了這個(gè)字段// If tag is nil, we inspect if the field is a struct.if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {return err}continue}}inputValue, exists := data[inputFieldName]if !exists {// Go json.Unmarshal supports case insensitive binding. However the// url params are bound case sensitive which is inconsistent. To// fix this we must check all of the map values in a// case-insensitive search.for k, v := range data {if strings.EqualFold(k, inputFieldName) {inputValue = vexists = truebreak}}}if !exists {continue}// Call this first, in case we're dealing with an alias to an array typeif ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {if err != nil {return err}continue}numElems := len(inputValue)if structFieldKind == reflect.Slice && numElems > 0 {sliceOf := structField.Type().Elem().Kind()slice := reflect.MakeSlice(structField.Type(), numElems, numElems)for j := 0; j < numElems; j++ {if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {return err}}val.Field(i).Set(slice)} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {return err}}return nil}
可以看到,在匹配路徑參數(shù)的過(guò)程中,如果結(jié)構(gòu)體的tag里沒有param,會(huì)選取結(jié)構(gòu)體的參數(shù)名,到路徑參數(shù)里去取對(duì)應(yīng)的value
inputFieldName := typeField.Tag.Get(tag)if inputFieldName == "" {inputFieldName = typeField.Name //在4.2.1中刪除了這個(gè)字段// If tag is nil, we inspect if the field is a struct.if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {return err}continue}}
也就是說(shuō),struct的tag即使不正確,也是可能匹配到正確參數(shù)的
最后看看v4.2.1的實(shí)現(xiàn)
go get -u github.com/labstack/echo/v4func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {if err := b.BindPathParams(c, i); err != nil {return err}if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {if err = b.BindQueryParams(c, i); err != nil {return err}}return b.BindBody(c, i)}
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {names := c.ParamNames()values := c.ParamValues()params := map[string][]string{}for i, name := range names {params[name] = []string{values[i]}}if err := b.bindData(i, params, "param"); err != nil {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}return nil}func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {if err := b.bindData(i, c.QueryParams(), "query"); err != nil {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}return nil}func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {req := c.Request()if req.ContentLength == 0 {return}ctype := req.Header.Get(HeaderContentType)switch {case strings.HasPrefix(ctype, MIMEApplicationJSON):if err = json.NewDecoder(req.Body).Decode(i); err != nil {}}}
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error {if destination == nil || len(data) == 0 {return nil}typ := reflect.TypeOf(destination).Elem()val := reflect.ValueOf(destination).Elem()// Mapif typ.Kind() == reflect.Map {for k, v := range data {val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))}return nil}// !structif typ.Kind() != reflect.Struct {return errors.New("binding element must be a struct")}for i := 0; i < typ.NumField(); i++ {typeField := typ.Field(i)structField := val.Field(i)if !structField.CanSet() {continue}structFieldKind := structField.Kind()inputFieldName := typeField.Tag.Get(tag)if inputFieldName == "" {// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).// structs that implement BindUnmarshaler are binded only when they have explicit tagif _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {return err}}// does not have explicit tag and is not an ordinary struct - so move to next fieldcontinue //注意從哪部移動(dòng)出來(lái)了,所以,沒有tag就不繼續(xù)了}inputValue, exists := data[inputFieldName]if !exists {// Go json.Unmarshal supports case insensitive binding. However the// url params are bound case sensitive which is inconsistent. To// fix this we must check all of the map values in a// case-insensitive search.for k, v := range data {if strings.EqualFold(k, inputFieldName) {inputValue = vexists = truebreak}}}if !exists {continue}// Call this first, in case we're dealing with an alias to an array typeif ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {if err != nil {return err}continue}numElems := len(inputValue)if structFieldKind == reflect.Slice && numElems > 0 {sliceOf := structField.Type().Elem().Kind()slice := reflect.MakeSlice(structField.Type(), numElems, numElems)for j := 0; j < numElems; j++ {if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {return err}}val.Field(i).Set(slice)} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {return err}}return nil}
可以看到,匹配過(guò)程更嚴(yán)格了,嚴(yán)格要求按照結(jié)構(gòu)體tag定義來(lái)匹配
inputFieldName := typeField.Tag.Get(tag)if inputFieldName == "" {// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).// structs that implement BindUnmarshaler are binded only when they have explicit tagif _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {return err}}// does not have explicit tag and is not an ordinary struct - so move to next fieldcontinue //注意從哪部移動(dòng)出來(lái)了,所以,沒有tag就不繼續(xù)了}
好處是什么呢?可以處理同名參數(shù)
這兩個(gè)版本可以具體diff一下看看改動(dòng)
vimdiff ~/go/pkg/mod/github.com/labstack/echo/v4@v4.1.17/bind.go ~/go/pkg/mod/github.com/labstack/echo/v4@v4.2.1/bind.go 推薦閱讀
