滅多にないことだと思うが、非常に大きな zip ファイルを動的に生成してダウンロードさせるみたいなことをしたいことがあるかもしれない。

Archive::Zip だとストリーム生成できないので、Archive::Zip::SimpleZip を使う。Archive::Zip::SimpleZip だとストリーム出力で file handle などに書き出せる。

これで一度ファイルに書いてから、そのファイルを sendfile 的にレスポンスしてもいいのだけれど、書いている間はクライアントからしてみれば完全に無反応になるので、場合によってはタイムアウトになってしまうことがある。そこでストリーム出力をそのままクライアントにストリームしたくなる。

簡単なコードにすると以下のようになった。Archive::Zip::SimpleZip にコールバックかオブジェクトを渡せたらいいが、渡せないようなので、文字列リファレンスをいちいちクリアしながら出力するキモいコードになっている。

#!plackup
use strict;
use warnings;

use Archive::Zip::SimpleZip qw($SimpleZipError :zip_method);

sub {
    my $env = shift;

    # Return streaming response
    sub {
        my $respond = shift;
        my $writer = $respond->([
            200,
            [
                'Content-Type' => 'application/zip',
                'Content-Disposition' => 'attachment; filename="dekai.zip"',
            ],
        ]);

        my $_buf = "";
        my $buf = \$_buf;
        my $zip = Archive::Zip::SimpleZip->new($buf, Stream => 1) or die "Failed to create zip file";

        for my $n (1..1000) {
            warn $n;
            $zip->addString("$n" x 100, Name => sprintf('%04d.txt', $n), Method => ZIP_CM_STORE) or die "SimpleZipError : $SimpleZipError";
            $writer->write($$buf); $$buf = "";
            select(undef, undef, undef, 0.01);
        }

        $zip->close or die "SimpleZipError : $SimpleZipError";

        $writer->write($$buf);
        $writer->close();
    };
}
  1. トップ
  2. tech
  3. Perl (PSGI) で zip ファイルを動的に作りつつ順次クライアントに送りつける