Голангский модуль запроса на чтение

Я пишу свой собственный logginMiddleware. В принципе, мне нужно записать тело запроса и ответ. Проблема, с которой я столкнулась, заключается в том, что когда я читаю тело, он становится пустым, и я не могу его дважды прочитать. Я понимаю, что это происходит потому, что это тип ReadCloser. Есть ли способ перемотать тело в начало?

Ответы

Ответ 1

Инспекция и издевательство над телом запроса

Когда вы впервые читаете тело, вы должны сохранить его, так что, как только вы закончите с ним, вы можете установить новый io.ReadCloser в качестве тела запроса, io.ReadCloser из исходных данных. Поэтому, когда вы продвигаетесь в цепочке, следующий обработчик может прочитать то же самое тело.

Один из вариантов - прочитать все тело, используя ioutil.ReadAll(), которое дает вам тело в виде байтового фрагмента.

Вы можете использовать bytes.NewBuffer() чтобы получить io.Reader из байтового фрагмента.

Последний отсутствующий элемент - сделать io.Reader io.ReadCloser, потому что bytes.Buffer не имеет метода Close(). Для этого вы можете использовать ioutil.NopCloser() который оборачивает io.Reader и возвращает io.ReadCloser, чей добавленный метод Close() будет no-op (ничего не делает).

Обратите внимание, что вы можете даже изменить содержимое байтового фрагмента, который вы используете для создания "нового" тела. Вы имеете полный контроль над этим.

Следует соблюдать осторожность, так как могут быть другие поля HTTP, такие как длина содержимого и контрольные суммы, которые могут стать недействительными, если вы измените только данные.Если последующие обработчики проверят их, вам также придется их изменить!

Проверка/изменение тела ответа

Если вы также хотите прочитать тело ответа, вам нужно обернуть полученный http.ResponseWriter и передать упаковщик в цепочку. Эта оболочка может кэшировать отправленные данные, которые вы можете проверить на лету (после того, как последующие обработчики запишут в нее).

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

type MyResponseWriter struct {
    http.ResponseWriter
    buf *bytes.Buffer
}

func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
    return mrw.buf.Write(p)
}

Обратите внимание, что MyResponseWriter.Write() просто записывает данные в буфер. Вы также можете проверить его на лету (в методе Write()) и немедленно записать данные в упакованный/встроенный ResponseWriter. Вы даже можете изменить данные. У вас есть полный контроль.

Однако необходимо соблюдать осторожность, так как последующие обработчики могут также отправлять заголовки HTTP-ответа, относящиеся к данным ответа - такие как длина или контрольные суммы - которые также могут стать недействительными, если вы измените данные ответа.

Полный пример

Собираем все вместе, вот полный рабочий пример:

func loginmw(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            log.Printf("Error reading body: %v", err)
            http.Error(w, "can't read body", http.StatusBadRequest)
            return
        }

        // Work / inspect body. You may even modify it!

        // And now set a new body, which will simulate the same data we read:
        r.Body = ioutil.NopCloser(bytes.NewBuffer(body))

        // Create a response wrapper:
        mrw := &MyResponseWriter{
            ResponseWriter: w,
            buf:            &bytes.Buffer{},
        }

        // Call next handler, passing the response wrapper:
        handler.ServeHTTP(mrw, r)

        // Now inspect response, and finally send it out:
        // (You can also modify it before sending it out!)
        if _, err := io.Copy(w, mrw.buf); err != nil {
            log.Printf("Failed to send out response: %v", err)
        }
    })
}

Ответ 2

Я мог бы использовать пакет GetBody from Request.

Посмотрите этот комментарий в исходном коде из request.go в net/http:

GetBody определяет необязательный func для возврата новой копии Body. Он используется для клиентских запросов, когда перенаправление требует чтения тела более одного раза. Использование GetBody все еще требует установки Body. Для запросов к серверу он не используется ".

GetBody func() (io.ReadCloser, error)

Таким образом, вы можете получить запрос тела, не делая его пустым.

Образец:

getBody := request.GetBody
copyBody, err := getBody()
if err != nil {
    // Do something return err
}
http.DefaultClient.Do(request)