Go Web輕量級框架Gin學習系列:資料繫結

張君鴻發表於2019-04-25

前面寫了兩篇與Gin框架學習有關的文章,主要講了Gin框架的安裝,定義處理HTTP請求的各種方法以及如何根據客戶端需求返回不同格式的資料,但這中間漏了一個環節,那就是返回資料之前,如何獲取客戶端HTTP請求中帶上來的引數,關於這點,我們就在這篇文章中講一講。

Gin框架將處理HTTP請求引數以及如何響應等操作都封裝到了gin.Conetxt結構體,併為gin.Context提供了非常多的方法,因此瞭解gin.Context的結構定義與方法,對使用Gin框架編寫Web專案非常重要。

下面是gin.Context結構定義程式碼:

type Context struct {
    Request *http.Request
    Writer  ResponseWriter
    Params Params
    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}
    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs
    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string
    // contains filtered or unexported fields
}
複製程式碼

從上面的gin.Context的結構定義來看,gin.Context封裝了http.Requesthttp.ResponseWriter

獲取請求引數

1. Path

path是指請求的url中域名之後從/開始的部分,如掘金首頁地址:https://juejin.im/timeline/timeline部分便是path,可以使用gin.Context中的Param()方法獲取這部分引數。

func (c *Context) Param(key string) string
複製程式碼

使用Param()方法獲取path中的引數:

r.GET("/user/:id",func(c *gin.Context){
    id := c.Param("id")
})
複製程式碼

除了使用gin.Context的中Param()方法外,還可以用gin.Context中的Params欄位獲取到path中的引數,Params的定義如下:

type Params []Param
func (ps Params) ByName(name string) (va string)
func (ps Params) Get(name string) (string, bool)
複製程式碼

使用gin.Context中的Params欄位獲取path中的引數示例如下:

r.GET("/user/:id",func(c *gin.Context){
    id,err := c.Params.Get("id")
    //id := c.Params.ByName("id")
})
複製程式碼

2. Query

query是指url請求地址中的問號後面的部,稱為查詢引數,如下面地址中,query=%E6%96%87%E7%AB%A0&type=all就是查詢引數。

https://juejin.im/search?query=%E6%96%87%E7%AB%A0&type=all
複製程式碼

gin.Context提供了以下幾個方法,用於獲取Query部分的引數。

1. 獲取單個引數
func (c *Context) GetQuery(key string) (string, bool)
func (c *Context) Query(key string) string
func (c *Context) DefaultQuery(key, defaultValue string) string
複製程式碼

上面三個方法用於獲取單個數值,GetQueryQuery多返回一個error型別的引數,實際上Query方法只是封裝了GetQuery方法,並忽略GetQuery方法返回的錯誤而已,而DefaultQuery方法則在沒有獲取相應引數值的返回一個預設值。

示例如下:

r.GET("/user", func(c *gin.Context) {
    id,_ := c.GetQuery("id")
    //id := c.Query("id")
    //id := c.DefaultQuery("id","10")
    c.JSON(200,id)
})
複製程式碼

請求:http://localhost:8080/user?id=11

響應:11

2. 獲取陣列

GetQueryArray方法和QueryArray的區別與GetQuery和Query的相似。

func (c *Context) GetQueryArray(key string) ([]string, bool)
func (c *Context) QueryArray(key string) []string

複製程式碼

示例如下:

r.GET("/user", func(c *gin.Context) {
    ids := c.QueryArray("id")
    //id,_ := c.QueryArray("id")
    c.JSON(200,ids)
})
複製程式碼

請求:http://localhost:8080/user?id=10&id=11&id=12

響應:["10","11","12"]

3. 獲取map

GetQueryArray方法和QueryArray的區別與GetQuery和Query的相似。

func (c *Context) QueryMap(key string) map[string]string
func (c *Context) GetQueryMap(key string) (map[string]string, bool)
複製程式碼

示例如下:

r.GET("/user", func(c *gin.Context) {
    ids := c.QueryMap("ids")
    //ids,_ := c.GetQueryMap("ids")
    c.JSON(200,ids)
})
複製程式碼

請求:http://localhost:8080/user?ids[10]=zhang

響應:{"10":"zhang"}

3. Body

一般HTTP的Post請求引數都是通過body部分傳給伺服器端的,尤其是資料量大或安全性要求較高的資料,如登入功能中的賬號密碼等引數。

gin.Context提供了以下四個方法讓我們獲取body中的資料,不過要說明的是,下面的四個方法,只能獲取Content-typeapplication/x-www-form-urlencodedmultipart/form-databody中的資料。

下面方法的使用方式與上面獲取Query的方法使用型別,區別只是資料來源不同而已,這裡便不再寫示例程式。

func (c *Context) PostForm(key string) string
func (c *Context) PostFormArray(key string) []string
func (c *Context) PostFormMap(key string) map[string]string
func (c *Context) DefaultPostForm(key, defaultValue string) string
func (c *Context) GetPostForm(key string) (string, bool)
func (c *Context) GetPostFormArray(key string) ([]string, bool)
func (c *Context) GetPostFormMap(key string) (map[string]string, bool)
func (c *Context) GetRawData() ([]byte, error)
複製程式碼

資料繫結

在前面的例子中,我們直接使用gin.Context提供的方法獲取請求中通過pathquerybody帶上來的引數,但使用前面的那些方法,並不能處理請求中比較複雜的資料結構,比如Content-type為application/json或application/xml時,其所帶上的資料會很複雜,因此我們需要使用另外一種方法獲取這些資料,這種方式叫資料繫結

Gin框架將資料繫結的操作都封裝在gin/binding這個包中,下面是gin/binding包定義的常量,說明gin/binding包所支援的Content-type格式。

const (
    MIMEJSON              = "application/json"
    MIMEHTML              = "text/html"
    MIMEXML               = "application/xml"
    MIMEXML2              = "text/xml"
    MIMEPlain             = "text/plain"
    MIMEPOSTForm          = "application/x-www-form-urlencoded"
    MIMEMultipartPOSTForm = "multipart/form-data"
    MIMEPROTOBUF          = "application/x-protobuf"
    MIMEMSGPACK           = "application/x-msgpack"
    MIMEMSGPACK2          = "application/msgpack"
    MIMEYAML              = "application/x-yaml"
)
複製程式碼

gin.binding包也定義處理不同Content-type提交資料的處理結構體,並以變數的形式讓其他包可以訪問,如下:

var (
    JSON          = jsonBinding{}
    XML           = xmlBinding{}
    Form          = formBinding{}
    Query         = queryBinding{}
    FormPost      = formPostBinding{}
    FormMultipart = formMultipartBinding{}
    ProtoBuf      = protobufBinding{}
    MsgPack       = msgpackBinding{}
    YAML          = yamlBinding{}
    Uri           = uriBinding{}
)
複製程式碼

但實際上,我們並不需要呼叫gin/binding包的程式碼來完成資料繫結的功能,因為gin.Context中已經在gin.Context的基礎上封裝了許多更加快捷的方法供我們使用:

gin.Context封裝的相關繫結方法,分為以Bind為字首的系列方法和以ShouldBind為字首的系列方法,這兩個系列方法之間的差別在於以Bind為字首的方法,在使用者輸入資料不符合相應格式時,會直接返回http狀態為400的響應給客戶端。

以Bind為字首的系列方法

1. Path
func (c *Context) BindUri(obj interface{}) error
複製程式碼

程式碼示例:

type User struct {
    Uid      int    //使用者id
    Username string //使用者名稱
}
func main() {
    r := gin.Default()
    r.GET("/bind/:uid/username", func(c *gin.Context) {
        var u User
        e := c.BindUri(&u)
        if e == nil{
            c.JSON(200,u)
        }
    })
    r.Run()
}

複製程式碼

請求:http://localhost:8080/bind/1/小張

輸入:{1,"小張"}

2. Query
func (c *Context) BindQuery(obj interface{}) error
複製程式碼

程式碼示例:

r.GET("/bind/:uid/username", func(c *gin.Context) {
    var u User
    e := c.BindQuery(&u)
    if e == nil{
        c.JSON(200,u)
    }
})
複製程式碼

請求:http://localhost:8080/bind?uid=1&username=小張

輸出:{1,"小張"}

3. Body

當我們在HTTP請求中Body設定不同資料格式,需要設定相應頭部Content-Type的值,比較常用為jsonxmlyamlgin.Context提供下面三個方法繫結對應Content-type時body中的資料。

func (c *Context) BindJSON(obj interface{}) error
func (c *Context) BindXML(obj interface{}) error
func (c *Context) BindYAML(obj interface{}) error
複製程式碼

除了上面三個方法外,更常用的Bind()方法,Bind()方法會自動根據Content-Type的值選擇不同的繫結型別。

func (c *Context) Bind(obj interface{}) error
複製程式碼

示例

r.POST("bind",func(c *gin.Context){
    u := User{}
    c.Bind(&u)
})
複製程式碼

上面幾個方法都是獲取固定Content-type或自動根據Content-type選擇繫結型別,我們也可以使用下面兩個方法自行選擇繫結型別。

下面兩個方法的第二個引數值是gin.binding中定義好的常量,我們在上面講過。

func (c *Context) BindWith(obj interface{}, b binding.Binding) error
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error
複製程式碼

示例

r.POST("bind",func(c *gin.Context){
	u := User{}
	c.BindWith(&u,binding.JSON)
    c.MustBindWith(&u,binding.JSON)
})
複製程式碼

以ShouldBind為字首的系列方法

以ShouldBind為字首的相應的方法與以Bind為字首的方法使用基本相同,因此下面沒有相應演示的程式碼。

1. Path
func (c *Context) ShouldBindUri(obj interface{}) error
複製程式碼
2. Query
func (c *Context) ShouldBindQuery(obj interface{}) error
複製程式碼
3. Body
func (c *Context) ShouldBind(obj interface{}) error
func (c *Context) ShouldBindJSON(obj interface{}) error
func (c *Context) ShouldBindXML(obj interface{}) error
func (c *Context) ShouldBindYAML(obj interface{}) error
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error)
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error
複製程式碼

小結

Gin框架在net/http包的基礎上封裝了許多的方法,讓我們可以接收客戶端傳遞上來的各種不同格式的資料,但是從客戶端得到的資料之後,還是要驗證資料是否合法或是否我們想要的,這是Gin框架中有關資料驗證器的知識了,有機會再寫寫這方面的文章。

相關文章