Webman需手动解析Range头、校验范围、fseek定位并分块输出,返回206状态码及严格符合RFC 7233的Accept-Ranges、Content-Range、Content-Length响应头,禁用gzip,确保流式输出不爆内存且连接安全关闭。

Webman 默认不处理 Range 请求,直接用 readfile() 或 fpassthru() 做下载时,大文件断点续传会失败——浏览器发 Range: bytes=1000-,服务端却返回 200 + 全量内容,客户端判定“续传无效”,自动重头开始。
Webman 怎么识别并响应 Range 请求
Webman 是基于 PSR-7 的框架,请求头需从 $request->getHeaderLine('Range') 获取,不能依赖 $_SERVER['HTTP_RANGE'](尤其在 Swoole 环境下该值不可靠)。必须手动解析、校验、定位、分块输出。
- 先检查
$request->getHeaderLine('Range')是否非空,再用正则/^bytes=(\d+)-(\d+)?$/提取起始和可选结束位置 - 若匹配失败(比如
Range: abc),直接返回 416;不要 fallback 到全量下载 - 用
filesize($path)获取真实大小,判断$start是否 ≥ 文件大小,超界就返回 416 - 若
$end为空(如bytes=1000-),则设为$size - 1 - 最终片段长度为
$end - $start + 1,必须严格等于Content-Length值
Webman 流式输出分片内容不爆内存
不能用 file_get_contents() 或 readfile(),也不能 fread($fp, filesize($path)) —— 这些都会把整段内容读进 PHP 内存。必须用 fseek() 定位后,循环 fread($fp, 8192) 输出。
- 打开文件用
fopen($path, 'rb'),避免文本模式换行转换干扰二进制 -
fseek($fp, $start)前务必确认$fp有效,否则后续fread返回 false - 每次
fread($fp, 8192)后检查返回值:空字符串表示 EOF,false表示读取失败(如磁盘 I/O 错误) - 用
echo输出每一块,并在循环中调用ob_flush()和flush()(仅当未启用zlib.output_compression时有效) - 最后必须
fclose($fp),且在异常分支(如 416 返回前)也要确保关闭
Webman 下载响应头必须严格符合 RFC 7233
少一个头、错一个值,Chrome/Firefox 就可能拒绝接受分片响应,降级为全量重下。重点不是“有没有”,而是“对不对”。
- 状态码必须是
HTTP/1.1 206 Partial Content,不能只写206 -
Accept-Ranges: bytes必须存在,且值只能是bytes(不能是none或空) -
Content-Range格式必须为bytes 1000-1999/123456:中间是短横线,末尾是总大小(不能写*) -
Content-Length必须等于本次输出的字节数(即$end - $start + 1),不是整个文件大小 - 禁用
Content-Encoding: gzip(Swoole 默认不压缩,但若配了 Nginx 反向代理,需关掉gzip on和proxy_buffering)
Webman 中容易被忽略的底层细节
Webman 运行在 Swoole 上,没有传统 PHP-FPM 的 fastcgi_finish_request(),也无法用 ignore_user_abort()。一旦连接断开,Swoole 会立即关闭协程,但 fread 循环可能卡在系统调用里——这不是 bug,是协程调度模型决定的。
- 别在下载逻辑里做耗时操作(如查 DB、调 API),所有权限校验、路径白名单过滤必须在进入流式输出前完成
- 文件路径必须绝对、真实、不在 Web 目录下,用
realpath()+is_file()+is_readable()三重校验 - 文件名要过滤:去掉路径遍历符(
../)、控制字符、NUL 字节,用basename()+ 正则/^[a-zA-Z0-9._\- ]+$/限制 - 如果用
Response::create()->withBody(...)包装流,注意 PSR-7 Body 必须实现__toString()或支持getContents(),而文件指针流不满足——得自己构造StreamInterface实例或直接 echo 输出
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











