Gin: Use BindJSON multiple times fails

Created on 24 Aug 2017  路  11Comments  路  Source: gin-gonic/gin

When using BindJSON multiple times in a single request handler like below it fails the second time because the c.Request.Body has already been read the first time and can't be read a second time.

var b interface{}
err := c.BindJSON(&b)
...
var requestBody RequestBody
err = c.BindJSON(&requestBody)

I do this to be able to log the full structure of a request body coming into my application so I can refine my RequestBody struct one property at a time. Also when unMarshalling fails I log the origin body as a string, but using the body ReadCloser fails if BindJSON already ran.

A workaround is to go back to the non-gin way of reading the body first

body, err := ioutil.ReadAll(c.Request.Body)
...
var b interface{}
err = json.Unmarshal(body, &b)
...
var requestBody RequestBody
err = json.Unmarshal(body, &requestBody)

But that obviously isn't very nice.

According to https://stackoverflow.com/questions/31884093/read-multiple-time-a-reader using a io.TeeReader in your code would solve the issue. Is that sensible or too slow? Any other options?

Most helpful comment

All 11 comments

I think this could easily be done by changing line https://github.com/gin-gonic/gin/blob/ce670a64977a513d8caa58c143471ddbbf641bd4/binding/json.go#L24 to

b := bytes.NewBuffer(make([]byte, 0))
reader := io.TeeReader(req.Body, b)
decoder := json.NewDecoder(reader)

Sorry, using TeeReader as above doesn't actually work. Being a golang newbie I misunderstood how it actually works.

If Bind changed the following code:

+var o interface{}
+
 func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
        decoder := json.NewDecoder(req.Body)
        if EnableDecoderUseNumber {
                decoder.UseNumber()
        }
+       if o != nil {
+               return validate(o)
+       }
        if err := decoder.Decode(obj); err != nil {
                return err
        }
+       o = obj
        return validate(obj)
 }

It will solve the issue, but I don't know the method is OK?

@thinkerou I don't think it can work. Async call will obfuscate variable o.

@JorritSalverda I think you can read once then set to ctx, and when u need it, just read it from ctx.

Yes, create a middleware that executes before your request handler. Take your json bytes, and set it in the *gin.Context. When your handler exits, your middlware can continue and you can pull it from the context.

Any update? I have the same problem

@lishuhao yeah, this solved, but maybe it's not the right way. You can't guarantee every middleware using NopCloser to write back. Try to define a []byte in context and reuse it maybe better.

please use ShouldBindBodyWith

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iiinsomnia picture iiinsomnia  路  3Comments

mastrolinux picture mastrolinux  路  3Comments

kekemuyu picture kekemuyu  路  3Comments

lilee picture lilee  路  3Comments

olegsobchuk picture olegsobchuk  路  3Comments