Джин Если `запрос тела` связан в промежуточном программном обеспечении, c.Request.Body становится 0

Мой API-сервер имеет промежуточное ПО, которое получает токен из заголовка запроса. Если это правильный доступ, его функция перейти к следующей.

Но запрос перешел к промежуточному программному обеспечению и перешел к следующей функции, c.Request.Body становиться 0,

посуда

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

следующий функционал

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 вернуть ошибку с EOF, потому что возможно c.Request.Body является 0,

я хочу получить user_id из тела запроса в средней посуде. Как это сделать без проблем, что c.Request.Body стать 0

2 ответа

Решение

Вы можете прочитать тело только один раз. Данные передаются от пользователя, и они не собираются отправлять их снова. Если вы хотите прочитать это более одного раза, вам придется буферизовать все это в памяти, например так:

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

Обратите внимание, что буферизация всего запроса в памяти означает, что пользователь может заставить вас выделять неограниченную память - вам, вероятно, следует либо заблокировать это на внешнем прокси-сервере, либо использовать io.LimitedReader ограничить объем данных, которые вы будете буферизовать.

Вы также должны прочитать все тело, прежде чем Unmarshal сможет начать свою работу - это, вероятно, не страшно, но вы можете добиться большего, используя io.TeeReader а также json.NewDecoder если вы так склонны.

Конечно, лучше было бы найти способ реструктурировать ваш код так, чтобы ненужная буферизация тела и его расшифровка.

Gin предоставляет собственное решение, позволяющее многократно получать данные из файлов . Решение заключается в использованииc.ShouldBindBodyWith. Согласно документации джина

ShouldBindBodyWith... сохраняет тело запроса в контексте и повторно использует его при повторном вызове.

Для вашего конкретного примера это будет реализовано в вашем промежуточном программном обеспечении следующим образом:

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

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

    return jsonBody.Id
}

После промежуточного программного обеспечения, если вы хотите снова выполнить привязку к телу, просто используйтеctx.ShouldBindBodyWithснова. Для вашего конкретного примера это будет реализовано так

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

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

Проблема, с которой мы боремся, заключается в том, чтоginнастроен какio.ReadCloserобъект -- это означает, что он предназначен для чтения только один раз. Таким образом, если вы вообще получите доступ к своему коду, байты будут прочитаны (потреблены) и после этого будут пустыми. ИспользуяShouldBindBodyWithдля доступа к байтам gin сохраняет байты в другом механизме хранения в контексте, чтобы его можно было использовать снова и снова.

В качестве примечания, если вы использовали и позже хотите получить доступc.Request.Body, вы можете сделать это, подключившись к механизму хранения джина черезctx.Get(gin.BodyBytesKey). Вот пример того, как вы можете получить тело запроса, хранящееся в gin, как[]byteа затем преобразовать его в строку,

          var body string
    if cb, ok := ctx.Get(gin.BodyBytesKey); ok {
        if cbb, ok := cb.([]byte); ok {
            body = string(cbb)
        }
    }
Другие вопросы по тегам