1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        echo 源碼分析之?dāng)?shù)據(jù)綁定過(guò)程

        共 25415字,需瀏覽 51分鐘

         ·

        2021-05-10 17:58

        我們知道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 main
        import ( "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_email Email 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 middleware    for 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] = r  return r}
        func (r *Router) Add(method, path string, h HandlerFunc) {  // Validate path  if path == "" {    panic("echo: path cannot be empty")  }  if path[0] != '/' {    path = "/" + path  }  pnames := []string{} // Param names  ppath := path        // Pristine path
        for i, l := 0, len(path); i < l; i++ { if path[i] == ':' { j := i + 1
        r.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 param  l := len(pnames)  if *r.echo.maxParam < l {    *r.echo.maxParam = l  }
        cn := r.tree // Current node as root if cn == nil { panic("echo: invalid method") } search := path
        for { sl := len(search) pl := len(cn.prefix) l := 0
        // LCP max := pl if sl < max { max = sl } for ; l < max && search[l] == cn.prefix[l]; l++ { }
        if l == 0 { // At root node cn.label = search[0] cn.prefix = search if h != nil { cn.kind = t cn.addHandler(method, h) cn.ppath = ppath cn.pnames = pnames } } else if l < pl { // Split node n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
        // Reset parent node cn.kind = skind cn.label = cn.prefix[0] cn.prefix = cn.prefix[:l] cn.children = nil cn.methodHandler = new(methodHandler) cn.ppath = "" cn.pnames = nil
        cn.addChild(n)
        if l == sl { // At parent node cn.kind = t cn.addHandler(method, h) cn.ppath = ppath cn.pnames = pnames } else { // Create child node n = 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 deeper cn = c continue } // Create child node n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames) n.addHandler(method, h) cn.addChild(n) } else { // Node already exists if h != nil { cn.addHandler(method, h) cn.ppath = ppath if len(cn.pnames) == 0 { // Issue #729 cn.pnames = pnames } } } return }}

        接著我們看下,請(qǐng)求到來(lái)的時(shí)候,參數(shù)匹配的過(guò)程


        func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {  // Acquire context  c := e.pool.Get().(*context)  c.Reset(r, w)
        h := NotFoundHandler
        if 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 chain if err := h(c); err != nil { e.HTTPErrorHandler(err, c) }
        // Release context e.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.RawPath  if path == "" {    path = r.URL.Path  }  return path}

        find是路徑匹配的過(guò)程


        func (r *Router) Find(method, path string, c Context) { ctx := c.(*context) ctx.path = path cn := r.tree // Current node as root
        var ( search = path child *node // Child node n int // Param counter nk kind // Next kind nn *node // Next node ns string // Next search pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice )
        // Search order static > param > any for { if search == "" { break }
        pl := 0 // Prefix length l := 0 // LCP length
        if cn.label != ':' { sl := len(search) pl = len(cn.prefix)
        // LCP max := pl if sl < max { max = sl } for ; l < max && search[l] == cn.prefix[l]; l++ { } }
        if l == pl { // Continue search search = search[l:] } else { cn = nn search = ns if nk == pkind { goto Param } else if nk == akind { goto Any } // Not found return }
        if search == "" { break }
        // Static node if child = cn.findChild(search[0], skind); child != nil { // Save next if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623 nk = pkind nn = cn ns = search } cn = child continue }
        // Param node Param: if child = cn.findChildByKind(pkind); child != nil { // Issue #378 if len(pvalues) == n { continue }
        // Save next if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623 nk = akind nn = cn ns = search }
        cn = child i, l := 0, len(search) for ; i < l && search[i] != '/'; i++ { } pvalues[n] = search[:i] n++ search = search[i:] continue }
        // Any node Any: if cn = cn.findChildByKind(akind); cn == nil { if nn != nil { cn = nn nn = cn.parent // Next (Issue #954) search = ns if nk == pkind { goto Param } else if nk == akind { goto Any } } // Not found return } pvalues[len(cn.pnames)-1] = search break }
        ctx.handler = cn.findHandler(method) ctx.path = cn.ppath ctx.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.ppath ctx.pnames = cn.pnames pvalues[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] = r  printTree(e.router.tree)  return r}

        其中printTree的定義如下

        func printTree(tree *node) {  v1, err1 := json.Marshal(struct {    Kind          kind    Label         byte    Prefix        string    Parent        *node    Children      children    ChildrenNum   int    Ppath         string    Pnames        []string    MethodHandler *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,  })  fmt.Println(string(v1), 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 *Response Path string Pnames []string Pvalues []string Query url.Values //Handler HandlerFunc Store 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()
        // Map if typ.Kind() == reflect.Map { for k, v := range data { val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) } return nil }
        // !struct if 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 = v exists = true break } } }
        if !exists { continue }
        // Call this first, in case we're dealing with an alias to an array type if 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/v4
        func (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()
        // Map if typ.Kind() == reflect.Map { for k, v := range data { val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) } return nil }
        // !struct if 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 tag if _, 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 field continue //注意從哪部移動(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 = v exists = true break } } }
        if !exists { continue }
        // Call this first, in case we're dealing with an alias to an array type if 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 tag if _, 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 field continue //注意從哪部移動(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 


        推薦閱讀


        福利

        我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

        瀏覽 41
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            三级日韩黄片调教 | 久久久久久久久久久久免费 | 国产伦精品毛片一区二区免妓女 | 成人午夜免费视频在线 | 国产精品久久久久久妇女 | 婷婷五月色综合 | 色欲插插 | 亚洲不卡 | 男人天堂新地址 | 美女搞j网站 |