Composer镜像本身不参与HTTP缓存协商,但若镜像服务正确返回ETag或Last-Modified响应头,客户端即可复用本地缓存;关键在于镜像后端响应头合规,而非修改Composer配置。

Composer 镜像本身不主动参与 HTTP 缓存协商,但镜像服务(如 packagist-mirror、私有 Satis / Toran / Private Packagist)若正确返回 ETag 或 Last-Modified,就能让 Composer 客户端复用本地缓存,显著降低重复请求的带宽消耗。关键不是改 Composer,而是确保镜像后端响应头合规。
Composer 会自动发送 If-None-Match 和 If-Modified-Since 吗?
会,但仅限于特定场景:当 Composer 已缓存某包的 composer.json 或 dist 归档(如 .zip)时,后续更新检查(composer update 或 composer install)会带上对应条件请求头。
-
composer.json类元数据请求 → 若之前响应含ETag,则本次发If-None-Match -
dist包下载(如https://mirrors.example.com/xxx/yyy.zip)→ 若之前响应含Last-Modified,则本次发If-Modified-Since - Composer 不缓存
packages.json(根索引)的完整响应体,因此该文件几乎从不触发 304 —— 这是设计使然,不是 bug
镜像服务必须返回合法 ETag 才能生效
ETag 必须是内容强校验值,且格式严格:双引号包裹、不含空格、推荐用内容哈希(如 SHA256)。Nginx 或 Apache 托管静态包时默认生成的 ETag(基于 mtime + size)在包内容不变但修改时间被覆盖时会失效,不适用于镜像场景。
- 对
composer.json:后端应计算文件内容 MD5 或 SHA256,输出ETag: "d41d8cd98f00b204e9800998ecf8427e" - 对
.zip/.tar.gz包:同样用内容哈希,而非文件系统时间戳;避免用stat时间,因为 rsync 或 cp 可能重置 mtime - 禁止返回
ETag: ""、ETag: null或未加引号的值(如ETag: abc123),否则 Composer 会忽略该头
为什么你的镜像返回了 304 却没节省带宽?
常见原因是响应体未清空或头字段冲突。Composer 收到 304 后仍会解析响应体(哪怕为空),若后端误传了 Content-Length: 0、Transfer-Encoding: chunked 或多余 Set-Cookie,Composer 可能拒绝复用缓存并降级为重新下载。
- 304 响应必须无响应体,且不能含
Content-Length、Transfer-Encoding、Content-Type等实体相关头 - 镜像服务若在所有响应中统一设置
Set-Cookie(如用于统计),需在 304 路径中显式清除,否则 Nginx 或反向代理可能拦截并转为 200 - 验证方式:用
curl -I -H "If-None-Match: \"abc123\"" https://mirror.example.com/pkg/composer.json,确认返回纯HTTP/1.1 304 Not Modified且无额外头
真正起效的点不在 Composer 配置,而在镜像服务能否稳定输出可验证的 ETag 并干净返回 304 —— 少一个引号、多一行空行、错一个哈希,缓存链就断了。


















