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