curl访问HTTPS返回304却没有正文:条件请求、缓存与证书验收排查

用curl检查HTTPS接口,退出码是0,响应状态却是304,下载文件没有新内容。别重签证书,也别用空文件覆盖旧产物。回环实验分开TLS、HTTP与文件验收。浏览器缓存与curl下载不是一回事。

一、304没有正文,是协议行为

RFC9110第15.4.5节规定,304 Not Modified表示条件GET或HEAD原本会得到200,但条件求值阻止了传输。它指向已有表示;304在头部结束处终止,不能携带内容或尾部字段。没有正文不是下载中途断了。

图为条件GET检查关系,不是完整协议时序,也不是实测日志。配图由DM-Fox生成,经技术与可读性复核。

二、先留版本与三层证据

本次实测是Linux上的curl7.61.1、OpenSSL1.1.1k FIPS,临时CA与回环TLS1.2、HTTP/1.1服务。仅测试隔离环境,没有修改系统信任库,也不代表CDN、生产浏览器或HTTP/2的实际行为。先记录构建:

bash 复制代码
curl --version
openssl version
证据 回答的问题
curl退出码、TLS错误 这次传输有没有失败
HTTP状态和响应头 服务器如何处理请求条件
本地文件、校验值与业务契约 能否采用这个结果

本例没有关闭证书验证。错误CA会让curl在HTTP请求前失败;信任与名称正确后,服务器仍可返回304、200或500。退出码不是HTTP状态,HTTP状态也不是业务完成证明。日志先脱敏。

三、找到是谁发出了条件请求

优先检查脚本里的If-None-Match、If-Modified-Since、-z,以及客户端配置。If-None-Match携带实体标签;ETag是服务器给表示的验证器,不保证等于内容哈希,也不能拿证书指纹替代。demo-v1仅为隔离样本:

bash 复制代码
curl -q --noproxy '*' --http1.1 -sS --max-time 15 \
  -H 'If-None-Match: "demo-v1"' \
  -D conditional.headers -o conditional.body \
  -w 'http=%{http_code} bytes=%{size_download}\n' \
  https://example.com/data
rc=$?
printf 'curl_exit=%s\n' "$rc"
exit "$rc"

本次服务收到匹配的If-None-Match后返回304与同一ETag,curl退出0、下载字节0。-f也不会把304变成错误22,因为它不是400以上的HTTP错误。本轮304创建了零字节输出文件,不能当新正文。

-q放在第一个参数只用于跳过默认curlrc;它不会清除所有环境影响。--noproxy显式绕过代理,仅适合已授权可直连的检查环境。生产必须经代理时保留真实链路,不能为了通过检查绕过控制。

四、想取新正文,先去掉请求条件

同一URL,不发送条件头再做一次GET,能够区分"服务返回不变"与"资源确实有内容"。不要用-k换取成功。下面命令不手动发送条件字段:

bash 复制代码
curl -q --noproxy '*' --http1.1 -sS --max-time 15 \
  -D fresh.headers -o fresh.body \
  -w 'http=%{http_code} bytes=%{size_download}\n' \
  https://example.com/data
rc=$?
printf 'curl_exit=%s\n' "$rc"
exit "$rc"

隔离服务这时返回200、正文OK加换行,curl下载3字节。匹配标签为304,不匹配标签为200,错误CA为60且服务没有收到HTTP请求。这些是本机构建的观察,不是所有故障的固定退出码清单。

若无条件GET仍收到304,核对实际发出的头、代理改写和服务端实现;不要自动认定"本地缓存有效"。Cache-Control: no-cache表示希望缓存先重验证,不等于保证每次返回200,也不是删除所有请求条件的快捷键。

五、复用缓存必须绑定到同一份表示

只有持有与验证器对应的缓存正文,且满足请求目标、选择表示的字段以及缓存规则时,304才有复用基础。至少把URL、必要的Vary维度、验证器、正文和采样信息绑定保存;认证身份切换也不能随意复用另一个人的缓存。

较新curl的--etag-save与--etag-compare可保存和发送ETag,但它们不是完整缓存管理器;两者均在7.68.0加入。本次7.61.1不支持,正文因此采用显式头部对照,不声称实测了新选项。通用缓存还须遵循RFC9111。

六、下载脚本宁可拒绝,也别覆盖错文件

下面是Bash单请求示例,不复用缓存:只接受200和精确的OK加换行。保存为check.sh,在可写的独占工作目录执行bash check.sh;example.com/data是占位地址,实际使用须换成你获授权的接口,并替换内容契约。私有CA通过受控--cacert指定,不能信任对端随手发来的根。

bash 复制代码
#!/usr/bin/env bash
set -eu
umask 077
work=$(mktemp -d ./download.XXXXXX) || exit 1
trap 'rm -rf -- "$work"' EXIT
if status=$(curl -q --noproxy '*' --http1.1 -sS -f \
  --connect-timeout 5 --max-time 15 \
  -o "$work/body" -w '%{http_code}' \
  https://example.com/data); then
  :
else
  rc=$?
  printf 'transfer_failed=%s\n' "$rc" >&2
  exit "$rc"
fi
[ "$status" = 200 ] || { printf 'unexpected_http=%s\n' "$status" >&2; exit 1; }
printf 'OK\n' > "$work/expected"
cmp -s "$work/expected" "$work/body" || { printf 'content_failed\n' >&2; exit 1; }
mv -- "$work/body" ./result.txt
printf 'accepted\n' 

临时目录建在目标同级,检查成功才移动文件;失败不动result.txt。这里只提供单写者演示,不承诺并发更新事务或断电持久性。二进制下载应比对经可信渠道取得的摘要或签名,不要把"文件非空"升级成完整性保证。

七、正反例决定脚本能否上线

本轮对照 应如何处理
匹配条件、304、无正文 诊断可退出0;单次下载脚本拒绝
200且内容正确 校验后替换目标
200但空体或内容错误 拒绝并保留旧目标
500或证书信任失败 保留非零退出,不用旧文件冒充成功

本文完整代码从最终HTML抽取后执行,条件命中、不命中、错误CA,以及包装脚本的200正确/错误内容、空正文、304和500均做了隔离对照。测试临时私钥与监听用完清理;未改真实站点。

  • 保留curl构建、实际URL、脱敏请求条件和响应头。
  • TLS验证保持开启,HTTP状态与退出码分别记录。
  • 304复用前确认缓存正文和验证器归属;不明则停止。
  • 只在状态及内容验收通过后更新产物,失败保留旧文件。

八、参考资料

参考实现不代替本地文件与业务验收。

相关推荐
NEAI_N1 小时前
嵌入式Linux调试:microcom 串口工具
linux
叼烟扛炮1 小时前
Linux 学习笔记(九):自动化构建神器 Make 与 Makefile
linux·运维·服务器
lisanmengmeng1 小时前
监控Kubernetes集群的GPU资源(三)
linux·容器·kubernetes
蜡笔羊驼2 小时前
ModuleNotFoundError: No module named ‘torchvision.transforms.functional_tensor‘
linux·人工智能·python
箓维2 小时前
线程池&&线程安全与可重入
linux·网络·安全
程序员-Benothing2 小时前
Linux端口与连接排查:ss、lsof、nc、telnet、curl
linux·运维·服务器
邪修king2 小时前
Re:Linux系统篇(三十七):进程间通信Chapter4:System V 共享内存深度解析:为什么它是最快的 IPC?
android·linux·运维
李纲明3 小时前
微信小程序 web-view 打开空白或提示未配置业务域名:校验文件、HTTPS 与跳转域名排查
前端·微信小程序·https·web-view·小程序开发·业务域名
张张123y3 小时前
2026 Agent 框架怎么选?拆 3 层底层逻辑(运行循环 / 记忆 / 工具协议)+ 一张决策表
linux·ubuntu·microsoft·langgraph·mcp·crewai·agent 框架