0
0

Go言語:http.Get のレスポンスボディの処理で失敗

Last updated at Posted at 2024-09-02

どんな問題か

net/http の Get などを使ってリクエストをかけた時、返ってきたレスポンスのボディは必ず閉じなければいけない、というのは、あちこちで言われていまして、わりとよく知られていることかと思います。
もちろん、net/http のドキュメントにも、冒頭に書かれています。

The caller must close the response body when finished with it:

resp, err := http.Get("http://example.com/")
if err != nil {
	// handle error
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
// ...

なので、上の net/http のサンプルコードのように、defer を使って、関数・メソッド終了時に処理するというのが定番です。
ただし、サンプルコードのように io.ReadAll(resp.Body) を使ってすぐにレスポンスボディを読み込むことを怠り、resp.Body を別の処理にそのまま使おうとすると、まれに問題がおきます。次のコードのような場合です。

defer resp.Body.Close()
x, err := doSomething(resp.Body)
return x, err

上のコードだと、doSomething の処理が瞬時に終わるようならおそらく問題は起きないと思いますが、処理の間、リクエストのコネクションはつながったままなので、時間がかかるようだと接続先の方からタイムアウトでコネクションを切断されていしまい、doSomething の処理が失敗してエラーということが起こりえます。というか、起きました・・・(悲)。
こういうエラーが返ってきました。

read tcp [xxxx:xx:xxxx:x:xxxx:xxxx:xxxx:xxxx]:55555->
[yyyy:yyy:yyy:yy::yyyy:yyy]:443: read: connection reset by peer

コードを書いた当初、このエラーは起きませんでした。
複数のリクエストを、順番に、前のリクエストの処理が終わってから、次のリクエストをかける、というやり方から、時間短縮のため、Goroutine を使って同時にリクエストをかけるように変更したところで、時折エラーが出るようになりました(時折、というのがすごく嫌)。
もともと、レスポンスボディに含まれる処理対象のデータが数メガバイトとやや大きめだったことに加え、同時にリクエストをかけるようになったことによる処理の競合やその時時の通信状況が影響したと考えられます。
以下のように修正して対処しています。

defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
	return err
}
x, err := doSomething(bytes.NewBuffer(b))
return x, err

横着はするもんじゃないですね。自戒、自戒。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0