1

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

oguz ismail
  • 1
  • 16
  • 47
  • 69
rluisr
  • 341
  • 6
  • 16
  • I don't understand what "c.Request.Body is 0" means. c.Request.Body is an io.ReadCloser--`0` is an impossible value for an io.ReadCloser. Can you elaborate on what you mean? Do you mean that the value read from the body is the string `"0"`? Or do you mean that it's 0-bytes long? Or something else? – Jonathan Hall Dec 27 '17 at 10:58
  • @Flimzy sorry `c.Request.Body.Read` not `c.Request.Body` – rluisr Dec 27 '17 at 12:02
  • `c.Request.Body.Read` is a function, not an integer. Are you saying _that_ returns `0` as the number of bytes read? – Jonathan Hall Dec 27 '17 at 12:04
  • @Flimzy yes `fmt.Println(c.Request.Body.Read(body))` – rluisr Dec 27 '17 at 12:41

2 Answers2

9

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.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • 1
    Thanks @hobbs ! solve this problem. I learned a lot from your comment! – rluisr Dec 27 '17 at 12:09
  • If you'd like please teach me how to use `io.LimitedReader`. I don't understand even googled this method. – rluisr Dec 27 '17 at 12:22
  • @rluisr this might help [Limiting amount of data read in the response to a HTTP GET request](https://stackoverflow.com/a/38874756/678491) – k1m190r Dec 27 '17 at 14:22
  • @biosckon thanks comment. this code is return `0`. Do you know what is wrong? https://play.golang.org/p/40c6XQ44ESz – rluisr Dec 28 '17 at 09:28
  • @rluisr if you have solved it, please post answer to your own question. – k1m190r Dec 28 '17 at 11:23
1

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)
        }
    }
Drew H
  • 1,226
  • 1
  • 12
  • 13