PHP 实现 HTTP Range 协议支持文件断点续传与分段下载
利用 PHP 处理 HTTP Range 协议是解决大文件断点续传和高效分段下载的常用方案。通过识别客户端的字节请求范围,服务端仅需输出对应片段并设置正确的 Response 头部(如 206 Partial Content),即可实现浏览器或下载工具的中断恢复功能,同时优化服务器负载。
Range 协议的作用场景
在 Web 服务中,当需要向用户输送大体积文件(如高清视频、大型安装包)时,直接输出全量文件会占用大量内存和带宽。HTTP Range 协议允许客户端请求文件的特定字节范围(例如 bytes=1000-2000),服务端响应部分数据。其主要价值包括:
- 断点续传: 若下载过程中途断开,客户端可从上次停止的字节位置继续请求,避免重复下载。
- 流媒体缓冲: 视频或音频播放器通常按小段范围预加载数据,提升播放流畅度并减少等待时间。
- 性能优化: 避免单次建立庞大的文件读入流,通过控制读取块大小降低 PHP-FPM 进程的内存峰值。
核心代码实现逻辑
以下是基于原生 PHP 实现的完整逻辑示例。该脚本接收文件名参数,校验文件存在性,解析 Range 头,并逐块输出二进制内容。
<?php
// 获取请求的文件名
$filename = $_GET['filename'];
// 构建服务器端文件路径(建议根据实际安全规范限制目录)
$location = 'media/' . $filename;
// 获取后缀以设定 MIME 类型
$extension = substr(strrchr($filename, '.'), 1);
if ($extension == "mp3") {
$mimeType = "audio/mpeg";
} else if ($extension == "ogg") {
$mimeType = "audio/ogg";
} else {
// 默认 fallback
$mimeType = "application/octet-stream";
}
// 1. 检查文件是否存在
if (!file_exists($location)) {
header("HTTP/1.1 404 Not Found");
return;
}
// 获取文件大小和修改时间
$size = filesize($location);
$time = date('r', filemtime($location));
// 2. 打开文件句柄
$fm = @fopen($location, 'rb');
if (!$fm) {
header("HTTP/1.1 505 Internal server error");
return;
}
// 3. 初始化范围边界
$begin = 0;
$end = $size - 1;
// 4. 解析 HTTP_RANGE 请求头
if (isset($_SERVER['HTTP_RANGE'])) {
// 匹配格式:bytes=start-end 或 bytes=start-
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]);
}
}
}
// 5. 设置状态码:有 Range 为 206,否则为 200 OK
if (isset($_SERVER['HTTP_RANGE'])) {
header('HTTP/1.1 206 Partial Content');
} else {
header('HTTP/1.1 200 OK');
}
// 6. 设置必要的响应头部
header("Content-Type: $mimeType");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
// 计算本次实际输出的长度
$contentLength = ($end - $begin) + 1;
header('Content-Length: ' . $contentLength);
if (isset($_SERVER['HTTP_RANGE'])) {
header("Content-Range: bytes $begin-$end/$size");
}
header("Content-Disposition: inline; filename=$filename");
header("Content-Transfer-Encoding: binary");
header("Last-Modified: $time");
// 7. 定位文件指针并开始读取
$cur = $begin;
fseek($fm, $begin, 0);
// 循环读取并输出,直到达到结束位置或连接断开
// connection_status() == 0 表示客户端连接正常
while (!feof($fm) && $cur <= $end && (connection_status() == 0)) {
// 每次读取 16KB 或剩余所需字节数,防止内存溢出
$readLength = min(1024 * 16, ($end - $cur) + 1);
print fread($fm, $readLength);
$cur += $readLength;
// 可选:适当休眠以避免占用过多 CPU
// usleep(1000);
}
fclose($fm);
?>
关键技术细节说明
第 1 步:验证文件与准备句柄
在执行任何读取操作前,必须通过 file_exists 确认目标文件位于预期路径下,防止越权访问。使用 fopen($location, 'rb') 以二进制只读模式打开文件,确保在处理非文本文件(如音视频)时不乱码。
第 2 步:正确解析 Request Header
重点在于正则匹配 $_SERVER['HTTP_RANGE']。常见的有效格式包括 bytes=0-(从头开始)、bytes=100-200(中间片段)以及 bytes=-500(最后500字节,需注意上述代码主要适配了前两种标准形式,若需支持尾部截取需额外处理正则)。一旦解析出起始字节 $begin 和结束字节 $end,即为后续 IO 提供依据。
第 3 步:响应头部的精准配置
断点续传能否生效,取决于头部字段的准确性:
- Accept-Ranges: bytes:告知客户端当前资源支持范围查询。
- Content-Range: bytes {start}-{end}/{total_size}:明确告诉客户端本次响应数据的起始点、结束点以及资源总长度,这对于客户端计算剩余进度至关重要。
第 4 步:分块读取与防断连
直接使用 fread($fm, filesize($location)) 尝试一次性将大文件读入缓冲区会导致 Out of Memory 错误。代码采用循环结构,每次仅读取固定长度(如 16KB)。同时,connection_status() == 0 的判断确保了如果用户在下载中途关闭页面,服务端能及时中止 I/O 操作,释放资源。
排错与安全提示
- 相对路径风险: 示例中的
'media/' . $filename属于相对路径,在生产环境中应改为绝对物理路径,且必须对$filename做严格校验(如白名单机制或使用basename()),防止../路径遍历攻击读取敏感文件。 - MIME 类型缺失: 示例仅处理了 mp3 和 ogg。若请求 MP4、PNG 或其他格式,因未定义
$mimeType变量可能导致 Header 报错。建议补充完整的映射数组或统一使用通用二进制流。 - 大文件超时: PHP 默认最大执行时间为 30 秒。对于极大的文件下载,需在脚本顶部设置
set_time_limit(0);或调整 php.ini 中的max_execution_time。
以上就是利用 PHP 实现 HTTP Range 协议支持断点续传的详细内容,更多关于 PHP 大文件处理及网络协议的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













