gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
78.79k stars 8.02k forks source link

[Bug] EOF when using ShouldBindJSON #4035

Open victor-gapeev opened 2 months ago

victor-gapeev commented 2 months ago

Description

When I use ctx.ShouldBindJSON(&body), it quietly close request without any response. Other requests with same method works normally.

How to reproduce

Model:

type FastEnterDto struct {
    UUID     string `json:"uuid" binding:"required, max=256"`
    DeviceId int    `json:"deviceId" binding:"required"`
}

Handler:

group.POST("fastenter", func(ctx *gin.Context) {
        var body entities.FastEnterDto

        fmt.Println("start bind")
        err := ctx.ShouldBindJSON(&body)
        fmt.Println("end bind, err", err.Error())
        if err != nil {
            ctx.AbortWithError(http.StatusBadRequest, ErrBadRequest)
            return
        }
        ...
}

Expectations

Correct binding.

Actual result

When I`m making a request, I get the next log:

start bind

BUT, if I add lines to print the response as a string before binding, like this:

group.POST("fastenter", func(ctx *gin.Context) {
        s, _ := ioutil.ReadAll(ctx.Request.Body)
        fmt.Println(string(s))
        var body entities.FastEnterDto
        ...

I got:

{"uuid":"qCXDPm2qbtuyHNUrlSi80","deviceId":725087484513740}
start bind
end bind, err EOF

I'm sure I don't bind body twice anywhere. I use only cors and log middlewares. This behavior doesn't make any sense and strongly needs explanation.

My code which send requests:

const response = await fetch(baseUrl + path, {
        method: "POST",
        body: JSON.stringify(body),
        credentials: "include",
        headers: {
            "Content-Type": "application/json",
        },
})

Environment

k8scommander commented 2 months ago

Trying to reproduce it, for this part:

        err := ctx.ShouldBindJSON(&body)
        fmt.Println("end bind, err", err.Error())

if err == nil, printing err.Error() will produce an exception

For the second part: s, _ := ioutil.ReadAll(ctx.Request.Body) reading the request body will prevent you from binding at as it's already been read.

JimChenWYU commented 2 months ago

image

There is an extra space.

rcarrion2 commented 2 months ago

I have the same problem. I had to downgrade to 1.9.1.

blkcor commented 2 months ago

Trying to reproduce it, for this part:

        err := ctx.ShouldBindJSON(&body)
        fmt.Println("end bind, err", err.Error())

if err == nil, printing err.Error() will produce an exception

For the second part: s, _ := ioutil.ReadAll(ctx.Request.Body) reading the request body will prevent you from binding at as it's already been read.

Yes, that is.I think there is no bug actually

RedCrazyGhost commented 2 months ago

@victor-gapeev There may be some logical problems with the code you write, and request.go explains Body io.ReadCloser and has a rudimentary understanding of its use

https://github.com/golang/go/blob/fc9f02c7aec81bcfcc95434d2529e0bb0bc03d66/src/net/http/request.go#L174-L196

@rcarrion2 @victor-gapeev If you need to use the data in the Body repeatedly, you can use methods that support caching the Body

Methods:

https://github.com/gin-gonic/gin/blob/3cb30679b5e3021db16c776ed7e70d380586e9ce/context.go#L777-L817

HHC26 commented 1 month ago

IF gin automatically implements binding routing, parameter verification, and generating swaggers, which can reduce a lot of workload when developing the web

type UserSearchReq struct { g.Meta path:"/user/list" tags:"sysUser" method:"get" summary:"xxx" DeptId string p:"deptId" RoleId uint p:"roleId" Status string p:"status" } type UserSearchRes struct { g.Meta mime:"application/json" UserList []*model.SysUserRoleDeptRes json:"userList" }

type UserAddReq struct { g.Meta path:"/user/add" tags:"sysUser" method:"post" summary:"xxx" UserName string p:"userName" v:"required#User account cannot be empty" Password string p:"password" v:"required|password#PWD account cannot be empty" UserSalt string } type UserAddRes struct { }

func GetUserList(c gin.Context, req UserSearchReq) (res *UserSearchRes, err error) { // todo }

func UserAddReq(c gin.Context, req UserAddReq) (res *UserAddRes, err error) { // todo }