鼻が詰まって困ってます
LWPやFurlを使ってインターネットから様々なファイルをダウンロードする。よくやりますよね。その際に大きなファイルをGETしてしまい、perlのプロセスがメモリを大量に使い、OOM Killerに殺されて2年経つ、なんて経験をした人もきっと多いはず。
そこで使うのがレスポンスをファイルに書き出す技。Furlであれば
my $furl = Furl->new();
open my $fh, '>', $filename;
$furl->request(
url => 'http://example.com/4k.jpg',
write_file => $fh
);
my $size = -s $fh;
seek($fh, 0, 0);
と書けて、$filenameのファイルに大きな画像データが保存されます。
しかし、取得対象とするデータが、大きなファイルから小さいファイルまでさまざまあり、また、取得しなければならない回数が多い場合、小さいファイルのためにいちいちテンポラリファイルを作るのももったいないと思う事もなきにしもあらず。
そんな場合に使いたいのがStream::Buffered。
Stream::Bufferedはデータを一時的に格納するバッファとして使え、格納するサイズが大きくなると自動的にファイルに書き出してメモリを大量に使ってしまわないようになっています。元々Plack::TempBufferという名前でPlackに含まれ、PSGIサーバにおいてPOSTデータをバッファリングする用途に使われていましたが、切り出されて別のディストーションになっています。現在のPlack::TempBufferはStream::Bufferedを継承するだけのモジュールとなっています。
Stream::Bufferedを使うと上のコードは、
my $furl = Furl->new();
my $buf = Stream::Buffered->new(メモリに格納するサイズ/デフォルト 1MB);
$furl->request(
url => 'http://example.com/4k.jpg',
write_code => sub {
my ( $status, $msg, $headers, $buf ) = @_;
$buf->print($buf);
}
);
my $size = $buf->size;
my $fh = $buf->rewind;
Stream::Bufferedのオブジェクトはファイルハンドルとしては扱えないので、write_fileではなく、write_codeを使います。そして最後にrewindメソッドを使うと、ファイル読み込みのオフセットをリセットした状態のファイルハンドルが得られます。seek済みなのでハマらなくてよいですね。$fhは通常のPerlのファイルハンドルなので、perlのread/sysread関数を使って読んだり、PSGIアプリケーションのレスポンスとしてそのまま使えます。
Stream::BufferedはHTTPクライアントで他のコンテンツを取って来て表示させる、Proxy的な動作を含むWebアプリケーションを作成する場合にぜひ使いたいモジュールです。