PHP:在使用fwrite时已发送头,但在使用fputcsv时未发送头
我知道这个错误背后的理论,但是现在它又让我发疯了。我正在我的应用程序中使用。使用此库,您可以将所有流量重定向到PHP:在使用fwrite时已发送头,但在使用fputcsv时未发送头,php,file-io,http-headers,Php,File Io,Http Headers,我知道这个错误背后的理论,但是现在它又让我发疯了。我正在我的应用程序中使用。使用此库,您可以将所有流量重定向到dispatch.php脚本,然后该脚本执行相应的资源,该资源返回一个响应,由dispatch.php显示(输出) Response的输出方法如下: /** * Output the response */ public function output() { foreach ($this->headers as $name => $value) {
dispatch.php
脚本,然后该脚本执行相应的资源
,该资源
返回一个响应
,由dispatch.php
显示(输出)
Response
的输出方法如下:
/**
* Output the response
*/
public function output()
{
foreach ($this->headers as $name => $value) {
header($name.': '.$value, true, $this->responseCode());
}
echo $this->body;
}
因此,这告诉我们,您不能在资源
中向php输出写入任何内容
我现在有了一个资源
,它可以从输入csv动态生成csv并将其输出到浏览器(它将一列数据转换为不同的格式)
这是百分百的好。标题没有问题,生成并提供了正确的文件供下载。这已经令人困惑,因为我们在设置标题之前写入输出?fputcsv实际上做什么
我有第二个资源,做类似的事情,但它输出一个自定义文件格式(文本文件)
唯一的区别是它使用fwrite而不是fputcsv和bang
headers already sent by... // line number = fwrite()
这太令人困惑了!在这两种情况下,它究竟应该如何失败?为什么第一个有效?我怎样才能让第二个开始工作?
(我可以生成一个包含该文件的巨大字符串,并将其放入响应正文中,它可以工作。但是文件可能相当大(高达50 mb),因此希望避免出现这种情况。)
$record
未设置,生成一个级别为的错误,请注意
。如果您有error\u报告到true
,PHP将在发送标题之前将此错误放入输出中
将“错误报告”设置为false,并监视日志。这里是我的解决方案。我暂时不会把它作为答案,因为也许有人会想出比这个更好(更简单)的方法
首先是关于fwrite和fputcsv的评论:
fputcsv有一个完全不同的源代码,与fwrite没有太多共同之处(它在内部不调用fwrite,它在C源代码中是一个单独的函数)。因为我不懂C,我说不出他们为什么会有不同的行为,但他们确实如此
解决方案:
生成的文件可能“很大”,这取决于输入,因此通过字符串连接生成整个文件并将其保存在内存中不是一个很好的解决方案
我在谷歌上搜索了一下,找到了apache的modxsendfile。这是通过在php中设置一个自定义头来实现的,该头包含要发送给用户的文件的路径。然后,mod删除该自定义头并将该文件作为响应发送
mod_xsendfile的问题是它与mod_rewrite不兼容,我也使用mod_rewrite。您将得到404个错误。要解决这个问题,您需要添加
RewriteCond %{REQUEST_FILENAME} !-f
到apache配置中相应的位置(如果请求是实际存在的文件,则不要重写)。然而,这还不够。您需要在未重写的php脚本中设置头X-Sendfile,它是一个实际存在的php文件
因此,在最后生成文件的\Tonic\Resource类中,我重定向到上述脚本:
$response->__set('location', $url . "?fileName=" . urlencode($fileName));
$response->code = \Tonic\Response::FOUND;
return $response;
在我们重定向到上面代码段中的下载脚本中,只需执行以下操作(省略验证内容):
浏览器将显示生成文件的下载对话框
您还需要创建cron作业来删除生成的文件
/usr/bin/find /path/to/generatedFiles/ -type f -mmin +60 -exec rm {} +
这将删除目录/path/to/generatedFiles
中超过60分钟的所有文件
我使用ubuntu服务器,因此您可以将其添加到文件中
/etc/cron.daily/standard
或在该目录中生成新文件,或在包含该命令的/etc/cron.hourly
中生成新文件
注:
我在输入csv文件的sha1散列之后命名生成的文件,因此名称是唯一的,如果有人在短时间内多次重复相同的请求,您可以再次返回已生成的文件。您是否应该将fwrite
写入$response
而不是$output
?这类问题的已知答案当然,我知道这一点,正如我在问题中所说的,实际上是为了防止这种评论。如果禁用错误报告并查看日志,会发生什么情况?如果我生成字符串而不是写入文件并输出它是否工作。我的“文件创建逻辑”正确,所有变量都已设置。问题在于如何正确返回生成的内容,特别是考虑到ti的大小可能有几MB。代码将内容放在标题之前。找出那是什么内容,你就有了答案。如果你读过我的问题,我知道这一点。只是说写信给php://output 使用fwrite()->错误。使用fputcsv->works。为什么?(AFAIK fputcsv()在内部使用fwrite())我理解这一点。但会有一条消息告诉你原因。试着得到那个信息。您可以做的另一件事是使用Wireshark嗅探脚本输出的实际内容。
$filePath = trim($_GET['fileName']);
header ('X-Sendfile: ' . $filePath);
header ('Content-Disposition: attachment; filename="' . $filePath . '"');
/usr/bin/find /path/to/generatedFiles/ -type f -mmin +60 -exec rm {} +
/etc/cron.daily/standard