7

I am trying to restore the context with it's data after performing validation on it's data.I need the data to keep moving as need it later on in the next function.

I am new to golang and the below code is as far I could go. any help and a better approach is much appreciated.

thanks in advance.

the validation middleware

func SignupValidator(c *gin.Context) {
    // Read the Body content
    // var bodyBytes []byte
    // if c.Request.Body != nil {
    //  bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
    // }
    var user entity.User
    if err := c.ShouldBindJSON(&user); err != nil {
         validate := validator.New()
         if err := validate.Struct(&user); err != nil {
              c.JSON(http.StatusBadRequest, gin.H{
                 "error": err.Error(),
          })
          c.Abort()
          return
        }
        // c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
    }
    // Read the Body content
    var bodyBytes []byte
    if c.Request.Body != nil {
        bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
    }
    fmt.Println(string(bodyBytes)) // this empty
    c.Next()

}

route

auth.POST("login", gin.Logger(), validations.SignupValidator, func(ctx *gin.Context) {
            ctx.JSON(200, videoController.Save(ctx))
        })
Roman Kiselenko
  • 43,210
  • 9
  • 91
  • 103
bihire boris
  • 1,530
  • 3
  • 19
  • 44

3 Answers3

21

You can try this.

ByteBody, _ := ioutil.ReadAll(c.Request.Body)
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(ByteBody))

You can then use ByteBody however you want without side-effects on c.Request.Body

Mic Fok
  • 837
  • 11
  • 12
spehlivan
  • 223
  • 3
  • 6
  • Make sure to handle the ioutil.ReadAll's error. Using underscore is not recommended in Go, because byteBody can be nil. and the server might panic. – Musab Gultekin Dec 14 '22 at 18:14
15

Here is an example of reading body twice with ShouldBindBodyWith, check it:

package main

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
)

type ParamsOne struct {
    Username string `json:"username"`
}

type ParamsTwo struct {
    Username string `json:"username"`
}

func main() {
    r := gin.New()
    r.POST("/", func(c *gin.Context) {
        var f ParamsOne
        // Read ones
        if err := c.ShouldBindBodyWith(&f, binding.JSON); err != nil {
            log.Printf("%+v", err)
        }
        log.Printf("%+v", f)
        var ff ParamsTwo
        
        if err := c.ShouldBindBodyWith(&ff, binding.JSON); err != nil {
            log.Printf("%+v", err)
        }
        log.Printf("%+v", ff)
        c.IndentedJSON(http.StatusOK, f)
    })
    r.Run(":4000")
}

Output:

$example: ./example
[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   /                         --> main.main.func1 (1 handlers)
[GIN-debug] Listening and serving HTTP on :4000
2020/07/05 10:47:03 {Username:somename}
2020/07/05 10:47:03 {Username:somename}
Roman Kiselenko
  • 43,210
  • 9
  • 91
  • 103
  • 4
    that what I am trying to say, it won't work. because you can't read buffers twice, and you already did read it with `ShouldBindJSON`. try to run that if possible to see if it prints something, because it doesn't on my side. – bihire boris Jul 05 '20 at 07:33
  • 4
    To other readers: `ShouldBindJSON` and `ShouldBindBodyWith` behave differently. You can't call `ShouldBindJSON` twice, but `ShouldBindBodyWith` won't run into the same issue. If you really do need to manually read the body twice, @spehlivan's answer below should do the trick. – Mic Fok Mar 25 '21 at 16:26
1

As @Philidor has shown ShouldBindBodyWith should do the trick, in my case I decided to go with something similar to @spehlivan, because of two reasons:

  • ShouldBindBodyWith requires that the following binds are also ShouldBindBodyWith, it means I need to change all my previous code, which uses c.Bind
  • You need to explicitly tell to ShouldBindBodyWith the binding type you are trying to do, JSON, Form, ProtoBuf, etc, other binds like c.Bind detects it automatically.

This is what it looks like:

var input models.SomeInput

bodyCopy := new(bytes.Buffer)
// Read the whole body
_, err := io.Copy(bodyCopy, c.Request.Body)
if err != nil {
    log.Println(err)
    c.JSON(http.StatusBadRequest, gin.H{"error": "Error reading API token"})
    c.Abort()
    return
}
bodyData := bodyCopy.Bytes()

// Replace the body with a reader that reads from the buffer
c.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyData))

err = c.Bind(&input)

// Some code here...

// Replace the body with a reader that reads from the buffer
c.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyData))

Pay attention I replaced the c.Request.Body twice, for the bind in that code snippet and then at the end, for the next bind, in another place of my code (this snippet is from a middleware, the next bind is called from the controller).

In my case I needed to do this because the API Token is sent in the request body, which I don't recommend, it should be sent in the request header.

ruloweb
  • 704
  • 8
  • 10