Ответ 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)
}
})
}