PHP 8.3 中需手动解析 $_SERVER['HTTP_RANGE']:先 trim() 清空格,再用 preg_match('/bytes=(\d+)-(\d+)?/', $range, $matches) 提取起始/结束位置,校验 $start < $fileSize,设 206 Partial Content 及严格格式的 Content-Range 与 Content-Length,禁用输出缓冲和 gzip,优先交由 Nginx 处理 Range。

PHP 8.3 中如何正确解析 $_SERVER['HTTP_RANGE']
PHP 8.3 没有自动处理 Range 请求的能力,必须手动提取并校验。常见错误是直接用 explode('-', $range) 而不清理前缀或空格,导致解析失败。
实际请求头可能是:Range: bytes=1024-2047、Range: bytes=1024- 或带空格/换行的变体(如某些安卓 WebView 发送的 Range: bytes=1024-\n)。
- 先用
trim()去首尾空白,再用preg_match('/bytes=(\d+)-(\d+)?/', $range, $matches)提取起始位;末尾为空时按文件末尾处理 - 务必检查
$start < $fileSize,否则返回416 Requested Range Not Satisfiable - 忽略
If-Range头会导致文件被修改后仍返回旧分片——PHP 8.3 中需显式读取$_SERVER['HTTP_IF_RANGE']并比对ETag或最后修改时间
为什么不能用 readfile() 或 fpassthru() 实现续传
这两个函数会从文件开头全量加载或输出,完全无视 fseek() 定位,导致客户端收到重复数据或 500 错误。尤其在 PHP 8.3 的严格类型和内存管理下,大文件极易触发 Fatal error: Allowed memory size exhausted。
- 必须用
fopen($file, 'rb')+fseek($fp, $start)定位 - 循环
fread($fp, 8192)输出,每次不超过 8KB,避免缓冲区堆积 - PHP 8.3 默认启用
output_buffering,需在脚本开头加ini_set('output_buffering', 'Off')和ini_set('implicit_flush', 1) -
ob_end_clean()和flush()在 Nginx + PHP-FPM 架构下基本无效,真正起作用的是禁用缓冲配置
PHP 8.3 下响应头设置的关键细节
漏设或错设响应头会导致浏览器拒绝接受分片,尤其是 iOS Safari 和部分安卓 WebView 对 Content-Range 格式极其敏感。
立即学习“PHP免费学习笔记(深入)”;
- 必须返回
HTTP/1.1 206 Partial Content,不能只写206 -
Content-Range必须严格匹配格式:bytes 1024-2047/1048576(注意空格、斜杠、无单位) -
Content-Length必须是本次响应体字节数($end - $start + 1),不是整个文件大小 - Nginx 若开启
gzip on,会压缩整个响应体,破坏分片长度一致性——必须在 location 块中加gzip off
生产环境建议交给 Nginx 处理 Range
PHP 8.3 的核心优势在于类型安全和 JIT 性能,但 HTTP 协议层的 Range 解析与流式响应仍是 Web 服务器更擅长的事。让 PHP 只做鉴权和重定向,既安全又高效。
- 在 PHP 中验证用户权限后,用
header('X-Accel-Redirect: /internal/' . rawurlencode($filePath)) - Nginx 配置
location /internal/ { internal; alias /var/www/files/; },它会自动识别Range并返回 206 - 避免 PHP 自己读文件、设头、控制流——这省去了所有内存/超时/编码/BOM 问题
- 若必须 PHP 输出(如动态生成加密文件),则务必关闭
zlib.output_compression,否则压缩会干扰字节范围
Nginx 不处理 Range 时,PHP 8.3 的续传逻辑才真正暴露问题:fseek() 偏移计算错误、Content-Range 少个空格、Content-Length 算错一位,都会让 iOS 设备静默失败——这些细节无法靠“运行一下试试”发现,只能靠严格校验和真实终端抓包确认。



















