如何让PHP脚本在接收大文件上传时同步执行?
解决Apache+PHP(非FastCGI)大文件上传实时处理缓冲问题
问题根源
你遇到的核心问题是Apache默认会将完整的请求体(即便是chunked编码)缓冲到磁盘或内存后,才会转发给mod_php模块执行脚本,导致PHP代码要等上传100%完成后才启动,无法在上传过程中通过php://input实时处理数据。
解决方案步骤
1. 修改Apache配置,禁用请求体缓冲
在Apache的虚拟主机配置文件(如/etc/apache2/sites-available/000-default.conf)或全局httpd.conf中添加以下配置:
# 强制Apache不缓冲请求体,直接传递给PHP SetEnv no-buffering 1 # 禁用请求体的gzip压缩,避免额外缓冲 SetEnv no-gzip 1 # 关闭TCP_DEFER_ACCEPT,防止Apache等待完整请求再处理 AcceptFilter http none
修改后重启Apache服务(Docker容器内执行apache2ctl restart)。
2. 调整PHP配置,关闭自动POST数据解析
编辑php.ini文件,修改以下参数:
# 仅解析GET和COOKIE变量,跳过POST自动解析 request_order = "GP" # 完全关闭PHP自动读取POST数据到$_POST的功能 enable_post_data_reading = Off
这一步是关键——如果开启enable_post_data_reading,PHP会自动读取整个php://input内容填充$_POST,强制触发缓冲。
3. 编写实时处理的PHP脚本
使用流式读取方式处理php://input,避免一次性加载全部数据到内存,示例代码如下:
<?php // 脚本启动立即打日志,验证是否实时触发 error_log("上传流程启动,开始接收数据"); // 开启隐式刷新,确保实时输出(如果需要给客户端返回进度) ob_implicit_flush(true); ob_end_flush(); // 打开输入流 $inputStream = fopen('php://input', 'rb'); if (!$inputStream) { error_log("无法打开php://input流"); http_response_code(500); exit; } // 打开目标输出文件 $targetPath = '/var/www/uploads/large_file.dat'; $outputStream = fopen($targetPath, 'wb'); if (!$outputStream) { error_log("无法创建目标文件:{$targetPath}"); fclose($inputStream); http_response_code(500); exit; } // 分块读取并写入,每次处理4MB数据 $chunkSize = 4 * 1024 * 1024; $totalBytes = 0; while (!feof($inputStream)) { $chunk = fread($inputStream, $chunkSize); if ($chunk === false) { error_log("读取数据块失败"); break; } fwrite($outputStream, $chunk); $totalBytes += strlen($chunk); // 实时记录上传进度 error_log("已接收:{$totalBytes} 字节"); } // 关闭流资源 fclose($inputStream); fclose($outputStream); error_log("上传完成,总接收字节数:{$totalBytes}"); http_response_code(200); echo "上传成功"; ?>
4. Docker环境额外注意事项
- 确保Apache配置修改在容器中生效:如果使用官方Apache+PHP镜像,可以通过Dockerfile复制自定义配置文件,或在
docker-compose.yml中挂载宿主机的配置文件到容器对应目录。 - 磁盘空间:虽然上传期间内存稳定,但要确保容器挂载的存储目录有足够空间容纳大文件,避免磁盘耗尽导致上传失败。
内容的提问来源于stack exchange,提问作者Simao Gomes Viana
相关产品推荐
相关产品推荐

