Предположим, у меня был этот простой сервер (для краткости обработка ошибок опущена):
func main() {
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}
func HelloServer(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadAll(r.Body)
fmt.Fprintf(w, "The request is: %s", string(b))
}
Мне нужно добавить простое промежуточное ПО, которое читает тело запроса и регистрирует его. У меня уже есть это рабочее решение:
func requestLogger(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
pr, pw := io.Pipe()
tee := io.TeeReader(r.Body, pw)
r.Body = pr
go func() {
body, _ := ioutil.ReadAll(tee)
defer pw.Close()
log.Printf("This is the logged request: %s", string(body))
}()
next(w, r)
}
}
Я пришел к этому решению интуитивно методом проб и ошибок, поэтому я не уверен, будет ли оно работать во всех случаях. Вот мои вопросы относительно моего собственного решения:
- Почему я должен вручную закрывать
Pipe
? Не ioutil.ReadAll()
закрывает его, когда встречает EOF на r .Body. - Почему это не блокировка? Имеется циклическая ссылка, потому что
TeeReader
читает из того же источника (r.Body), в который он пишет через канал (r.Body = пр). Согласно документации, запись в блок PipeWriter выполняется до тех пор, пока он не выполнит одно или несколько операций чтения, а в случае TeeReader запись должна быть завершена до завершения операции чтения.