鼻が詰まって困ってます
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アプリケーションを作成する場合にぜひ使いたいモジュールです。