Goのio.Readerとio.Writerの仕組み【図解】
この記事を書こうと思った理由
Goのio.Readerとio.Writerはさまざまなところで使われており、特にAPIテストを書く際に使用することが多いです。
私自身もテストやhttp通信周りでよく使用してきたのですが、io.packageとの関係性が曖昧だったので整理してみることにしました。
io.Reader・io.Writerとは?
Goでfileとの読み書きやnetworkとのやりとりを行うためにある抽象化されたインターフェース。
bytes.Buffer型とstrings.Reader型
bytes.Buffer型
bytes.Buffer型はio.Readerとio.Writerのinterfaceを両方満たしている便利な構造体を持っています。
writeメソッドは内部のbufフィールドに書き込みを行います。
readメソッドは内部のbufフィールドから読み取りを行います。
strings.Reader型
strings.Reader型はio.Readerのみを満たす構造体を持っています。
strings.NewReader(string)
とすることでインスタンスを生成します。
readメソッドで内部の文字列を読み取ります。
それぞれの使い所
Readメソッドが必要な場合 → bytes.Buffer型・strings.Reader型どちらでも対応できる。
Writeメソッドが必要な場合 → bytes.Buffer型で対応する。
応用編
個人的にこれらio.Readerとio.Writerの存在を意識するのはGoで作られたAPIのテストを書く時などである。
Request(net/http)
net/httpパッケージにはRequest構造体があり、その中にBodyフィールドがある。(参照)
これはio.Readerを拡張したio.ReadCloser型で書かれている。
type Request struct {
// ...
Body io.ReadCloser
// ...
}
つまりBody.Read([]bytes)
でhttpリクエストの値を受け取れるようになっている。
上記のような実際の内部処理を操作するためにテストでは以下のようにしてRequestを作成することがよくある。
req := NewRequest(method, target string, body io.Reader)
ResponseRecorder(net/http)
ResponseRecorderも内部にBodyフィールドを持っている。
ただRequest構造体とは違い*bytes.Buffer型で定義されている。(参照)
type ResponseRecorder struct {
// ...
Body *bytes.Buffer
// ...
}
そのため、writeメソッドの使用が可能となる。(bytes.Buffer型はio.Readerとio.Writerの両方の型を満たしている)
つまりResponseRecorderのwriteメソッドが使えるのでで以下のようにレスポンスを生成することができる。
上記のような実際の内部処理をさせるためにテストでは以下のようにして空っぽのResponseRecorderを作成することがよくある。
res := httptest.NewRecorder()
まとめ
あまりGo以外ではio.Readerやio.Writerといった低いレイヤーとやり取りするパッケージと対面する機会がないのに対し、シンプルであるが故にそういったパッケージにもよく触れる機会があるのがGoの魅力だなぁと感じました。
参考記事
https://qiita.com/theoden9014/items/ac8763381758148e8ce5
https://zenn.dev/hsaki/books/golang-io-package/viewer/bytestring
Discussion