gin03:请求中的参数

4 阅读2分钟

上次我们探讨了gin中的路径参数是如何获取,这次我们将目光转向请求中的参数

示例:解析请求中的参数

func main() {
    router := gin.Default()
    router.GET("/welcome", func(c *gin.Context) {
       firstname := c.DefaultQuery("firstname", "Guest")
       lastname := c.Query("lastname")
       c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
    router.Run(":9090")
}

类似的,我们想知道,在DefaultQuery以及Query中到底发生了什么。

获取参数的底层讨论

func (c *Context) DefaultQuery(key, defaultValue string) string {
    if value, ok := c.GetQuery(key); ok {
       return value
    }
    return defaultValue
}

DefaultQuery中,实际是调用了Query,如果存在对应的key,就直接返回value,否则返回默认值。

func (c *Context) GetQuery(key string) (string, bool) {
    if values, ok := c.GetQueryArray(key); ok {
       return values[0], ok
    }
    return "", false
}

Query中,调用了GetQueryArray方法,如果对应的key,就返回values[0],否则返回空字符串

func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
    c.initQueryCache()
    values, ok = c.queryCache[key]
    return
}

GetQueryArray中,先调用initQueryCache,再从queryCache中获取values

queryCacheContext中的字段,它的类型是url.Values,其本质是

type Values map[string][]string

先查看initQueryCache

func (c *Context) initQueryCache() {
    if c.queryCache == nil {
       if c.Request != nil && c.Request.URL != nil {
          c.queryCache = c.Request.URL.Query()
       } else {
          c.queryCache = url.Values{}
       }
    }
}

如果queryCache是空指针,将会获取Request.URL.Query()的值

func (u *URL) Query() Values {
    v, _ := ParseQuery(u.RawQuery)
    return v
}

在本例中,u.RawQuery的值是firstname=si&lastname=li,将会利用ParseQuery解析这个字符串

func ParseQuery(query string) (Values, error) {
    m := make(Values)
    err := parseQuery(m, query)
    return m, err
}

继续使用parseQuery解析字符串

func parseQuery(m Values, query string) (err error) {
    for query != "" {
       var key string
       key, query, _ = strings.Cut(query, "&")
       if strings.Contains(key, ";") {
          err = fmt.Errorf("invalid semicolon separator in query")
          continue
       }
       if key == "" {
          continue
       }
       key, value, _ := strings.Cut(key, "=")
       key, err1 := QueryUnescape(key)
       if err1 != nil {
          if err == nil {
             err = err1
          }
          continue
       }
       value, err1 = QueryUnescape(value)
       if err1 != nil {
          if err == nil {
             err = err1
          }
          continue
       }
       m[key] = append(m[key], value)
    }
    return err
}

首先通过strings.Cut,将原本的firstname=si&lastname=li,切出一个firstname=si,剩余字符重新保留,再通过strings.Cut将切出firstname=si再次切成firstname以及si

QueryUnescape的作用是将十六进制数解码,如果传输的是中文,keyvalue全是十六进制。

最后将value放在对应的key的切片中,这也实现了参数重名的多值传输解析。