50
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

たぶんみんな間違っていないgolangのHTTP Respose Bodyの閉じ方

Last updated at Posted at 2015-06-04

ネット眺めているとたぶんみんな間違えてる golang の HTTP Respose Body の閉じ方というエントリが流れてきたけれど、多分みんな間違っていないという話。

元エントリでの話

net/httpのResponseを撮るときに、errorがnilであることを確認するだけでは不十分で、Responseがnilかどうかを確かめたほうがよいというエントリ。すなわち、

resp, err := http.Get("http://golang.org")
if err != nil {
    return err
}
defer resp.Body.Close()

ではなく、

resp, err := http.Get("https://api.ipify.org?format=json")
if resp != nil {
    defer resp.Body.Close()    // ← ここで nil じゃないときは閉じるようにしておく
}
if err != nil {
    return err
}

と書いたほうが良いとされている。

たぶんこうする必要はない

ところで、僕は多分こうしなくても良いと思う。
go 1.4のnet/httpを読むと、たしかにredirectFailedの時は、

if redirectFailed {
    // Special case for Go 1 compatibility: return both the response
    // and an error if the CheckRedirect function failed.
    // See http://golang.org/issue/3795
    return resp, urlErr
}

とrespを返すように書かれているのだけれど、ここで返されるrespはclient.goの378行目で必ずCloseが呼ばれている。

if shouldRedirect(resp.StatusCode) {
	// Read the body if small so underlying TCP connection will be re-used.
	// No need to check for errors: if it fails, Transport won't reuse it anyway.
	const maxBodySlurpSize = 2 << 10
	if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {
		io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize)
	}
	resp.Body.Close()
	if urlStr = resp.Header.Get("Location"); urlStr == "" {
		err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode))
		break
	}
	base = req.URL
	via = append(via, req)
	continue
}

たしかにResponseが帰ってくることはあるが、BodyはCloseされているので別にそっとしておいてあげても良いと思う。少なくともリークするとか悪さはしない。この性質を利用すればレスポンスコードを取ったりすることはできるだろうけれど、少なくとも閉じるという操作はしなくても大丈夫そう!

結論

そういうわけで、まぁみんな間違っていないんじゃないかなあと思った。
僕のほうでは明日からも元気よく、

resp, err := http.Get("http://golang.org")
if err != nil {
    return err
}
defer resp.Body.Close()

と書いていきたいと思う。

追記:ちなみに、Go1では

ところで、コメントで「Go1と互換性を取るために〜」と書いてあるけれど、本当にGo1ではそうなっているのだろうか。そこでGo1ではどう動くのかを確認してみると、client.goの259行目でClose()が呼ばれている。

if shouldRedirect(resp.StatusCode) {
	resp.Body.Close()
	if urlStr = resp.Header.Get("Location"); urlStr == "" {
		err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode))
		break
	}
	base = req.URL
	via = append(via, req)
	continue
}
return

なので、コメントの趣旨を汲んでもここで閉じない対応は問題ないと思う。

#追記2: 本当に閉じなくてもいいのだろうか
僕の読みは浅はかだったのかもしれない。難しい。
https://twitter.com/mattn_jp/status/606472630665773056
https://twitter.com/mattn_jp/status/606478900596645889

50
42
2

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
50
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?