• go version: go1.11 darwin/amd64
  • gin version (or commit ref): v1.3.0
  • operating system: Mac OS V10.10.5

Description

Request body disappear on controller if we access it on middleware or two times on general. but this can be fixed if we read the body and then reassign the body again on GetRawData method.

Code

// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import (
    "log"
    "github.com/gin-gonic/gin"
)

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        rawBody, _ := c.GetRawData()
        log.Println("first", string(rawBody))
    }
}

func main() {
    r := gin.New()
    r.Use(Logger())

    r.POST("/test", func(c *gin.Context) {
        // Body disappear on controller
        NrawBody, _ := c.GetRawData()
        log.Println("second", string(NrawBody))
    })

    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}
  • Do a curl request
$ curl -X POST -H "Content-Type: application/json" -d '{"key":"app_name","value":"Beaver"}' "http://localhost:8080/test"
  • The gin log
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /test                     --> main.main.func1 (2 handlers)
[GIN-debug] Listening and serving HTTP on :8080
2018/11/19 10:50:31 first {"key":"app_name","value":"Beaver"}
2018/11/19 10:50:31 second 

Working Code

// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import (
    "log"
    "io/ioutil"
    "bytes"
    "github.com/gin-gonic/gin"
)

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        var bodyBytes []byte
        if c.Request.Body != nil {
          bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
        }
       c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

        log.Println("first", string(bodyBytes))
    }
}

func main() {
    r := gin.New()
    r.Use(Logger())

    r.POST("/test", func(c *gin.Context) {
        // Body disappear on controller
        NrawBody, _ := c.GetRawData()
        log.Println("second", string(NrawBody))
    })

    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}
  • Do a curl request
$ curl -X POST -H "Content-Type: application/json" -d '{"key":"app_name","value":"Beaver"}' "http://localhost:8080/test"
  • The gin log
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /test                     --> main.main.func1 (2 handlers)
[GIN-debug] Listening and serving HTTP on :8080
2018/11/19 11:17:41 first {"key":"app_name","value":"Beaver"}
2018/11/19 11:17:41 second {"key":"app_name","value":"Beaver"}

I am not sure if gin community would like to support that?

Comment From: thinkerou

c.GetRawData() only call one time, it has nothing to do with middleware

Comment From: Clivern

@thinkerou i was thinking it is a good idea to make it accessible multiple times and it can be done BTW. Middlewares most of times used as logging layers to incoming requests but anyway there is an available approach so this issue can be closed.

Comment From: duktig-dev

@Clivern Would be nice id you share the available approach. I'm trying to get Raw request data after c.ShouldBindJSON(...), but as mentioned in documentation, after the binding, the body is not available.

Thanks !

Comment From: Clivern

@duktig-dev I've shared the working example above. So you can store the request body on a local variable then use c.ShouldBindJSON(...) then assign it back ... etc

import (
    "io/ioutil"
    "bytes"
)

r.POST("/test", func(c *gin.Context) {
    var bodyBytes []byte
    if c.Request.Body != nil {
      bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
    }

    // Access Request Body
    c.ShouldBindJSON(...)

    // Assign Back the request body
    c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

    // Access Request Body Again
    c.GetRawData()
})

Comment From: duktig-dev

@Clivern Thank you!