Golang Gin 優雅地解析JSON請求數據(ShouldBindBodyWith避免出現EOF錯誤)

JSON是前後端交互的重要數據類型之一,使用Gin Web框架可以很方便地將HTTP請求報文中JSON格式的Body數據解析到結構體Struct字典Map數據結構中。

Golang Gin 優雅地解析JSON請求數據

環境

go version go1.14.3 windows/amd64
github.com/gin-gonic/gin v1.6.3

1. 結論

參考 Fix #216: Enable to call binding multiple times in some formats #1341

使用場景 函數
單次綁定 ShouldBindJSON > BindJSON
多次綁定⭐ ShouldBindBodyWith

ShouldBindJSON方法是最常用解析JSON數據的方法之一,但在重複調用的情況下會出現EOF的報錯,這個原因出在ShouldBindJSON在調用過一次之後context.request.body.sawEOF的值是false導致,所以如果要多次綁定多個變量,需要使用ShouldBindBodyWith

至於爲什麼單次綁定不優選使用BindJSON方法,主要因爲BindJSON方法會強制拋出錯誤,影響正常流程。

以下爲範例:

// 以下是用於存儲JSON數據的結構體
type MsgJson struct {
	Msg string `json:"msg"`
}

// 單次綁定使用 ShouldBindJSON 方法
func bindExample(c *gin.Context) {
    // ---> 聲明結構體變量
	var a MsgJson
	
	// ---> 綁定數據
	if err := c.ShouldBindJSON(&a); err != nil {
		c.AbortWithStatusJSON(
			http.StatusInternalServerError,
			gin.H{"error": err.Error()})
		return
	}
	
	// --> 返回
	c.JSON(http.StatusOK, gin.H{"msg": "ok"})
	return
}

// 多次綁定優先使用 ShouldBindBodyWith 方法
func bindWithRightWay(c *gin.Context) {
    // ---> 聲明兩個結構體變量用於存儲JSON數據
	var a, b MsgJson
	
	// ---> 第一次解析(注意第二個參數是 binding.JSON)
	if err := c.ShouldBindBodyWith(&a, binding.JSON); err != nil {
		c.AbortWithStatusJSON(
			http.StatusInternalServerError,
			gin.H{"error": err.Error()})
		return
	}
	
	// ---> 第二次解析
	if err := c.ShouldBindBodyWith(&b, binding.JSON); err != nil {
		c.AbortWithStatusJSON(
			http.StatusInternalServerError,
			gin.H{"error": err.Error()})
		return
	}
	
	// ---> 返回
	c.JSON(http.StatusOK, gin.H{"msg": "ok"})
	return
}

2. EOF錯誤復現

EOF錯誤出現在第二次使用ShouldBindJSON方法,在多次綁定的情況下,優先使用ShouldBindBodyWith,以下爲❌錯誤示範:

type MsgJson struct {
	Msg string `json:"msg"`
}

func main() {
	r := gin.Default()
	r.POST("/v2", bindWithError)
	_ = r.Run("127.0.0.1:9001")
}

func bindWithError(c *gin.Context) {
	var a, b MsgJson
	if err := c.ShouldBindJSON(&a); err != nil {....}
	
	// ---> 注意,這裏會出現EOF報錯
	if err := c.ShouldBindJSON(&b); err != nil {....}
    .......
	return
}

Postman測試結果:
postman測試結果
Goland斷點調試:
goland斷點調試
request.body.sawEOF


3. ShouldBindBodyWith 源碼分析

ShouldBindBodyWithShouldBindWith很像,但它保存了requestsBody到上下文,允許Body被繼續調用。
注意:這個方法會先讀取Body然後綁定,如果只綁定一次,建議使用ShouldBindWith來獲得更好的性能(因爲後者會直接讀取並寫到指定變量,而沒有寫入上下文)。

func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {
	var body []byte
	// ---> 先看上下文是否已經有Body的Bytes數據
	if cb, ok := c.Get(BodyBytesKey); ok {
		if cbb, ok := cb.([]byte); ok {
			body = cbb
		}
	}
	// ---> 如果Body不爲空的情況下,讀取Body數據並寫入上下文
	if body == nil {
		body, err = ioutil.ReadAll(c.Request.Body)
		if err != nil {
			return err
		}
		c.Set(BodyBytesKey, body)
	}
	return bb.BindBody(body, obj)
}

流程圖

檢查上下文是否有Body的Bytes數據
讀取Request中Body的數據
返回上下文中Body的Bytes數據
綁定到指定的變量
將Body的Bytes數據添加到上下文
沒有
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章