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が返ってくる場合がありました。