
本文介绍通过优化 PHP 文件下载逻辑来防范自动化脚本高频请求导致的服务器资源滥用,重点解决 readfile() 无法及时响应客户端断连的问题,并提供可中断的流式读取方案与基础防护建议。
本文介绍通过优化 php 文件下载逻辑来防范自动化脚本高频请求导致的服务器资源滥用,重点解决 `readfile()` 无法及时响应客户端断连的问题,并提供可中断的流式读取方案与基础防护建议。
在使用 PHP 提供文件下载服务(尤其是大体积媒体文件如 MP4)时,直接调用 readfile() 存在一个隐蔽但严重的安全隐患:它会阻塞执行、无视客户端连接中断。当恶意脚本反复触发下载链接后立即关闭连接(例如模拟点击后秒退),PHP 仍会完整读取并尝试输出整个文件——不仅浪费带宽与 I/O,更可能拖垮服务器性能。
根本原因在于:readfile() 是底层 C 实现的原子操作,PHP 在其执行期间无法检测 connection_aborted() 或响应用户中止信号,即使已调用 ignore_user_abort(false)(默认值)。因此,必须改用可控的分块读取方式,为每次 I/O 操作留出检查机会。
以下是一个健壮、可中断的替代方案:
<?php
// 启用输出缓冲(可选,便于统一控制)
ob_start();
// 获取并校验文件路径(关键!防止路径遍历)
$MP4Address = $_GET['MP4Address'] ?? '';
if (empty($MP4Address) || !is_file($MP4Address) || !is_readable($MP4Address)) {
http_response_code(404);
die('File not found or inaccessible.');
}
// 基础安全过滤:仅允许特定目录下的文件
$allowedDir = '/var/www/videos/';
if (strpos(realpath($MP4Address), $allowedDir) !== 0) {
http_response_code(403);
die('Access denied.');
}
$filename = basename($MP4Address);
// 设置响应头
header('Content-Type: video/mp4'); // 更准确的 MIME 类型
header('Content-Length: ' . filesize($MP4Address));
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: no-cache, must-revalidate');
header('Expires: 0');
// 关键:启用连接状态检测(PHP 默认为 false,显式确保)
ignore_user_abort(false);
// 分块读取并实时检测连接状态
$chunkSize = 8192;
if ($fh = fopen($MP4Address, 'rb')) {
while (!feof($fh) && connection_status() === CONNECTION_NORMAL) {
echo fread($fh, $chunkSize);
ob_flush();
flush(); // 立即推送至客户端,避免缓冲累积
usleep(1000); // 微小延迟,降低 CPU 占用(可选)
}
fclose($fh);
} else {
http_response_code(500);
die('Failed to open file.');
}
// 清理输出缓冲
ob_end_flush();
?>✅ 关键改进说明:
立即学习“PHP免费学习笔记(深入)”;
- 使用 fopen() + fread() 分块读取,每读一块即调用 connection_status() 检查连接是否已断开;
- ob_flush() 和 flush() 确保数据及时发送,避免因输出缓冲导致“假性”持续传输;
- 显式 ignore_user_abort(false) 强制 PHP 尊重客户端断连信号;
- 添加 realpath() 路径校验与白名单目录限制,杜绝路径遍历攻击;
- Content-Length 头有助于客户端预估进度,也便于反爬策略识别异常请求。
⚠️ 注意事项:
- flush() 在某些 Web 服务器(如 Nginx + FastCGI)或代理环境下可能被拦截,需配合 fastcgi_finish_request()(PHP ≥ 5.3.3)进一步优化;
- 对于高并发场景,仅靠应用层防护不够,建议叠加 Nginx 限流(limit_req)、IP 黑名单或行为分析(如单位时间请求数阈值);
- 避免将原始文件路径直接暴露在 URL 中(如 ?MP4Address=...),应使用映射 ID + 数据库查询,增加滥用门槛。
综上,放弃 readfile() 改用可控流式输出是解决下载滥用的第一步;结合路径校验、连接状态监控与基础设施级防护,才能构建真正健壮的媒体分发系统。



















