response.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なりますか?


errがnilでない場合、すでに閉じられているため、returnが存在する場合はerr!= nilの後にdeferresp.Body.Close()を配置しても問題ありません。一方、リクエストが成功した場合、本体は明示的に閉じる必要があります。
Vasantha Ganesh K

回答:


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ドキュメントから:

返されたエラーがnilの場合、応答には、ユーザーが閉じることが期待されるnil以外の本文が含まれます。BodyがEOFに読み取られて閉じられていない場合、クライアントの基盤となるRoundTripper(通常はTransport)は、後続の「キープアライブ」要求のためにサーバーへの永続的なTCP接続を再利用できない可能性があります。


4
このリンクによると、コードとの接続をリークする可能性があります。応答がnil以外で、エラーがnil以外の場合があります。
mmcdole 2016

13
@mmcdole:その投稿は間違っており、エラーで返される応答には定義された状態がないため、パニックにならないという保証はありません。ボディがエラーで閉じられない場合、それはバグであり、報告する必要があります。ランダムなブログ投稿ではなく、「エラーが発生した場合、応答はすべて無視できます」と記載されているクライアント公式ドキュメントを参照する必要があります
JimB 2016

2
@ del-boy:そのクライアントがより多くのリクエストを行うことを期待している場合は、接続を再利用できるように本文を読み取ってみてください。接続が必要ない場合は、本文をわざわざ読んではいけません。本文を読む場合は、で包んでくださいio.LimitReader。リクエストが大きすぎる場合は新しい接続を確立する方が速いため、通常はかなり小さい制限を使用します。
JimB 2017年

1
これを行う_, err := client.Do(req)と、ファイル記述子が開いたままになることも指摘しておく価値があります。したがって、応答が何であるかを気にしない場合でも、それを変数に割り当てて本体を閉じる必要があります。
j boschiero 2017年

1
興味のある人のために、完全なドキュメントは、(強調追加)です。「というエラーで、任意の応答はCheckRedirectが失敗したときにnil以外のエラーでA非nilのレスポンスのみ発生し、無視することができ、その後も返さResponse.Bodyはすでにあります閉まっている。"
nishanthshanmugham

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

4
エラー処理条件の範囲内で返されることに注意してください。ユーザーがエラー処理に戻らない場合、これはパニックを引き起こします。
アップルウッド

3

上記のように、最初は記述子が閉じません。

さらに、falseの場合、golangは接続をキャッシュして(persistConnstructを使用してラップし)再利用しDisableKeepAlivesます。

golang after useclient.Doメソッドでは、goはreadLoopステップの1つとしてgoroutineメソッドを実行します。

したがって、golang httptransport.goでは、メソッドでreqがキャンセルされるまでチャネルpconn(persistConn struct)に入れられません。また、このgoroutine(method)は、reqがキャンセルされるまでブロックされます。idleConnreadLoopreadLoop

これがそれを示すコードです。

詳細を知りたい場合は、readLoopメソッドを確認する必要があります。


1

https://golang.org/src/net/http/client.go「err
がnilの場合、respには常にnil以外のresp.Bodyが含まれます」を参照してください

しかし、err!= nilの場合、respは常にnilであるとは言いません。さらに、
「resp.Bodyが閉じられていない場合、クライアントの基盤となるRoundTripper(通常はTransport)は、後続の「キープアライブ」要求のためにサーバーへの永続的なTCP接続を再利用できない可能性があります。」

だから私は通常このような問題を解決しました:

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

3
これは正しくなく、エラーが発生したときにresp.Bodyがnitnilであるという保証はありません。
JimB 2016

1
@JimBに感謝します。ドキュメントの文言は「エラーが発生した場合、どの応答も無視できます」です。「エラーが発生すると、応答ボディは常に閉じられます」と言う方が正確です。
candita 2017

1
いいえ、通常、閉じる応答本体がないためです。ドキュメントでその段落を読み続けると、「非nilエラーのある非nil応答は、CheckRedirectが失敗した場合にのみ発生し、それでも返されるResponse.Bodyはすでに閉じられています。」
JimB 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.