根据经验,当接收到带有Content-Disposition: attachment 标头的响应时,不同的浏览器会在以下时刻显示文件下载对话框:
- Firefox 收到标题后立即显示对话框
- Internet Explorer 在收到标头和响应正文的 255 个字节后会显示该对话框。
- Chromium 在收到标头以及响应正文的 1023 个字节后显示对话框。
那么,我们的目标如下:
- 尽快将响应正文的第一个 KB 刷新到浏览器,以便 Chrome 用户尽早看到文件下载对话框。
- 此后,定期向浏览器发送更多内容。
阻碍这些目标的实现可能是多层次的缓冲,您可以尝试以不同的方式与之抗争。
PHP 的 output_buffer
如果您将output_buffering 设置为Off 以外的值,PHP 将自动创建一个输出缓冲区来存储您的脚本尝试发送到响应body 的所有输出。您可以通过确保将php.ini 文件中的Off 或apache.conf 或nginx.conf 等网络服务器配置文件中的output_buffering 设置为Off 来防止这种情况发生。或者,您可以在脚本开头使用ob_end_flush() 或ob_end_clean() 关闭输出缓冲区(如果存在):
if (ob_get_level()) {
ob_end_clean();
}
由您的网络服务器完成的缓冲
一旦您的输出通过 PHP 输出缓冲区,它可能会被您的网络服务器缓冲。您可以尝试通过定期调用flush() 来解决此问题(例如每 100 行),尽管 PHP 手册对提供任何保证犹豫不决,列出了一些可能失败的特殊情况:
冲洗
...
刷新 PHP 的写入缓冲区以及 PHP 正在使用的任何后端(CGI、Web 服务器等)。这会尝试将当前输出一直推送到浏览器,但有一些注意事项。
flush() 可能无法覆盖您的 Web 服务器的缓冲方案...
一些服务器,尤其是在 Win32 上,仍然会缓冲脚本的输出,直到它在将结果传输到浏览器之前终止。
像 mod_gzip 这样的 Apache 服务器模块可能会自己做缓冲,这会导致 flush() 不会导致数据立即发送到客户端。
您也可以在每次尝试回显任何输出时自动让 PHP 调用 flush(),方法是在脚本开头调用 ob_implicit_flush - 但请注意,如果您通过尊重 flush() 的机制启用了 gzip调用,例如 Apache 的 mod_deflate 模块,这种定期刷新将削弱其压缩尝试,并可能导致您的“压缩”输出大于未压缩时的输出。因此,对于一些适度但不小的 n 行,每输出 n 行显式调用 flush() 可能是一种更好的做法。
然后,将它们放在一起,您可能应该调整您的脚本,使其看起来像这样:
<?php
if (ob_get_level()) {
ob_end_clean();
}
$csv = 'title.csv';
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$csv\"" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
flush(); // Get the headers out immediately to show the download dialog
// in Firefox
$array = get_your_csv_data(); // This needs to be fast, of course
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $i => $fields)
{
fputcsv($fp, $fields, ';', '"');
if ($i % 100 == 0) {
flush(); // Attempt to flush output to the browser every 100 lines.
// You may want to tweak this number based upon the size of
// your CSV rows.
}
}
fclose($fp);
?>
如果这不起作用,那么我认为您无法从您的 PHP 代码中尝试解决问题 - 您需要弄清楚是什么导致您的 Web 服务器缓冲您的输出并尝试使用服务器的配置文件解决这个问题。