rluisr
rluisr

Reputation: 351

Gin If `request body` bound in middleware, c.Request.Body become 0

My API server has middle ware which is getting token from request header. If it access is correct, its go next function.

But request went to middle ware and went to next function, c.Request.Body become 0.

middle ware

func getUserIdFromBody(c *gin.Context) (int) {
    var jsonBody User

    length, _ := strconv.Atoi(c.Request.Header.Get("Content-Length"))
    body := make([]byte, length)
    length, _ = c.Request.Body.Read(body)
    json.Unmarshal(body[:length], &jsonBody)

    return jsonBody.Id
}

func CheckToken() (gin.HandlerFunc) {
    return func(c *gin.Context) {
        var userId int

        config := model.NewConfig()

        reqToken := c.Request.Header.Get("token")

        _, resBool := c.GetQuery("user_id")
        if resBool == false {
            userId = getUserIdFromBody(c)
        } else {
            userIdStr := c.Query("user_id")
            userId, _ = strconv.Atoi(userIdStr)
        }
    ...
        if ok {
            c.Nex()
            return
        }
}

next func

func bindOneDay(c *gin.Context) (model.Oneday, error) {
    var oneday model.Oneday

    if err := c.BindJSON(&oneday); err != nil {
        return oneday, err
    }
    return oneday, nil
}

bindOneDay return error with EOF. because maybe c.Request.Body is 0.

I want to get user_id from request body in middle ware. How to do it without problem that c.Request.Body become 0

Upvotes: 1

Views: 4024

Answers (2)

Drew H
Drew H

Reputation: 1292

Gin provides a native solution to allow you to get data multiple times from c.Request.Body. The solution is to use c.ShouldBindBodyWith. Per the gin documentation

ShouldBindBodyWith ... stores the request body into the context, and reuse when it is called again.

For your particular example, this would be implemented in your middleware like so,

func getUserIdFromBody(c *gin.Context) (int) {
    var jsonBody User

    if err := c.ShouldBindBodyWith(&jsonBody, binding.JSON); err != nil {
        //return error
    }

    return jsonBody.Id
}

After the middleware, if you want to bind to the body again, just use ctx.ShouldBindBodyWith again. For your particular example, this would be implemented like so

func bindOneDay(c *gin.Context) (model.Oneday, error) {
    var oneday model.Oneday

    if err := c.ShouldBindBodyWith(&oneday); err != nil {
        return error
    }
    return oneday, nil
}

The issue we're fighting against is that gin has setup c.Request.Body as an io.ReadCloser object -- meaning that it is intended to be read from only once. So, if you access c.Request.Body in your code at all, the bytes will be read (consumed) and c.Request.Body will be empty thereafter. By using ShouldBindBodyWith to access the bytes, gin saves the bytes into another storage mechanism within the context, so that it can be reused over and over again.

As a side note, if you've consumed the c.Request.Body and later want to access c.Request.Body, you can do so by tapping into gin's storage mechanism via ctx.Get(gin.BodyBytesKey). Here's an example of how you can obtain the gin-stored Request Body as []byte and then convert it to a string,

    var body string
    if cb, ok := ctx.Get(gin.BodyBytesKey); ok {
        if cbb, ok := cb.([]byte); ok {
            body = string(cbb)
        }
    }

Upvotes: 1

hobbs
hobbs

Reputation: 239881

You can only read the Body from the client once. The data is streaming from the user, and they're not going to send it again. If you want to read it more than once, you're going to have to buffer the whole thing in memory, like so:

bodyCopy := new(bytes.Buffer)
// Read the whole body
_, err := io.Copy(bodyCopy, req.Body)
if err != nil {
    return err
}
bodyData := bodyCopy.Bytes()
// Replace the body with a reader that reads from the buffer
req.Body = ioutil.NopCloser(bytes.NewReader(bodyData))
// Now you can do something with the contents of bodyData,
// like passing it to json.Unmarshal

Note that buffering the entire request into memory means that a user can cause you to allocate unlimited memory -- you should probably either block this at a frontend proxy or use an io.LimitedReader to limit the amount of data you'll buffer.

You also have to read the entire body before Unmarshal can start its work -- this is probably no big deal, but you can do better using io.TeeReader and json.NewDecoder if you're so inclined.

Better, of course, would be to figure out a way to restructure your code so that buffering the body and decoding it twice aren't necessary.

Upvotes: 10

Related Questions