Reader
interface の Read
関数は、どのタイミングで io.EOF
を返すのでしょうか。
まずは strings.Reader
で見てみましょう。
package main import ( "fmt" "strings" ) func main() { r := strings.NewReader("example\n") for { var b [1]byte n, err := r.Read(b[:]) fmt.Printf("%d %q %v\n", n, b, err) if err != nil { break } } }
結果
1 "e" <nil> 1 "x" <nil> 1 "a" <nil> 1 "m" <nil> 1 "p" <nil> 1 "l" <nil> 1 "e" <nil> 1 "\n" <nil> 0 "\x00" EOF
Read
の結果は、読み込んだbyte数です。なにもなくなってから io.EOF
を返していることがわかります。
ファイルだとどうでしょうか。
package main import ( "fmt" "os" ) func main() { f, err := os.Open("main.go") if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } defer f.Close() for { var b [1]byte n, err := f.Read(b[:]) fmt.Printf("%d %q %v\n", n, b, err) if err != nil { break } } }
1 "b" <nil> 1 "r" <nil> 1 "e" <nil> 1 "a" <nil> 1 "k" <nil> 1 "\n" <nil> 1 "\t" <nil> 1 "\t" <nil> 1 "}" <nil> 1 "\n" <nil> 1 "\t" <nil> 1 "}" <nil> 1 "\n" <nil> 1 "}" <nil> 1 "\n" <nil> 0 "\x00" EOF
同じですね。
ではHTTPリクエストだとどうでしょうか。
package main import ( "fmt" "net/http" "os" ) func main() { resp, err := http.Get("http://example.com") if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } defer resp.Body.Close() for { var b [1]byte n, err := resp.Body.Read(b[:]) fmt.Printf("%d %q %v\n", n, b, err) if err != nil { break } } }
1 "<" <nil> 1 "/" <nil> 1 "h" <nil> 1 "t" <nil> 1 "m" <nil> 1 "l" <nil> 1 ">" <nil> 1 "\n" EOF
なぜなのか… なぜなのか!!!
検索するとruiさんのエントリーが出てきました。 qiita.com (三年前の記事だった… もしかして… これは常識なのか!?) そして、mattnさんがgolang-nutsでスレッドを立てられていたのでざっと見ました。 Issue 49570044: code review 49570044 も勉強になります。 これはContent-Lengthがセットされたレスポンスボディーを最後まで読んだ後に、すぐにコネクションを使いまわせるようにするための意図した挙動であるということがわかりました。
Reader
はわりと使い慣れたinterfaceなのにハマってしまいました。
Go言語を書いていると手癖ですぐにerr
を返してしまいがちですが、Read
は読み込んだバイトがありながら io.EOF
になることがあることに気をつけなくてはいけません。
いや、これはruiさんの記事を同じことを言ってますね… ほんとは常識なのかもしれない…
ライブラリーが Reader
使ったインターフェースを公開しておきながら、io.EOF
の扱いが適切でないと簡単にバグを踏んでしまいます。
strings.Reader
でテストして満足していると、resp.Body
に繋いだ瞬間なぜか挙動が変わるということがあるかもしれません。
Read
の返り値のバイト数を捨ててませんか?
err != nil
でもデータを読み込んでいるかもしれませんよ?
みなさん、気をつけましょう。おわり。