get_headers 获取 Content-Length 不可靠,因重定向、分块传输、动态脚本及 CDN 缓存常导致其为空或错误;应改用 cURL 手动控制 HEAD 请求并 fallback 到 Range 头检测。

get_headers 在 PHP 中确实能拿到 Content-Length,但**绝大多数情况下它不靠谱——尤其是对重定向、分块传输、动态脚本或 CDN 缓存后的响应**。
为什么 get_headers 返回的 Content-Length 经常是空或错的
HTTP 协议本身不要求服务器返回 Content-Length;现代服务更倾向用 Transfer-Encoding: chunked 或直接省略该头。PHP 的 get_headers 默认只发 HEAD 请求,而很多 Web 服务器(比如 Nginx + PHP-FPM)对 HEAD 的处理会丢掉 Content-Length,哪怕 GET 能拿到。
- 遇到 301/302 重定向时,
get_headers默认跟随,但中间跳转响应可能没Content-Length,最终返回的是最后一跳的头——而那一跳可能是空响应体或网关页 - PHP 脚本(如
download.php?id=123)通常用readfile()或fopen() + stream_copy_to_stream()输出,压根不设Content-Length头 - CDN 或反向代理(如 Cloudflare)可能剥离或伪造原始头,
Content-Length变成不可信值
真要获取远程文件大小,得换方法:用 curl 发 HEAD 并手动控制行为
比 get_headers 更可控,关键是显式关闭重定向、设置超时、检查真实响应码,并允许 fallback 到 GET(带 Range 头只取首字节)。
- 先用
curl_setopt($ch, CURLOPT_NOBODY, true)发HEAD,同时设CURLOPT_FOLLOWLOCATION为false - 检查
curl_getinfo($ch, CURLINFO_HTTP_CODE)是否为200;若为3xx,需手动解析Location头再试一次(不能依赖自动跳转) - 从
curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD)读大小——这个值是 cURL 内部解析响应头后提取的,比自己array_search更稳 - 如果该值为
-1或空,说明头里没给,可退化为GET+Range: bytes=0-0,然后看Content-Range头里的总长度(如bytes 0-0/1234567)
示例关键逻辑:
立即学习“PHP免费学习笔记(深入)”;
$ch = curl_init('https://example.com/file.zip');
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_exec($ch);
$size = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
curl_close($ch);
别忽略 Content-Range 和 Accept-Ranges 这两个“备胎”头
有些服务(特别是静态文件托管或支持断点续传的 API)虽然不返回 Content-Length,但会返回 Accept-Ranges: bytes,配合一次带 Range: bytes=0-0 的 GET 请求,就能从 Content-Range 响应头里拿到完整尺寸。
-
Content-Range: bytes 0-0/8388608→ 总大小就是8388608 - 若响应码是
206 Partial Content,说明服务支持分片,Content-Range可信;若是200,那这个Range请求被无视了,不能采信 - 注意:某些 CDN(如阿里云 OSS)对小文件会合并响应,
Range请求可能直接返回完整内容且状态码为200,此时需靠Content-Length或Content-Range是否存在来判断
真正难的不是写几行代码,而是得根据目标 URL 的实际响应特征动态选策略:先 HEAD,失败就 Range,再失败才考虑下载整个头(不推荐)。没人能保证一个函数调用就搞定所有远程地址。











