上次我们探讨了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
queryCache是Context中的字段,它的类型是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的作用是将十六进制数解码,如果传输的是中文,key和value全是十六进制。
最后将value放在对应的key的切片中,这也实现了参数重名的多值传输解析。