Что может случиться, если я не закрою ответ. Body?

98

В Go у меня есть несколько HTTP-ответов, и я иногда забываю позвонить:

resp.Body.Close()

Что происходит в этом случае? будет утечка памяти? Также безопасно ли вставлять defer resp.Body.Close()сразу после получения объекта ответа?

client := http.DefaultClient
resp, err := client.Do(req)
defer resp.Body.Close()
if err != nil {
    return nil, err
}

Что делать , если есть ошибка, может respили resp.Bodyбыть нулевым?

Дэниел Робинсон
источник
Можно поставить defer resp.Body.Close () после err! = Nil, когда присутствует return, потому что, когда err не nil, он уже закрывается. С другой стороны, тело должно быть закрыто явно при успешном выполнении запроса.
Васанта Ганеш К.

Ответы:

110

Что происходит в этом случае? будет утечка памяти?

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

Также безопасно ли вставлять defer resp.Body.Close () сразу после получения объекта ответа?

Нет, следуйте примеру, приведенному в документации, и закройте его сразу после проверки ошибки.

client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
    return nil, err
}
defer resp.Body.Close()

Из http.Clientдокументации:

Если возвращенная ошибка равна нулю, ответ будет содержать тело, отличное от нуля, которое пользователь должен закрыть. Если Body не одновременно считывается в EOF и не закрывается, базовый RoundTripper клиента (обычно Transport) может не иметь возможности повторно использовать постоянное TCP-соединение с сервером для последующего запроса «keep-alive».

JimB
источник
4
По этой ссылке все еще возможна утечка соединения с вашим кодом. Бывают случаи, когда ответ не равен нулю, а ошибка не равна нулю.
mmcdole
13
@mmcdole: Этот пост просто неправильный, и нет никакой гарантии, что он не запаникует, поскольку любой ответ, возвращаемый при ошибке, не имеет определенного состояния. Если тело не закрывается из-за ошибки, значит, это ошибка, о которой необходимо сообщить. Вам следует воспользоваться официальной клиентской документацией , в которой указано «При ошибке любой ответ можно игнорировать», а не случайным сообщением в блоге.
JimB
2
@ del-boy: Если вы ожидаете, что этот клиент будет делать больше запросов, вам следует попытаться прочитать тело, чтобы соединение можно было повторно использовать. Если вам не нужна связь, то не утруждайтесь чтением тела. Если читаете тело, заверните io.LimitReader. Обычно я использую довольно небольшой лимит, поскольку при слишком большом запросе быстрее установить новое соединение.
JimB
1
Стоит отметить, что в _, err := client.Do(req)результате дескриптор файла также остается открытым. Таким образом, даже если вам все равно, какой будет ответ, все равно необходимо присвоить его переменной и закрыть тело.
j boschiero 07
1
Для всех, кто интересуется, полная документация (курсив добавлен): «При ошибке любой ответ можно проигнорировать. Ответ, отличный от nil, с ошибкой, отличной от nil, только тогда, когда CheckRedirect терпит неудачу, и даже тогда возвращенный Response.Body уже закрыто."
nishanthshanmugham 01
15

Если Response.Bodyне будет закрыт Close()методом, то ресурсы, связанные с fd, не будут освобождены. Это утечка ресурсов.

Закрытие Response.Body

Из источника ответа :

Ответственность за закрытие Body - ответственность вызывающего абонента.

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

Обработка ошибок и отложенная очистка

В случае ошибки любой ответ можно игнорировать. Отклик, отличный от nil, с ошибкой, отличной от nil, возникает только в случае сбоя CheckRedirect, и даже тогда возвращенный Response.Body уже закрыт.

resp, err := http.Get("http://example.com/")
if err != nil {
    // Handle error if error is non-nil
}
defer resp.Body.Close() // Close body only if response non-nil
I159
источник
4
Вы должны отметить, что они должны возвращаться в рамках вашего условия обработки ошибок. Это вызовет панику, если пользователь не вернется к обработке ошибок.
Applewood
3

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

Более того, golang будет кэшировать соединение (используя persistConnstruct to wrap) для его повторного использования, если оно DisableKeepAlivesравно false.

В golang после использования client.Doметода go будет запускать readLoopметод goroutine как один из шагов.

Таким образом, в golang http transport.goa pconn(persistConn struct)не будет помещен в idleConnканал до тех пор, пока req не будет отменен в readLoopметоде, а также этот goroutine ( readLoopметод) будет заблокирован до тех пор, пока req не будет отменен.

Вот код, показывающий это.

Если вы хотите узнать больше, вам нужно увидеть readLoopметод.

Затрикс
источник
1

См. Https://golang.org/src/net/http/client.go
"Когда err равно nil, resp всегда содержит ненулевое значение resp.Body."

но они не говорят, что когда err! = nil, resp всегда равно nil.
Далее они говорят: «Если resp.Body не закрыт, базовый RoundTripper клиента (обычно Transport) может не иметь возможности повторно использовать постоянное TCP-соединение с сервером для последующего запроса« keep-alive »».

Поэтому я обычно решал проблему следующим образом:

client := http.DefaultClient
resp, err := client.Do(req)
if resp != nil {
   defer resp.Body.Close()
}
if err != nil {
    return nil, err 
}
Candita
источник
3
Это неверно, и нет никакой гарантии, что resp.Body не имеет значения nil в случае ошибки.
JimB 02
1
Спасибо @JimB. Формулировка в документации: «При ошибке любой ответ можно игнорировать». Точнее было бы сказать «При ошибке тело ответа всегда закрывается».
Candita 07
1
Нет, потому что обычно нет тела ответа, которое нужно закрыть. Если вы продолжите читать этот абзац в документации - «Ответ, отличный от nil, с ошибкой, отличной от nil, возникает только при сбое CheckRedirect, и даже тогда возвращенный Response.Body уже закрыт».
JimB 07