当使用gin.Context.ShouldBind()时,对于一个有效的JSON,出现EOF错误。

huangapple go评论109阅读模式
英文:

Getting EOF for a valid json when doing a gin.Context.ShouldBind()

问题

我遇到了一个之前没有出现过的奇怪错误,可能是由于我没有彻底测试的JWT实现引起的。所以我有以下的代码:

ginGine.POST("/edge_device_info", func(ctx *gin.Context) {
    reqMap := helpers.GetJSONFromReqBody(ctx.Request.Body, ctx)
    accessToken := fmt.Sprintf("%v", reqMap["access_token"])

    tokenValid, jwtErr := srvHelpers.ValidateJWTToken(accessToken)

    if jwtErr != nil || !tokenValid {
        fmt.Println("invalid token")
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error0": jwtErr.Error(),
        })
        return
    }
    
    // reqBody, err := json.Marshal(reqMap)
    // if err != nil {
    //     ctx.JSON(http.StatusBadRequest, gin.H{
    //         "error10": err.Error(),
    //     })
    //     return
    // }

    var edge_device_info models.EdgeDeviceInfo

    // err = json.Unmarshal(reqBody, &edge_device_info)
    err := ctx.ShouldBind(&edge_device_info)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error1": err.Error(),
        })
        return
    }

    // more code
})

这个API端点接收到类似的json数据:

{
    "name": "一些文本",
    "description": "另一些文本",
    "access_token": "SOME_JWT_TOKEN"
}

现在,这个json看起来没问题,对吧?但是ShouldBind()不喜欢它,会返回一个EOF错误。如果你检查代码的顶部部分,我将接收到的JSON体转换为一个map,然后使用access_token(一个JWT令牌)来验证用户的访问权限。我不太确定这个过程是否会导致这个问题,这就是我想知道的。这就是我提出这个问题的原因。我已经找到了解决方法,就是上面被注释掉的代码。这很奇怪,因为首先创建map意味着请求体的JSON是有效的。

英文:

I'm getting a weird error which previously didn't appear for my code, which might be caused by my JWT implementation that I didn't test thoroughly tbh. So I have this current code:

ginGine.POST("/edge_device_info", func(ctx *gin.Context) {
	reqMap := helpers.GetJSONFromReqBody(ctx.Request.Body, ctx)
	accessToken := fmt.Sprintf("%v", reqMap["access_token"])

	tokenValid, jwtErr := srvHelpers.ValidateJWTToken(accessToken)

	if jwtErr != nil || !tokenValid {
		fmt.Println("invalid token")
		ctx.JSON(http.StatusBadRequest, gin.H{
			"error0": jwtErr.Error(),
		})
		return
	}
	
	// reqBody, err := json.Marshal(reqMap)
	// if err != nil {
	// 	ctx.JSON(http.StatusBadRequest, gin.H{
	// 		"error10": err.Error(),
	// 	})
	// 	return
	// }

	var edge_device_info models.EdgeDeviceInfo

	// err = json.Unmarshal(reqBody, &edge_device_info)
	err := ctx.ShouldBind(&edge_device_info)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{
			"error1": err.Error(),
		})
		return
	}

	// more code
})

This API endpoint gets sent this similar looking json:

{
    "name": "Some text",
    "description": "Another bunch of text",
    "access_token": "SOME_JWT_TOKEN"
}

Now, this json looks fine, right? But ShouldBind() doesn't like that and would return an EOF. If you check the top part of my code, I'm converting the received JSON body to a map then use the access_token, which is a JWT token, to validate the user's access. I'm not quite sure if that process somehow made that issue, and that's what I wanted to know. That's the reason I made this question. I already found the workaround, which is the commented code above. Which is quite weird as creating the map in the first place means that the request body's JSON was valid.

答案1

得分: 1

正如@Zeke Lu所指出的那样,你应该在gin.Context结构体上使用ShouldBindBodyWith方法。我为你编写了一个小例子供你使用。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)

type Todo struct {
	Description string `json:"description" binding:"required"`
}

func Todos(c *gin.Context) {
	// 第一次读取请求体
	var todo Todo
	if err := c.ShouldBindBodyWith(&todo, binding.JSON); err != nil {
		c.JSON(http.StatusBadRequest, err)
		return
	}
	fmt.Println("第一次:", todo)

	// 再次读取请求体
	var todoSecondTime Todo
	if err := c.ShouldBindBodyWith(&todoSecondTime, binding.JSON); err != nil {
		c.JSON(http.StatusBadRequest, err)
		return
	}
	fmt.Println("第二次:", todoSecondTime)
}

func main() {
	gin.SetMode(gin.DebugMode)
	r := gin.Default()

	r.POST("/todos", Todos)

	r.Run(":8080")
}

这段代码非常简单,但它的目的只是提供一个有价值的示例。如果你需要多次读取请求体,就像你的场景一样,你应该依赖这个方法,因为它会将请求的负载复制到上下文中。由于这个原因,后续的调用将再次使用该请求体流进行绑定。

请注意,如果你只需要读取一次请求体,出于性能原因,你应该依赖ShouldBindWith方法。

如果这个例子解决了你的疑问,请告诉我,谢谢!

英文:

As correctly pointed out by @Zeke Lu, you should use the method ShouldBindBodyWith on the gin.Context struct. I've written a small example for you to use.

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)

type Todo struct {
	Description string `json:"description" binding:"required"`
}

func Todos(c *gin.Context) {
	// read the body the first time
	var todo Todo
	if err := c.ShouldBindBodyWith(&todo, binding.JSON); err != nil {
		c.JSON(http.StatusBadRequest, err)
		return
	}
	fmt.Println("first time:", todo)

	// read the body again
	var todoSecondTime Todo
	if err := c.ShouldBindBodyWith(&todoSecondTime, binding.JSON); err != nil {
		c.JSON(http.StatusBadRequest, err)
		return
	}
	fmt.Println("second time:", todoSecondTime)
}

func main() {
	gin.SetMode(gin.DebugMode)
	r := gin.Default()

	r.POST("/todos", Todos)

	r.Run(":8080")
}

The code is pretty simple but it's purpose is to just provide a valuable example. If you're going to read the body more than once, like in your scenario, you should rely on this method as it does a copy of the request payload into the context. Thanks to this, the subsequent calls will use that body stream for binding it again.
> Please note that, if you're going to read the body only once, you should rely on the ShouldBindWith method due to performance reasons.

Let me know if this example clarifies your doubts, thanks!

huangapple
  • 本文由 发表于 2023年6月13日 15:58:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76462803.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定