问题描述
我有一个脚本,该脚本使用 fputcsv
并将其发送到浏览器.它可以工作,但是在服务器端生成整个CSV文件之前,浏览器不会显示文件下载提示(或开始下载文件),这需要很长时间.
I have a script that generates a large CSV file using fputcsv
and sends it to the browser. It works, but the browser doesn't show the file download prompt (or start downloading the file) until the whole CSV file has been generated serverside, which takes a long time.
相反,我希望在文件的其余部分仍在生成的同时开始下载.我知道这是可能的,因为这是PHPMyAdmin中导出数据库" 选项的工作方式-即使您的数据库很大,只要单击导出"按钮,下载就会立即开始.
Instead, I'd like the download to begin while the remainder of the file has still being generated. I know this is possible because it's how the 'Export database' option in PHPMyAdmin works - the download starts as soon as you click the 'export' button even if your database is huge.
如何调整下面的现有代码,以便立即开始下载?
How can I tweak my existing code, below, to let the download begin immediately?
$csv = 'title.csv';
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$csv\"" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $fields)
{
fputcsv($fp, $fields, ';', '"');
}
fclose($fp);
exit();
推荐答案
根据经验,似乎当接收到具有Content-Disposition: attachment
标头的响应时,不同的浏览器将在以下时刻显示文件下载对话框:
Empirically, it seems that when receiving responses featuring a Content-Disposition: attachment
header, different browsers will show the file download dialog at the following moments:
- Firefox在收到标题后立即显示该对话框
- Internet Explorer一旦接收到标题和响应正文的255个字节,便显示该对话框.
- Chromium接收到标题和响应正文的1023个字节后,就会显示该对话框.
那么,我们的目标如下:
Our objectives, then, are as follows:
- 尽快将响应正文的第一个千字节刷新到浏览器,以便Chrome用户尽早看到文件下载对话框.
- 此后,定期向浏览器发送更多内容.
实现这些目标的方式可能是多个级别的缓冲,您可以尝试以不同的方式进行应对.
Standing in the way of these objectives are, potentially, multiple levels of buffering, which you can try to fight in different ways.
如果您具有 output_buffering
设置为Off
以外的值,PHP将自动创建一个输出缓冲区,该缓冲区存储脚本尝试发送到响应 body 的所有输出.您可以通过确保从php.ini
文件或Web服务器配置文件(例如apache.conf
或nginx.conf
)将output_buffering
设置为Off
来防止这种情况.或者,您可以在脚本的开头使用 ob_end_flush()
或 ob_end_clean()
:
If you have output_buffering
set to a value other than Off
, PHP will automatically create an output buffer which stores all output your script tries to send to the response body. You can prevent this by ensuring that you have output_buffering
set to Off
from your php.ini
file, or from a webserver config file like apache.conf
or nginx.conf
. Alternatively, you can turn off the output buffer, if one exists, at the start of your script using ob_end_flush()
or ob_end_clean()
:
if (ob_get_level()) {
ob_end_clean();
}
由您的网络服务器完成缓冲
一旦您的输出超过了PHP输出缓冲区,它可能会被您的Web服务器缓冲.您可以尝试通过定期(例如每100行)调用 flush()
来解决此问题,尽管PHP手册对于提供任何担保不愿提供帮助,并列出了某些可能失败的特殊情况:
Buffering done by your webserver
Once your output gets past the PHP output buffer, it may be buffered by your webserver. You can try to get around this by calling flush()
regularly (e.g. every 100 lines), although the PHP manual is hesitant about providing any guarantees, listing some particular cases where this may fail:
刷新PHP的写缓冲区以及PHP正在使用的任何后端(CGI,Web服务器等).这样做有一些注意事项,试图将电流输出一直推到浏览器.
Flushes the write buffers of PHP and whatever backend PHP is using (CGI, a web server, etc). This attempts to push current output all the way to the browser with a few caveats.
flush()可能无法覆盖Web服务器的缓冲方案...
flush() may not be able to override the buffering scheme of your web server ...
多个服务器,尤其是Win32上的服务器,仍将缓冲脚本的输出,直到脚本终止,然后再将结果传输到浏览器.
Several servers, especially on Win32, will still buffer the output from your script until it terminates before transmitting the results to the browser.
Apache的服务器模块(例如mod_gzip)可能会自行缓冲,从而导致 flush()不会导致数据立即发送到客户端.
Server modules for Apache like mod_gzip may do buffering of their own that will cause flush() to not result in data being sent immediately to the client.
或者,每次尝试回显任何输出时,也可以通过调用 ob_implicit_flush
在脚本的开头-尽管要注意,如果您通过尊重flush()
调用的机制(例如Apache的mod_deflate
模块)启用了gzip,定期刷新会削弱其压缩尝试,并可能导致压缩"输出比未压缩输出大.因此,对于某些适度但不是很小的 n ,每输出 n 行显式调用flush()
也许是一种更好的做法.
You can alternatively have PHP call flush()
automatically every time you try to echo any output, by calling ob_implicit_flush
at the start of your script - though beware that if you have gzip enabled via a mechanism that respects flush()
calls, such as Apache's mod_deflate
module, this regular flushing will cripple its compression attempts and probably result in your 'compressed' output being larger than if it were uncompressed. Explicitly calling flush()
every n lines of output, for some modest but non-tiny n, is thus perhaps a better practice.
将它们放在一起,然后,您可能应该调整脚本,使其看起来像这样:
Putting it all together, then, you should probably tweak your script to look something like this:
<?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服务器缓冲您的输出并尝试使用服务器的配置文件解决该问题.
If this doesn't work, then I don't think there's anything more you can do from your PHP code to try to resolve the problem - you need to figure out what's causing your web server to buffer your output and try to solve that using your server's configuration files.
这篇关于生成大型CSV文件时将其下载到浏览器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!