PHPで巨大なファイルをブラウザにbinary-streamとして返すのに苦労したのでその時のメモです。
結果、passthruで解決しました
PHPで巨大なファイルをダウンロードさせる方法としては、
- fopen()でファイルを開いて
- fread()でチョイチョイ読みながら出力
ってのがよくあります
大抵の場合はこれでOK(max_execution_timeの制限はあるが)なのですが、5GBを超える場合等、巨大なファイルの場合だとうまくいかないようです
CentOS6.x(32bit)での話ですが、途中までうまくいっているのにいつの間にか死んでました。。。
で、64bitのOSに変えてみみても、やっぱりダメ
うーむ。困った。
passthruで解決
readfile($filename)とか使ってみてもやっぱりダメ
で、最終的に、passthru($command)にたどり着きました
コードは以下
$uri = '/path/to/file';
passthru('/bin/cat ' . $uri);
catコマンドを使って、ファイルをクライアントに送出するわけです
まあ、こんな大きなファイルをダウンロードさせるのが間違いって気もしますが、請負い開発してるとどうしてもこういうのがテストケースででてくるんすよね
で、テストNGとなりドツボにはまる・・・
ちなみに、session_start()してる状態でこの処理を行うと、ダウンロード中にブラウザから別の操作(例えば別機能へのリンクをクリック)してもダウンロードが完了するまで待たされます
なぜならsession_start()でセッションがロックされている為、開放されるまで同一セッションのリクエストはサーバ側で待たされるのです
待たされないようにするには、session_write_close()を事前に行う必要があります
それ以外にも、レスポンスヘッダーやもろもろ考慮して、最終的なコードは以下
$uri = '/path/to/file';
set_time_limit(0);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($uri));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . @filesize($uri)); // 32bitOSだと、ファイルサイズが4GBを超えると0を返すはずなので注意
ob_end_flush();
session_write_close(); // session_start()中であれば必須
passthru('/bin/cat ' . $uri);
なお、Windows版PHPの場合はどーなのか知りません(catコマンドとか無いし)w