yuw27b’s blog

技術メモと雑記

named pipeでオンデマンド・ダウンロード

「オンデマンド・ダウンロード」って勝手に呼んでるんですけど、要するに、ダウンロード要求があった時に、サーバーサイドで動的に(複数ファイルから)tarを生成してダウンロードさせる、というのを実装した時のメモです。

サーバにダウンロード対象のファイルが数万あって、どのセットをダウンロードしたいかが動的に決まるので、予めzipなどを用意することができない、でもファイル1つずつダウンロードしてもらうのは面倒、という状況でした。

デメリットとしては、tarなので圧縮されない、というのはあります。(今回の状況では、対象のファイルはバイナリが主で、大幅な圧縮は期待できないのでそれでも構わないとしました。)


実装にはnamed pipeを使いました。
named pipeとは:
名前付きパイプ - Wikipedia

これを使えば、一旦ファイルに書き出さなくても、プロセス間で通信(データの受け渡し)ができます。今回はtarの中身をパイプに流して、Webサーバのレスポンスとして使います。

named pipeを作って、tarを流す。

$ mkfifo pipe_path
$ tar cvfh pipe_path tar_path

pipeに流れているものをPHPからレスポンス。

if (file_exists($pipe_path)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="tar_name.tar"');
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Connection: close');
    ob_end_clean();
    readfile($pipe_path);
    exit();
}

最終的なファイルサイズが決まらないため、ブラウザではダウンロードの残り時間の予測は表示されません。

また、環境によるかもしれませんが、上記のような感じで、
1. PHPからnamed pipeを作るシェルスクリプトを実行
2. ダウンロード(レスポンス)開始
とやる場合、1と2の間で1秒くらいsleep()を入れるとスムーズでした。そうでないと、file_exists($pipe_path)でfalseが返ってくる場合がありました。