当前位置: 首页 > news >正文

Docker 容器中的 HEAD 请求缺失 header?从 Content-MD5 缺失聊起

  • 背景
  • 开发环境说明
  • 问题排查过程
    • 1. 添加请求头仍无效
    • 2. curl 请求对比
    • 3. IP 对比:确认是不同出口
  • 问题原因分析:CDN 节点行为差异
  • 解决方案:使用 GET(stream=True) 替代 HEAD
    • 是否会有性能问题?
  • 总结
  • 参考

最近在开发过程中遇到了一个让我颇为困惑的问题:我用 Python 的 requests.head() 方法请求一个 MP3 文件,想从响应头中获取 Content-MD5,但在 Docker 容器中却总是拿不到这个 header。更奇怪的是,偶尔还能拿到。这个诡异的行为一度让我手足无措,但经过一番研究,我终于找到了问题的原因。

背景

在一个音频处理相关的项目中,我需要验证远端 MP3 文件的完整性,最简单的方法就是请求文件的 Content-MD5 header。起初我使用的是如下代码:

import requestsresponse = requests.head(url)
md5 = response.headers.get("Content-MD5")

在前一天提交的时候明明已经跑通了,但第二天上来却发生了错误:response 报 200 状态,但 headers 中没有 Content-MD5

开发环境说明

项目是在 Windows 10 上进行开发,容器由 Docker Desktop 管理,代码所在文件夹被挂载映射到 Docker 容器中,代码运行在一个标准的 Python 容器中。

dev env

正是这个开发环境暴露了这个问题,但也对后续debug造成了一定的困扰。

问题排查过程

1. 添加请求头仍无效

出现这个问题,我的第一个反应是因为没有对 requests.head(url) 做处理,它默认的行为导致 requests 发出的报文可能有什么特征被服务端标记处理了。在打印它的报文头后,发现 request 的 headers 中是空的。

起初我怀疑是请求中缺少 User-Agent,所以将浏览器的 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0 添加到 headers 中,结果是无效。

而我使用 curl -I 命令来请求,却能得到包含 “Content-MD5” 的 headers。我打印出curl的命令,发现它添加了 User-AgentAccept 等字段,于是尝试这样调用:

response = requests.head(url, headers={"User-Agent": "curl/7.88.1","Accept": "*/*"
})

然而并没有变化,Content-MD5 依旧缺失。这个现象真令人很纳闷,底层明明是相同的http请求报文,但得到的response却不相同。

2. curl 请求对比

在 Windows 下编辑的 python 代码,直接在docker中运行,结果是无效的;但我的 curl 命令执行是直接在 Windows 下执行的,结果是有效的。我是不是应该拉齐,在容器中执行 curl 命令看下呢?

接着我在 Docker 容器中使用 curl 工具请求:

curl -I "https://xxx.com/audio.mp3"

response 中没有 Content-MD5 字段。这下行为对齐了:同一个运行环境,同样的 http 请求,响应的 headers 是相同的。

3. IP 对比:确认是不同出口

至此,我只能想到 host 和 docker 容器二者的网络环境不同,导致了 response 的差异。

在 host 和 container 中执行 curl https://ifconfig.me 分别测试,得到如下结果:

  • Windows 10 Host IP(ifconfig.me): 2409:8a00:xxxx:xxxx::(IPv6)
  • Container IP(ifconfig.me): 120.245.xxx.xxx(IPv4)

发现它们使用了完全不同的公网 IP,容器中使用的是 IPv4,而 host 使用的是 IPv6。

问题原因分析:CDN 节点行为差异

回到 HEAD 请求得到的回复报文(以成功获得 Content-MD5 的响应报文为例):

HTTP/1.1 200 OK
Server: Tengine
Content-Type: audio/mpeg
Content-Length: 7657913
Connection: keep-alive
Date: Tue, 22 Jul 2025 02:57:42 GMT
x-oss-request-id: 687EFE26F7D692303097C0A7
x-oss-cdn-auth: success
Accept-Ranges: bytes
x-oss-object-type: Normal
x-oss-storage-class: Standard
x-oss-server-time: 75
Via: cache66.l2cn3147[76,75,304-0,H], cache6.l2cn3147[77,0], kunlun3.cn5506[0,0,200-0,H], kunlun8.cn5506[1,0]
Content-MD5: UgsxQSaumh0wUVm3LH/z9A==
ETag: "520B314126AE9A1D305159B72C7FF3F4"
Last-Modified: Thu, 20 Feb 2025 01:29:43 GMT
x-oss-hash-crc64ecma: 6532237343798154919
Age: 996
Ali-Swift-Global-Savetime: 1753153062
X-Cache: HIT TCP_MEM_HIT dirn:-2:-2 mlen:0
X-Swift-SaveTime: Tue, 22 Jul 2025 02:57:42 GMT
X-Swift-CacheTime: 3600
Timing-Allow-Origin: *
EagleId: 6f0db51c17531540582252451e

Via这个header记录了请求从客户端到服务器过程中经历的代理服务器或缓存节点的路径信息。通过它基本可以判断请求经历了一系列的CDN节点(阿里云的CDN节点有时以kunlun命名,包括OSS,也是阿里的服务)。

CDN为了就近加速、减轻源站压力,会将内容缓存到不同的边缘节点。而每个节点的缓存行为可能不完全一致:

  • 有的 CDN 节点可能裁剪掉某些非标准或无用的响应头
  • 某些节点可能只缓存 GET 请求的响应;
  • 有的服务(如阿里云 CDN)默认不会缓存 Content-MD5,除非配置白名单;
  • HEAD 请求可能会走更轻量的处理链路,导致 header 丢失或被省略。

并且,从阿里云的相关文档12中也可以找到一些信息,的确存在不响应 Content-MD5 的情况。

而没有响应 Content-MD5 的 response 报文中,它的 Via 头中节点不同:

'Via': 'cache66.l2cn3147[0,0,200-0,H], cache73.l2cn3147[0,0], kunlun8.cn496[19,18,200-0,M], kunlun4.cn496[21,0]'

虽然无法得到各个CDN节点的配置情况,但基本可以判断当前的情况是:容器和主机走的是不同的网络出口,也命中了不同的 CDN 边缘节点,不同的 CDN 节点对响应进行了不同的处理。

解决方案:使用 GET(stream=True) 替代 HEAD

为了确保拿到完整的 header,我尝试将 requests.head() 改为:

response = requests.get(url, stream=True)
md5 = response.headers.get("Content-MD5")
response.close()

这个方案立刻奏效:即使在 Docker 容器中也可以稳定拿到 Content-MD5 了。

是否会有性能问题?

HEAD 请求改为 GET(stream=True) 后,我最关心的是性能是否会受到影响。

相信很多人第一反应也是:“GET 不是会下载整个文件吗?这样不是带宽开销很大?”

答案是:不会(只要你不读取 body)

当设置 stream=True 时,requests 会延迟加载响应体3。如果仅访问 headers,不触发 response.contentresponse.text,就不会下载实际的 MP3 内容。

建议使用如下写法,确保资源被安全释放:

with requests.get(url, stream=True) as response:md5 = response.headers.get("Content-MD5")

总结

HTTP 请求行为不仅受客户端控制,服务端和中间层(CDN)也有很大影响:

  • 由于 Docker 和主机在网络出口上的差异,导致命中了 CDN 的不同节点;
  • 某些 CDN 节点对 HEAD 请求会裁剪响应头或走不同缓存路径;
  • 使用 GET(stream=True) 能更稳定地获取完整的 header,前提是不读取响应体

参考

  1. CDN加速OSS后未响应Content-MD5
  1. https://developer.aliyun.com/ask/388501
  1. requests 文档中的 stream 参数说明
http://www.lryc.cn/news/596898.html

相关文章:

  • BitDistiller:通过自蒸馏释放 Sub-4-Bit 大语言模型的潜力
  • BiLLM:突破大语言模型后训练量化的极限
  • AI安全“面壁计划”:我们如何对抗算法时代的“智子”封锁?
  • 主要分布在背侧海马体(dHPC)CA1区域(dCA1)的时间细胞对NLP中的深层语义分析的积极影响和启示
  • 使用 QLExpress 构建灵活可扩展的业务规则引擎
  • 糖尿病数据分析:血压与年龄关系可视化
  • OpenAI发布ChatGPT Agent,AI智能体迎来关键变革
  • Linux网络-------1.socket编程基础---(UDP-socket)
  • 基于数据挖掘的短视频点赞影响因素分析【LightGBM、XGBoost、随机森林、smote】
  • 应用层自定义协议【序列化+反序列化】
  • 2025暑期—06神经网络-常见网络
  • ChatGPT桌面版深度解析
  • 华为7月23日机考真题
  • TDengine 的 HISTOGRAM() 函数用户手册
  • 解决Spring事务中RPC调用无法回滚的问题
  • 解构未来金融:深入剖析DeFi与去中心化交易所(DEX)的技术架构
  • 【音视频学习】五、深入解析视频技术中的像素格式:颜色空间、位深度、存储布局
  • LoRA 低秩矩阵实现参数高效的权重更新
  • 新手向:Pycharm的使用技巧
  • python3写一个异步http接口服务调用大模型(async, sanic)---6.1
  • Hexo - 免费搭建个人博客04 - 创建另一个私人仓库,对Hexo项目进行版本管理
  • Log4j CVE-2021-44228 漏洞复现详细教程
  • Sklearn 机器学习 线性回归
  • 20250704-基于强化学习在云计算环境中的虚拟机资源调度研究
  • OpenCV 零基础到项目实战 | DAY 2:图像预处理全解析
  • 基于Seata的微服务分布式事务实战经验分享
  • 7月23号打卡
  • 四、cv::Mat的介绍和使用
  • 【趣味解读】淘宝登录的前后端交互机制:Cookie-Session 如何保障你的账户安全?
  • 密码学中的概率论与统计学:从频率分析到现代密码攻击