1 介紹
Gin中可以友善的擷取URL中的查詢參數,或者也可以簡稱為URL參數,此類參數以?為起點,後面的k=v&k1=v1&k2=v2這樣的字元串就是查詢參數,本小節通過示例示範擷取URL查詢參數的方式,以及背後的實作方式。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// GET請求 url ?後面是querystring參數,key = value格式,多個key-value用&連接配接
// eg: /queryParams?name=randySun&age=18
r.GET("/queryParams", func(c *gin.Context) {
name := c.Query("name")
age := c.Query("age")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.GET("/queryDefault", func(c *gin.Context) {
name := c.DefaultQuery("name", "body")
age := c.DefaultQuery("age", "18")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.GET("/queryGet", func(c *gin.Context) {
name, ok := c.GetQuery("name")
if !ok {
name = "body"
}
age, ok := c.GetQuery("age")
if !ok {
age = "18"
}
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.Run(":9999")
}
有三種擷取URL查詢參數的方式,分别是Query、DefaultQuery和GetQuery。其中最基本的是Query,針對上面的例子,使用curl測試如下。
curl "127.0.0.1:9999/queryParams?name=TerryPro&age=10 "
{"age":"10","name":"TerryPro"}
2 Query分析
// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
// GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) string {
value, _ := c.GetQuery(key)
return value
}
通過注釋可以看出,Query傳回參數值,如果不存在的話則傳回空字元串""。在内部使用GetQuery擷取具體的參數值。
3 GetQuery分析
// GetQuery is like Query(), it returns the keyed url query value
// if it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns `("", false)`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
func (c *Context) GetQuery(key string) (string, bool) {
if values, ok := c.GetQueryArray(key); ok {
return values[0], ok
}
return "", false
}
GetQuery内部調用GetQueryArray擷取URL查詢參數的值,如果查詢參數存在的話,則傳回(value,true),如果參數值存在但是沒有設定值,則傳回("", true),見注釋中的lastname;如果不存在的話則傳回("",false)。GetQueryArray實際上傳回的是數組,後面會詳細分析。GetQuery僅傳回第一個就行。
4 GetQueryArray分析
// GetQueryArray returns a slice of strings for a given query key, plus
// a boolean value whether at least one value exists for the given key.
func (c *Context) GetQueryArray(key string) ([]string, bool) {
c.initQueryCache()
if values, ok := c.queryCache[key]; ok && len(values) > 0 {
return values, true
}
return []string{}, false
}
func (c *Context) initQueryCache() {
if c.queryCache == nil {
if c.Request != nil {
c.queryCache = c.Request.URL.Query()
} else {
c.queryCache = url.Values{}
}
}
}
type Values map[string][]string
type Context struct {
// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
queryCache url.Values
}
(1)首次調用initQueryCache擷取查詢參數,由于在一個請求中可能會多次調用Query方法是以使用Context中的queryCache對Query參數進行了緩存。
(2)通過源碼可以看出,queryCache的類型是一個鍵類型為字元串,值類型是字元串切片。
(3)initQueryCache調用URL的Query方法對URL進行解析。
5 GetQuery分析
// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
// GET /?name=Manu&lastname=
// c.DefaultQuery("name", "unknown") == "Manu"
// c.DefaultQuery("id", "none") == "none"
// c.DefaultQuery("lastname", "none") == ""
func (c *Context) DefaultQuery(key, defaultValue string) string {
if value, ok := c.GetQuery(key); ok {
return value
}
return defaultValue
}
GetQuery和Query的差別是,如果沒有查詢到相應的參數則直接傳回傳入的參數defaultValue。
6 查詢數組
從上面的分析可以看出Gin還提供了QueryArray和GetQueryArray兩種方法能夠擷取URL中的數組,例子如下:
r.GET("/queryArray", func(c *gin.Context) {
name := c.QueryArray("name")
c.JSON(http.StatusOK, gin.H{
"name": name,
})
})
curl "http://127.0.0.1:9999/queryArray?name=TerryPro&name=Hexiaoyu"
{"name":["TerryPro","Hexiaoyu"]}
可以看出當URL中存在多個重複的參數時,調用QueryArray時可以傳回一個數組切片。第4節對GetQueryArray進行了分析,下面對QueryArray進行簡單的分析。
7 QueryArray分析
// QueryArray returns a slice of strings for a given query key.
// The length of the slice depends on the number of params with the given key.
func (c *Context) QueryArray(key string) []string {
values, _ := c.GetQueryArray(key)
return values
}
QueryArray在内部調用GetQueryArray,如果不存在待查詢的參數,則傳回[]string{}。
8 QueryMap
Context中還提供了2個函數QueryMap、GetQueryMap,用于将Query參數轉化成map的形式,這兩個函數處理的URL查詢參數形式為:?params[key1]=value1¶ms[key2]=value2¶ms[key3]=value3,下面舉個例子,同時對相應的源碼進行分析。
r.GET("/queryMap", func(c *gin.Context) {
params := c.QueryMap("params")
c.JSON(http.StatusOK, gin.H{
"params": params,
})
})
使用如下指令進行測試。
curl "127.0.0.1:9999/queryMap?params\[key1\]=value1¶ms\[key2\]=value2¶ms\[key3\]=value3"
{"params":{"key1":"value1","key2":"value2","key3":"value3"}}
注意在curl指令中發送多個參數使用"",并且需要對[]進行轉移處理。下面對QueryMap和GetQueryMap函數進行分析。
// QueryMap returns a map for a given query key.
func (c *Context) QueryMap(key string) map[string]string {
dicts, _ := c.GetQueryMap(key)
return dicts
}
// GetQueryMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
// 緩存參數到queryCache中,然後對參數進行解析
c.initQueryCache()
return c.get(c.queryCache, key)
}
// get is an internal method and returns a map which satisfy conditions.
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
dicts := make(map[string]string)
exist := false
for k, v := range m {
if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
exist = true
dicts[k[i+1:][:j]] = v[0]
}
}
}
return dicts, exist
}
(1)QueryMap内部調用GetQueryMap,GetQueryMap傳回一個map[string]string的參數值和一個是否成功的辨別;首先仍然解析查詢參數并緩存到queryCache中,調用一個内部的get從queryCache中擷取map參數;
(2)get函數看起來比較複雜,實際上是一個解析方法,針對每一個參數,将其分解為key、'['、mapkey、']'四個部分,比如上面的params[key1]就被分解為params、'['、 key1、 ']'四個字段,比對params成功後,則在dicts中建立一個字段,dicts[key1] = value1。
9 小節
在Gin中接收數組是比較常用的,但是map不常用。其實對于接收參數來說,不光我們可以從URL查詢參數中獲得,還可以從送出的表單(Form)中獲得,它們的原理是大同小異的,使用方式也非常像,下一篇就介紹表單的使用和原理分析。