且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

生成大型CSV文件时将其下载到浏览器

更新时间:2023-02-14 21:32:00

根据经验,似乎当接收到具有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:

  1. 尽快将响应正文的第一个千字节刷新到浏览器,以便Chrome用户尽早看到文件下载对话框.
  2. 此后,定期向浏览器发送更多内容.

实现这些目标的方式可能是多个级别的缓冲,您可以尝试以不同的方式进行应对.

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.confnginx.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.

或者,每次尝试回显任何输出时,也可以通过调用 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.