强制下载PDF文件,文件损坏

7
我遇到了一个在SO上经常出现的问题,但我似乎找不到我的解决方案! 我试图将pdf文件传递给客户端而不是在浏览器中打开它,文件下载了,但是当我打开它时文件损坏并且原始文件缺少相当多的字节。 我尝试了几种下载文件的方法,但我只会展示我最近使用的,希望能得到一些反馈。

此外,我还在文本编辑器中打开了下载的PDF文件,没有php错误在顶部!

我也知道readfile()更快,但为了测试目的,我迫切需要让任何东西工作,所以我使用了while(!feof())方法!

无论如何,足够闲扯了,这是代码(来自为什么我的下载文件总是受损或损坏?):

$file     = __DIR__ . '/reports/somepdf.pdf';
$basename = basename($file);
$length   = sprintf("%u", filesize($file));

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $basename . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $length);

ob_clean();
set_time_limit(0);
readfile($file);

需要注意的是文件大小的差异:

Original: 351,873 bytes
Downloaded: 329,163 bytes

1
你尝试过使用readfile()函数吗? - barbashov
@DavidC799:如果您想讨论先前问题的答案,请在那里留下评论。不要只是在这里放一些代码,然后告诉我们“它不起作用”。请记住,只有其他人接受了该答案,这并不意味着该代码对您也必须有效。为了测试目的,请将代码减少到最少以引发问题。例如,没有函数,只有硬编码的文件名。使用readfile。 - hakre
@barbashov 是的,我尝试过几种不同的方法。 - David C
@DavidC799 或许将 Content-type 改为 application/pdf 会有帮助?顺便问一下,头部返回的 Content-length 值是多少? - barbashov
3个回答

8

确保您没有运行任何压缩输出缓冲处理程序,例如ob_gzhandler。我遇到过类似的情况,必须禁用输出缓冲才能正常工作。


成功了!非常感谢您的帮助,总是这么简单 :( - David C
@hakre 对不起,浪费了您的时间,呵呵! - David C
@DavidC799:我认为你没有浪费任何人的时间,这是一个合理的、不明显的问题,我个人认为提得很好。 - periklis
@periklis:你在回答中没有解释为什么从技术上讲这是一个问题(或者为什么必须是一个问题)。您能解释一下为什么不能使用ob_gzhandler和输出吗?我看不出为什么gz_handler应该被禁用。 - hakre
@DavidC799:这不是浪费时间,而是要找出正确的答案:https://dev59.com/fXDYa4cB1Zd3GeqPDLBg#15889925 - hakre
显示剩余2条评论

7
您正在使用 ob_gzhandler 来处理输出缓冲区。
它通过 gzencoding 输出的块来工作。然后,输出是一系列编码块的流。
每个块需要一些字节来进行编码,因此直到有足够的字节可用之前,输出会稍微缓冲一下。
然而,在您的脚本结束时,您丢弃了剩余的缓冲区而不是刷新它。
请改用 ob_end_flush() 而不是 ob_clean(),这样文件就可以完整地传输而且不会损坏。
当您在输出缓冲区在完成其工作之前破坏它时,您也可以将 ob_gzhandler 的传输编码与文件上传一起使用而没有任何问题。
如果启用了任何其他分块工作的输出缓冲,情况也是如此。
示例代码:
$file     = __DIR__ . '/somepdf.pdf';
$basename = basename($file);
$length   = sprintf("%u", filesize($file));

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $basename . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $length);

ob_end_flush();   // <--- instead of ob_clean()

set_time_limit(0);
readfile($file);

return;

(FYI:实际上,即使没有ob_end_flush();,重要的是不要让输出缓冲区在完成工作之前被强制关闭。)

我可以确认hakre的代码按预期工作;感谢您提供详细的解释。 - periklis
我刚刚检查了PHP中的readfile()源代码,如果可能的话,你应该始终使用它而不是像问题中那样自己使用fopen/缓冲读取。readfile()对于文件下载输出来说要优于自己编写的fopen/缓冲读取,因为它可以利用SAPI中的优势,而这在用户空间代码中是不可能的。 - hakre

-1

在解决我的问题之前,我花了两天时间尝试使用content-disposition来推送PDF下载。我的PDF文件大小也较小且损坏,但是我可以在Windows预览中打开它们,只是无法在Adobe中打开。经过大量的故障排除,我发现Adobe期望在文件的前1024个字节中包含%PDF。在创建标头之前,我在我的php代码中进行了所有文件类型检查。我删除了大部分标头之前的代码,然后我的PDF文件就修复了。

您可能没有像我一样设置它,但可能存在相同的问题:

http://helpx.adobe.com/acrobat/kb/pdf-error-1015-11001-update.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接