ネット眺めているとたぶんみんな間違えてる 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