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
- go version: go version go1.22.6 windows/amd64
- gin version (or commit ref): v1.10.0
- operating system: windows 11
Comment From: k8scommander
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.
Comment From: JimChenWYU
There is an extra space.
Comment From: rcarrion2
I have the same problem. I had to downgrade to 1.9.1.
Comment From: blkcor
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
Comment From: RedCrazyGhost
@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:
- ctx.ShouldBindBodyWithJSON(&body)
- ctx.ShouldBindBodyWithXML(&body)
- ctx.ShouldBindBodyWithYAML(&body)
- ctx.ShouldBindBodyWithTOML(&body)
https://github.com/gin-gonic/gin/blob/3cb30679b5e3021db16c776ed7e70d380586e9ce/context.go#L777-L817
Comment From: HHC26
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 }