こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

phpでダウンロード処理に失敗する事があります

phpにてexcelを作成後にダウンロードさせる処理を作成したのですが
excelのサイズが100MBを超える様な時にダウンロードが失敗(止まる)する
事があります。

ブラウザからダウンロードの状況を見ていると徐々に転送速度が落ち
最終的に転送速度が0になります。
ブラウザは転送速度0のまま待っている状態でエラーは出ていない状態です。

phpのエラーログやapacheのログにも何の情報も表示されていません。
スクリプトにconnection_aborted や connection_status で接続が切れた場合に
エラーログを吐く様に試しましたが何も出力されていませんでした。

以下環境になります。===================================================
CentOS Linux release 7.7.1908
Apache/2.4.6
PHP 5.6.40
使用しているブラウザは Chrome 最新版です。

以下PGになります。===================================================
ini_set("set_time_limit",0);
ini_set("memory_limit","-1");
ignore_user_abort(true);


// エクセル作成処理 開始


// エクセル作成処理 終了



// ダウンロードさせる
$tmpfile = "hogehoge.xlsx";
$size = filesize($tmpfile);

header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
header('Content-disposition: attachment; filename="' . urlencode("テストです" . ".xlsx") . '"');
header('Content-Length: '.$size);

ob_end_clean();
ob_start();

$fp = @fopen($tmpfile, 'rb');
if ($fp != null) {
while(!feof($fp)) {
$buf = fread($fp, 4096);
echo $buf;
ob_flush();
flush();
}
fclose($fp);

// ダウンロード後は不要なので削除する
unlink ($tmpfile);

}

投稿日時 - 2020-02-13 11:39:21

QNo.9712356

すぐに回答ほしいです

質問者が選んだベストアンサー

うーん、他の点というと、今気になったのが

>$tmpfile = "hogehoge.xlsx";

こちらは実際もそのままでしょうか?
現在時刻あるいはユーザー名等をファイル名に取り込んで、バッティングしない工夫とかされていれば良いんですが。

もし固定ファイル名だと、誰かがダウンロード中に別の誰かがこのスクリプトを起動して、転送中のファイルを更新してしまう事故があるかもしれないと思いました。

投稿日時 - 2020-02-13 16:00:28

補足

回答ありがとうございます。
ファイル名ですが、質問するにあたり簡略化しておりました。実際には str_shuffle("abcdefghijklmnopqrstuvwxyz") . ".xlsx" として重複を防いでおります。情報を小出しにするような形となり申し訳ありませんでした。又、この処理にアクセスしているのは今のところ私だけで複数のリクエストは来ていない状態です。

投稿日時 - 2020-02-13 16:24:50

お礼

phpでの改善は難しいと思いxsendfileでの処理を試しましたがphpと同じ挙動を見せたためhttpd.confを見直しSendBufferSizeの設定をデフォルトに戻したところ問題無くダウンロード出来る様になりました。

投稿日時 - 2020-02-14 11:01:50

ANo.3

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(3)

ANo.2

flush() の仕様かなとか考えてたんですが、気になる記述がありました。

https://so-zou.jp/web-app/tech/programming/php/sample/progress.htm

こちらに

>スクリプトが実行可能な時間は制限されており、この時間を超えると
>スクリプトは強制的に終了させられます。この時間は既定で30秒であり、
>長時間の処理を実行するには注意が必要です。

>制限時間を超えたときには、
>Fatal error: Maximum execution time of 30 seconds exceeded in C:\index.php on line 10
>のようなエラーが表示されます。

ということでして。

>この制限はset_time_limit()によって、実行可能な秒数を設定することで
>回避できます。なお、ゼロ秒とすると時間の制限はなくなります。
>PHP: set_time_limit - Manual
https://www.php.net/manual/ja/function.set-time-limit.php

ということなので、ファイル送信開始する前に
set_time_limit(120);
とか
set_time_limit(max(30, intdiv($size, 500000)));
(30秒または500KB/sで計算した秒数で転送できなければタイムアウト)
みたいにすればいいでしょうかね?
※ダウンロード速度は受け手側の環境にも左右されます。ADSLの4Mbps当たりが最低かなと考えて、4Mbps=500KB/sという値を使ってみました

さすがに
set_time_limit(0);
とはやらない方がいいと思います。走ったままのプロセスがいくつも積みあがってメモリ不足になる危険性がある気が。

----

あれ? 頭に

>ini_set("set_time_limit",0);

がありますが…これは…

>phpのini_set("max_execution_time",n)とset_time_limitの違いについて調べた
http://kannokanno.hatenablog.com/entry/2017/05/27/220521

実は

ini_set("max_execution_time",0);

の間違いだったという凡ミスでしょうか。

まあでも私としては0セットはお勧めできないので、set_time_limit() でファイルサイズに応じたタイムアウトを設定した方が良いと思います。

投稿日時 - 2020-02-13 13:32:19

お礼

回答ありがとうございます。
ご指摘頂いた max_execution_time に対して テスト的に 0 をセットし実行してみましたがダウンロード出来たりできなかったりの状態です。エラーログに何も出力されないので何処から疑ってよいのやらです。何か他に疑うべき個所等、お知恵を貸していただければ幸いです。

投稿日時 - 2020-02-13 14:43:12

ANo.1

そのとおり
よくあります

投稿日時 - 2020-02-13 13:01:39

あなたにオススメの質問