Go-Gin重复读取请求体多次

7
我试图在对数据进行验证后,恢复其上下文及其数据。我需要保留这些数据,因为稍后在下一个函数中可能还需要用到它。我对golang不熟悉,以下代码是我能达到的最远程度,非常感谢任何帮助和更好的方法。事先感谢您提供的帮助。
验证中间件
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()

}

路由

auth.POST("login", gin.Logger(), validations.SignupValidator, func(ctx *gin.Context) {
            ctx.JSON(200, videoController.Save(ctx))
        })

“恢复上下文”是什么意思? - Roman Kiselenko
当我在验证过程中读取上下文一次后,我无法再次读取该数据,并且我需要在该验证中间件之后执行其他操作。 - bihire boris
你可以多次读取请求体,c.ShouldBindJSON 正是为此而生。展示你所拥有的错误信息。 - Roman Kiselenko
错误出现在 c.ShouldBindJSON 之后,如果您尝试访问上下文中的数据,它将为空。来自同一个问题 @Зелёный - bihire boris
请显示完整的错误信息。 - Roman Kiselenko
我编辑了代码,尝试记录上下文数据。请查看 @Зелёный - bihire boris
3个回答

21

你可以试试这个。

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

你可以随意使用ByteBody,而不会对c.Request.Body造成任何副作用。


请务必处理ioutil.ReadAll的错误。在Go中不建议使用下划线,因为byteBody可能为空,服务器可能会崩溃。 - Musab Gultekin

16

这是一个使用ShouldBindBodyWith读取请求体两次的示例,检查一下:

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")
}

输出:

$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}

4
我尝试表达的是,这样做行不通。因为你无法两次读取缓冲区,而你已经使用了ShouldBindJSON进行了一次读取。如果可能的话,请尝试运行一下,看看是否会输出内容,因为在我的环境中它没有输出任何内容。 - bihire boris
4
给其他读者:ShouldBindJSONShouldBindBodyWith行为不同。你不能调用两次ShouldBindJSON,但是ShouldBindBodyWith不会遇到同样的问题。如果你确实需要手动两次读取请求体,可以参考下面@spehlivan的答案。 - Mic Fok

1

正如@Philidor所示,ShouldBindBodyWith应该可以解决问题,但在我的情况下,我决定采用类似于@spehlivan的方法,原因有两个:

  • ShouldBindBodyWith要求以下绑定也必须是ShouldBindBodyWith,这意味着我需要更改所有使用c.Bind的先前代码。
  • 您需要明确告诉ShouldBindBodyWith您正在尝试进行的绑定类型,例如JSON、Form、ProtoBuf等,而c.Bind之类的其他绑定会自动检测到它。

它看起来像这样:

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))

请注意,我在代码片段中两次替换了 c.Request.Body,一次是用于绑定,另一次是在代码的另一个位置进行下一次绑定(这个片段来自一个中间件,下一次绑定是从控制器调用的)。

在我的情况下,我需要这样做是因为 API Token 是通过请求体发送的,但我不建议这样做,应该发送到请求头中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接