
在使用php自定义函数流式传输pdf文件并由PDF.js在浏览器中渲染时,开发者可能会遭遇“Invalid or corrupted PDF file”的间歇性错误。这类问题通常表现为部分文件正常显示,部分间歇性失败,甚至有些文件完全无法加载。尽管文件在本地阅读器(如Acrobat Reader)中表现正常,但PDF.js却报告“Invalid PDF structure”,这往往指向了文件传输或服务器配置层面的问题,而非PDF文件本身的损坏。
1. 问题现象与初步分析
当PDF.js在控制台抛出 Invalid or corrupted PDF file. Message: Invalid PDF structure. 错误时,意味着它接收到的PDF数据流不完整或格式不正确。这通常发生在以下场景:
- 文件传输中断: 网络不稳定、服务器超时或客户端连接断开导致文件未能完全传输。
- 服务器端处理异常: 服务器在处理文件流时引入了额外数据、截断了文件,或未正确设置http头部。
- 服务器配置限制: 服务器对文件大小、传输速率或脚本执行时间有隐式限制。
原始问题中,开发者使用了一个名为 smartReadFile 的php函数来处理PDF文件的流式传输。该函数旨在支持HTTP Range请求,允许浏览器进行断点续传或请求部分文件内容。
以下是 smartReadFile 函数的核心逻辑:
立即学习“PHP免费学习笔记(深入)”;
function smartReadFile($location, $filename, $mimeType = 'application/octet-stream') { if (!file_exists($location)) { header ("HTTP/1.1 404 Not Found"); return; } $size = filesize($location); $time = date('r', filemtime($location)); $fm = @fopen($location, 'rb'); if (!$fm) { header ("HTTP/1.1 505 Internal server error"); return; } $begin = 0; $end = $size - 1; // 处理HTTP Range请求 if (isset($_SERVER['HTTP_RANGE'])) { if (preg_match('/bytes=h*(d+)-(d*)[D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) { $begin = intval($matches[1]); if (!empty($matches[2])) { $end = intval($matches[2]); } } } // 设置HTTP状态码和头部 if (isset($_SERVER['HTTP_RANGE'])) { header('HTTP/1.1 206 Partial Content'); // 部分内容 } else { header('HTTP/1.1 200 OK'); // 完整内容 } header("Content-Type: $mimeType"); header('Cache-Control: public, must-revalidate, max-age=0'); header('Pragma: no-cache'); header('Accept-Ranges: bytes'); header('Content-Length:' . (($end - $begin) + 1)); // 传输的实际长度 if (isset($_SERVER['HTTP_RANGE'])) { header("Content-Range: bytes $begin-$end/$size"); // 内容范围 } if($_REQUEST['SaveAs'] == "1"){ header('Content-Disposition: attachment; filename=' . $filename); }else{ header("Content-Disposition: inline; filename="$filename""); // 在线显示 } header("Content-Transfer-Encoding: binary"); header("Last-Modified: $time"); // 实际文件流输出 $cur = $begin; fseek($fm, $begin, 0); while(!feof($fm) && $cur <= $end && (connection_status() == 0)) { print fread($fm, min(1024 * 16, ($end - $cur) + 1)); // 分块读取并输出 $cur += 1024 * 16; } fclose($fm); // 关闭文件句柄 }
该函数在逻辑上是健全的,它正确处理了HTTP Range请求,并设置了必要的HTTP头部以支持文件流式传输。开发者曾尝试调整 fread 的块大小,但并未解决问题,这进一步暗示问题可能不在PHP代码的直接逻辑错误。
2. 问题的根源:服务器环境差异
最终,问题的解决指向了一个关键因素:服务器环境。当将同样的代码部署到生产环境的Web服务器上时,问题神秘地消失了。这强烈表明,导致“Invalid or corrupted PDF file”错误的原因在于开发环境(windows 10上的iis)与生产环境之间的配置差异。
这种差异可能涉及以下几个方面:
2.1 IIS服务器配置排查
对于在IIS上运行PHP的场景,有几个常见的配置点可能影响大文件或流式传输:
- 请求过滤 (Request Filtering):
- 连接超时 (Connection Timeouts):
- 站点级别超时: IIS站点的连接超时设置可能过短,导致长时间的文件传输被中断。
- FastCGI超时: 如果PHP通过FastCGI运行,FastCGI的请求超时、活动超时和空闲超时都可能导致PHP脚本在传输完成前被终止。
- 输出缓冲 (Output Buffering):
- IIS或PHP的输出缓冲机制可能在某些情况下干扰二进制流的实时传输,尤其是在缓冲满后才刷新,或者在刷新前发生超时。
- 模块冲突: 其他IIS模块(如URL重写、压缩模块等)有时可能会意外地修改或中断二进制流。
2.2 PHP配置排查
除了IIS,PHP自身的配置也可能影响文件流式传输的稳定性:
- max_execution_time: PHP脚本的最大执行时间。如果文件传输时间超过此限制,脚本会被强制终止,导致文件传输不完整。对于大文件流式传输,可能需要将其设置为0(无限制)或足够大的值。
- memory_limit: PHP脚本的最大内存使用量。虽然流式传输通常不需要将整个文件加载到内存,但某些操作或PHP内部缓冲可能需要内存。
- output_buffering: PHP的输出缓冲机制。如果启用,PHP会先将输出存储在内部缓冲区中,直到缓冲区满或脚本结束才发送。这可能导致客户端在接收数据时出现延迟或不完整。对于流式传输,通常建议关闭或在传输过程中显式调用 flush()。
- fastcgi.buffer_size 和 fastcgi.flush_buffers (FastCGI模式下): 这些设置控制FastCGI如何缓冲PHP的输出。如果 fastcgi.flush_buffers 未启用,FastCGI可能会等待缓冲区填满才发送数据,这可能导致延迟和超时。
3. 诊断与解决策略
当遇到此类问题时,可以采取以下步骤进行诊断和解决:
- 对比环境配置: 仔细对比开发环境(IIS)和生产环境的IIS配置、PHP php.ini 配置,以及FastCGI配置。特别关注上述提到的超时、缓冲和限制设置。
- 简化测试: 在开发环境中,尝试使用最简单的PHP文件读取方式(如 readfile())来传输PDF,看是否仍出现问题。这有助于隔离问题是否与自定义 smartReadFile 函数的复杂性有关。
// 简化版文件传输(不处理Range请求) function simpleReadFile($location, $filename, $mimeType = 'application/pdf') { if (!file_exists($location)) { header("HTTP/1.1 404 Not Found"); return; } header("Content-Type: $mimeType"); header("Content-Length: " . filesize($location)); header("Content-Disposition: inline; filename="$filename""); readfile($location); }如果简化版工作正常,则问题可能在于 smartReadFile 对HTTP Range请求的处理,或者IIS在处理Range请求时有特殊行为。如果简化版仍有问题,则问题更可能出在IIS或PHP的基本文件传输配置上。
- 日志分析: 检查IIS日志、PHP错误日志以及FastCGI日志,查找是否有关于请求中断、超时或内存不足的错误信息。
- 逐步调整配置: 在开发环境中,尝试逐步调整IIS和PHP的配置参数,每次只修改一个,然后进行测试,以确定哪个参数是问题的根源。
- 增加 max_execution_time。
- 关闭 output_buffering。
- 调整FastCGI相关超时和缓冲设置。
- 网络抓包: 使用wireshark等工具在客户端和服务器端进行网络抓包,分析HTTP响应的完整性,检查是否有异常的TCP连接中断或HTTP头部错误。
4. 总结与最佳实践
“Invalid or corrupted PDF file”错误在流式传输场景下,往往是服务器环境配置不当的信号。尽管PHP代码本身可能看起来无懈可击,但服务器(如IIS)和PHP运行环境(如FastCGI)的隐式设置却可能在幕后干扰文件传输。
最佳实践:
- 环境一致性: 尽量保持开发环境与生产环境的配置一致性,以避免此类难以追踪的问题。
- 充分测试: 对大文件和长时间传输的场景进行充分测试,并监控服务器日志。
- 错误处理: 在PHP代码中加入更健壮的错误处理和日志记录,例如在 connection_status() != 0 时记录错误信息,以便更快地定位问题。
- 服务器优化: 对于高并发或大文件传输的场景,考虑对Web服务器进行性能优化,包括调整连接池、线程数、文件缓存等。
通过系统地排查服务器配置,并结合对文件流式传输机制的理解,可以有效诊断并解决这类由环境差异引起的间歇性文件损坏问题。