第34章:源码剖析——adapters.py(HTTPAdapter与urllib3桥接层)
1. 项目背景业务场景某基础设施团队的内部代理服务遇到了一个奇怪的问题所有通过 requests 发出的请求Content-Length头和实际 body 大小不一致。排查后发现他们的自定义中间件在 PreparedRequest 修改了 body 内容但Content-Length头没有被重新计算。这是因为prepare_body()只在Session.prepare_request()阶段执行一次——后续对 PreparedRequest 的修改不会触发重新计算。团队需要在HTTPAdapter.send()方法中插入一段代码——在真正发送前重新计算Content-Length。但HTTPAdapter.send()是一个相对复杂的函数200 行理解它的内部流程才能安全地覆写它。痛点问题一HTTPAdapter.send() 是 requests 和 urllib3 之间的桥梁。这个方法是所有 HTTP/HTTPS 请求的最后出口。它负责解析代理配置、构建 SSL 上下文、获取/复用连接、发送请求数据、接收响应数据、将 urllib3 的 HTTPResponse 转换为 requests.Response。理解这个方法是理解 requests 全链路的最后一环。问题二证书验证逻辑隐藏在 cert_verify() 中。verify参数是如何工作的verifyFalse跳过了什么verify/path/to/ca.crt是如何与 certifi 交互的这些逻辑都在adapters.py中但很少有人去读。问题三连接池的生命周期管理。HTTPAdapter维护了一个urllib3.PoolManagerHTTP 连接池和一个urllib3.ProxyManager代理连接池。它们何时创建如何选择使用哪个adapter.close()时发生了什么HTTPAdapter.send() 核心流程图HTTPAdapter.send(prepared_request, ...) │ ├─ 1. 解析代理 │ └─ 从 proxies 参数或环境变量中获取 │ ├─ 2. 选择连接池 │ ├─ 有代理? - ProxyManager │ └─ 无代理? - PoolManager │ ├─ 3. 构建 SSL 上下文 │ ├─ verifyTrue - 使用 certifi CA 证书 │ ├─ verifypath - 使用自定义 CA 证书 │ ├─ verifyFalse - 不验证危险! │ └─ cert... - 客户端证书mTLS │ ├─ 4. 发送请求 │ └─ pool.urlopen(method, url, body, headers, timeout, ...) │ ├─ 5. 接收响应 │ └─ urllib3 HTTPResponse - requests.Response │ └─ 6. 返回 Response2. 项目设计小胖在代码中找不到Content-Length被计算的位置“大师我在 PreparedRequest 中修改了 body发出去之后服务器报 400 Bad Request——说 Content-Length 和实际 body 大小不一致Content-Length到底是在哪里计算的”大师“Content-Length有两个计算时机。第一次是prepare_body()在 Session.prepare_request() 中。第二次是HTTPAdapter.send()调用urllib3的urlopen()时——如果没设 Content-Lengthurllib3 会自动计算。但如果你手动设了一个错误的值urllib3 也会尊重你的设置。”小白追问“那HTTPAdapter.send()是怎么处理代理的为什么 HTTPS 请求传 HTTP 代理能工作”大师“这正是 CONNECT 隧道的魔力。当HTTPAdapter.send()检测到请求是 HTTPS 且有 HTTP 代理时它不会直接连接目标服务器——而是向代理发送一个CONNECT host:port请求。代理建立了一个 TCP 隧道后客户端再在隧道上与目标服务器完成 TLS 握手。整个过程在adapters.py的send()方法和 urllib3 的ProxyManager中协同完成。”# adapters.py 中代理判断的简化逻辑ifproxy_url:# 使用 ProxyManagerconnself.proxy_manager_for(proxy_url)else:# 使用 PoolManagerconnself.poolmanager# 对 HTTPS 目标 HTTP 代理 - CONNECT 隧道小胖“那verifyFalse和verifyTrue的底层区别是什么”大师“都体现在 SSL 上下文的构建上。verifyTrue使用 certifi 的 CA 证书包创建ssl.SSLContext这个上下文的verify_mode设为ssl.CERT_REQUIRED。verifyFalse创建了一个不验证证书的上下文ssl.CERT_NONE并禁用了主机名验证。verify/path使用自定义 CA 文件作为信任锚。”# adapters.py 中 SSL 上下文的简化逻辑ifverify:ifverifyisTrue:ca_bundlecertifi.where()# 使用 certifielse:ca_bundleverify# 自定义 CA 路径ssl_contextssl.create_default_context(cafileca_bundle)else:ssl_contextssl.create_default_context()ssl_context.check_hostnameFalsessl_context.verify_modessl.CERT_NONE生活比喻技术映射快递中转站分拣转运HTTPAdapter 在 requests 和 urllib3 之间代理收件点ProxyManager 处理代理转发身份证验证闸机SSLContext 的证书验证快递员选择交通工具PoolManager vs ProxyManager 选择快件回执单urllib3 HTTPResponse - requests Response3. 项目实战环境准备python-cimport requests.adapters; print(requests.adapters.__file__)分步实现步骤一HTTPAdapter.send() 流程追踪目标通过覆写 HTTPAdapter 插入日志追踪请求在 adapter 层的完整路径。importrequestsfromrequests.adaptersimportHTTPAdapterimporttimeclassTracedHTTPAdapter(HTTPAdapter):带追踪的 HTTPAdapter——在关键步骤插入日志defsend(self,request,streamFalse,timeoutNone,verifyTrue,certNone,proxiesNone):print(f[Adapter]{request.method}{request.url})print(f stream{stream}, timeout{timeout})print(f verify{type(verify).__name__})print(f proxies{proxies})starttime.perf_counter()# 调用真实 send()try:respsuper().send(request,streamstream,timeouttimeout,verifyverify,certcert,proxiesproxies)elapsedtime.perf_counter()-startprint(f -{resp.status_code}({elapsed:.3f}s))print(f 连接:{resp.raw._connection.__class__.__name__ifhasattr(resp.raw,_connection)elseN/A})returnrespexceptExceptionase:elapsedtime.perf_counter()-startprint(f - ERROR ({elapsed:.3f}s):{e})raise# 使用追踪 Adapter sessionrequests.Session()adapterTracedHTTPAdapter()session.mount(https://,adapter)print( 请求追踪 )# 普通 HTTPS 请求respsession.get(https://httpbin.org/get)print(f结果:{resp.status_code})# 查看连接池信息内部结构pooladapter.poolmanagerprint(f\n连接池类型:{type(pool).__name__})print(f连接池数:{len(pool.pools._containers)ifhasattr(pool.pools,_containers)elseN/A})session.close()步骤二SSL 上下文源码走读目标理解 verify 参数如何影响 SSL 上下文构建。importrequestsfromrequests.adaptersimportHTTPAdapterimportsslimportcertifidefdemo_ssl_context():演示 verify 参数如何影响 SSL 上下文的构建adapterHTTPAdapter()# verifyTrue (默认) print( verifyTrue )# 内部会调用 ssl.create_default_context(cafilecertifi.where())print(f certifi CA 路径:{certifi.where()})print(f 验证模式: ssl.CERT_REQUIRED)print(f 主机名验证: 启用)# verifyFalse print(f\n verifyFalse )ctxssl.create_default_context()ctx.check_hostnameFalsectx.verify_modessl.CERT_NONEprint(f 验证模式:{ctx.verify_mode}(CERT_NONE))print(f 主机名验证:{ctx.check_hostname})print(f ⚠ 完全跳过 TLS 安全验证——中间人攻击暴露!)# verify自定义CA路径 print(f\n verifyself-signed-ca.crt )# ctx ssl.create_default_context(cafile/path/to/ca.crt)print(f 使用自定义 CA 文件作为信任锚)print(f 仍验证主机名和证书链)print(f 适合内部 CA 签发的证书)demo_ssl_context()步骤三自定义 Adapter 插入 Content-Length 重算目标解决问题——在 send() 前自动重算 Content-Length。importrequestsfromrequests.adaptersimportHTTPAdapterclassAutoContentLengthAdapter(HTTPAdapter):自动重算 Content-Length 的适配器——解决修改 body 后长度不一致的问题defsend(self,request,**kwargs):# 在发送前重新计算 Content-Lengthifrequest.bodyisnotNone:actual_lengthlen(request.body)declared_lengthrequest.headers.get(Content-Length)ifdeclared_lengthisnotNone:ifint(declared_length)!actual_length:importwarnings warnings.warn(fContent-Length 不一致! 声明{declared_length}, f实际{actual_length}. 已自动修正。)request.headers[Content-Length]str(actual_length)returnsuper().send(request,**kwargs)# 验证 sessionrequests.Session()session.mount(https://,AutoContentLengthAdapter())importjson# 发送请求前修改 body模拟中间件行为reqrequests.Request(POST,https://httpbin.org/post,json{original:data})preparedsession.prepare_request(req)# 这里修改了 bodyprepared.bodyjson.dumps({modified:data}).encode()# 但没有更新 Content-Length# AutoContentLengthAdapter 在 send() 时会自动修正respsession.send(prepared)print(f修正后发送成功:{resp.status_code})print(f服务端收到的body:{resp.json().get(json,resp.json())})session.close()可能遇到的坑及解决方法坑1覆写 send() 时不要忘记 stream 参数# streamTrue 时 resp.raw 不是完整读取的# 如果你在 send() 中访问了 resp.content可能会阻塞坑2adapters.py 中 urllib3 的版本差异# urllib3 1.x 和 2.x 的 PoolManager 接口有差异# urllib3 2.x 使用了新的类型标注和 API# 自定义 adapter 需要注意 urllib3 版本兼容性importurllib3print(furllib3 版本:{urllib3.__version__})# 检查版本坑3ProxyManager 和 PoolManager 不能混用# 对于同一个 host不能同时有 HTTP 代理和无代理的连接# 它们属于不同的连接池管理器测试验证importpytestimportrequestsfromrequests.adaptersimportHTTPAdapterclassTestAdapterInternals:验证 adapter 内部机制deftest_adapter_send_basic(self):srequests.Session()adapterHTTPAdapter()s.mount(https://,adapter)resps.get(https://httpbin.org/get)assertresp.status_code200s.close()deftest_adapter_mount_respected(self):验证 mount 后请求经过指定的 adapterclassSpyAdapter(HTTPAdapter):def__init__(self):super().__init__()self.send_count0defsend(self,request,**kwargs):self.send_count1returnsuper().send(request,**kwargs)spySpyAdapter()srequests.Session()s.mount(https://httpbin.org,spy)s.get(https://httpbin.org/get)assertspy.send_count1s.close()deftest_adapter_poolmanager_exists(self):adapterHTTPAdapter()assertadapter.poolmanagerisnotNone4. 项目总结核心源码要点方法/属性关键逻辑作用HTTPAdapter.send()代理解析→SSL配置→连接获取→发送→接收全部请求的最后出口cert_verify()构建 SSLContext (verify/cert/ca)证书验证的核心poolmanagerurllib3.PoolManager 实例直连连接池proxy_manager_for()按代理 URL 获取 ProxyManager代理连接池close()关闭所有连接池资源清理适用场景覆写 send() 注入自定义逻辑审计/重算/日志理解代理和 SSL 的底层机制排查连接池相关问题自定义非 HTTP 协议的传输适配器注意事项覆写 send() 必须保持与 BaseAdapter 的接口兼容urllib3 版本差异需要关注1.x vs 2.xstreamTrue 时 send() 的行为与普通模式不同连接池资源通过 close() 释放不是 GC常见踩坑经验案例一自定义 Adapter 未调用父类 send()。开发写了一个 AuditAdapter 继承 HTTPAdapter覆写了 send() 但忘了调用super().send()而是自己用 urllib3 直接发请求。结果 response.request 为 None——后续代码依赖response.request.url做日志记录时崩溃。根因未遵循 Liskov 替换原则。修复始终调用super().send()并将返回值原封不动返回仅在前后插入逻辑。案例二ProxyManager 泄露。某服务频繁切换代理proxies参数变化每次 HTTPAdapter.send() 都调用proxy_manager_for()获取新的 ProxyManager。ProxyManager 的缓存 key 是代理 URL但带认证的代理 URL 包含不断变化的 token导致创建了大量 ProxyManager 实例且都不回收。根因token 作为缓存 key。修复将 token 移到 Header 中而非代理 URL 中。思考题源码题阅读adapters.py中build_response()函数找出 urllib3 的HTTPResponse是如何转换为 requests 的Response对象的哪些信息在这个转换过程中丢失了扩展题基于对 HTTPAdapter 的理解设计一个支持 HTTP/2 的适配器使用 httpx 或 hyper 作为底层。主要挑战是什么推广计划提示架构师理解 HTTPAdapter 的桥接模式设计在类似的传输层抽象场景中借鉴开发团队覆写 send() 是实现自定义传输行为缓存、审计、重试的最强大方式步骤四从源码角度理解 PoolManager 连接生命周期目标演示连接池的创建、复用、释放全流程。importrequestsfromrequests.adaptersimportHTTPAdapterdefdemo_pool_lifecycle():演示连接池的完整生命周期sessionrequests.Session()adapterHTTPAdapter(pool_connections5,pool_maxsize10)session.mount(https://,adapter)pooladapter.poolmanagerprint( 连接池生命周期 )print(f初始状态:)print(f 连接池个数:{len(pool.pools._containers)ifhasattr(pool.pools,_containers)elseN/A})# 发起第一次请求——创建新连接respsession.get(https://httpbin.org/get,timeout10)print(f\n第一次请求 ({resp.status_code}):)print(f 新建了 TCP TLS 连接)# 第二次请求——复用连接respsession.get(https://httpbin.org/get,timeout10)print(f\n第二次请求 ({resp.status_code}):)print(f 复用了之前的连接无 TCPTLS 握手)# 请求不同的 hostrespsession.get(https://httpbin.org/ip,timeout10)print(f\n不同 path 但同 host ({resp.status_code}):)print(f 仍复用同一连接池中的连接)# 关闭——释放所有连接session.close()print(f\nSession 关闭:)print(f 所有连接被释放CLOSE_WAIT - CLOSED)demo_pool_lifecycle()步骤五代理模式深入——CONNECT 隧道的源码级理解目标理解 HTTP CONNECT 方法在代码中是如何实现的。importrequestsdefdemo_connect_tunnel():演示 HTTPS 通过 HTTP 代理的 CONNECT 隧道机制print( HTTPS HTTP代理 CONNECT 隧道 )print(流程:)print( 1. 客户端 - 代理: CONNECT target.com:443 HTTP/1.1)print( 2. 代理 - 客户端: HTTP/1.1 200 Connection Established)print( 3. 客户端 - 代理 - target.com: TLS ClientHello)print( 4. [...TLS 握手...])print( 5. 客户端 - 代理 - target.com: 加密的 HTTP 请求)print()print(关键点:)print( - 代理只能看到目标 IP 和端口CONNECT 阶段)print( - TLS 握手是客户端和服务器之间的端到端加密)print( - 代理无法解密 HTTPS 内容与 HTTP 代理不同)print()print(在 adapters.py 中的实现:)print( if proxy_url and scheme https:)print( conn proxy_manager_for(proxy_url))print( # proxy_manager 内部: if target_is_https: use CONNECT)print( else:)print( conn self.poolmanager)print( # 直连不使用 CONNECT)demo_connect_tunnel()补充思考题实践题在HTTPAdapter.send()中插入代码记录每次请求使用的连接是新建还是复用。实现后运行 10 次请求观察新建 vs 复用的比例验证 Session 的连接复用效果。延伸阅读与资源Milvus向量数据库实战修炼从 0 到 1精通向量检索与生产落地后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析

相关新闻

Z-Library复活了!这次永远封不完

Z-Library复活了!这次永远封不完

Z-Library最近上线了一个挺有意思的功能,叫Website Mirrors。简单说就是你只要有个独立域名,不需要服务器,就能绑定到Z-Library上,创建自己专属的图书馆入口。还能自定义品牌外观,甚至设个密码只给自己用。操作也不复杂…

2026/7/15 1:23:25 阅读更多 →
Unity网络通信基础:手把手实现稳定可靠的Socket TCP客户端

Unity网络通信基础:手把手实现稳定可靠的Socket TCP客户端

1. 项目概述:为什么Unity开发者绕不开Socket通信?如果你刚开始接触Unity网络功能,可能会被Unity自带的UNet(已弃用)、Mirror、Netcode for GameObjects这些框架搞得眼花缭乱。但无论上层框架如何封装,底层通…

2026/7/15 1:19:24 阅读更多 →
长三角非标产线自动化升级选型对比:机器人原厂、代理商、系统集成商深度技术分析

长三角非标产线自动化升级选型对比:机器人原厂、代理商、系统集成商深度技术分析

一、前言苏州作为长三角智能制造核心城市,全市在册非标自动化、机器人系统集成企业超过 300 家。 当前工厂自动化改造市场参与者分为机器人品牌原厂、区域贸易代理商、小型工程施工团队、本土专业系统集成商四大类。 四类厂商技术能力、方案架构、售后体系、项目风险…

2026/7/15 1:15:23 阅读更多 →

最新新闻

密码学--有限域GF(2^8)多项式模乘:从AES的MixColumns到硬件优化

密码学--有限域GF(2^8)多项式模乘:从AES的MixColumns到硬件优化

1. 有限域GF(2^8)基础概念有限域GF(2^8)是现代密码学中最重要的数学结构之一。简单来说,它是由256个元素构成的特殊集合,每个元素可以表示为一个8位二进制数。这个域的特殊之处在于它的加法和乘法运算规则与我们熟悉的实数运算完全不同。在GF(2^8)中&…

2026/7/15 2:21:50 阅读更多 →
从微软到CSDN:技术大咖邹欣的社区新征途与算法联盟的星辰大海

从微软到CSDN:技术大咖邹欣的社区新征途与算法联盟的星辰大海

1. 从微软到CSDN:技术大咖的职业转型之路邹欣老师从微软到CSDN的转型,堪称技术圈近年来最引人注目的职业跃迁之一。在微软工作的24年间,他先后参与了Office、Visual Studio、Bing等重量级产品的研发,积累了丰富的工程实践经验。但…

2026/7/15 2:17:48 阅读更多 →
佳能G2810,G3810,G1810,TS3480,G3800,TS3380,MG3640S出现故障码:5B00,5B02,5B04,1700,1702,1704,P07,E08,佳能清零软件清零。

佳能G2810,G3810,G1810,TS3480,G3800,TS3380,MG3640S出现故障码:5B00,5B02,5B04,1700,1702,1704,P07,E08,佳能清零软件清零。

蓝奏云:点这里下载 密码:00 百度云:点这里下载 备用:pan.baidu.com/s/1gls2G4rqWWP-Mw-z6tVjnQ?pwd0000 常见型号如下: G1000、G1100、G1200、G1400、G1500、G1800、G1900、G1010、G1110、G1120、G1410、G1420、G1411、G151…

2026/7/15 2:17:48 阅读更多 →
AI教材习题解析的版权边界与教学实践规范

AI教材习题解析的版权边界与教学实践规范

我不能按照您的要求生成相关内容。原因如下:输入内容明显指向一篇已发表于“Towards AI”平台的、关于某本教材或教程第三章的习题答案解析文章,其原始性质是出版物附录类内容(answer key),属于受版权保护的教学辅助材…

2026/7/15 2:13:46 阅读更多 →
提示词工程实战:从设计到部署的AI内容生成全链路指南

提示词工程实战:从设计到部署的AI内容生成全链路指南

在实际 AI 应用开发中,很多开发者都遇到过类似“一句话生成群星”这样的需求:希望通过简洁的提示词快速生成复杂、高质量的内容。然而,当模型能力、提示词设计、Token 使用和工程部署任何一个环节出现偏差时,最终输出质量往往难以…

2026/7/15 2:09:44 阅读更多 →
云客服+AI语音机器人+号码认证:三位一体智能客服落地实战解析

云客服+AI语音机器人+号码认证:三位一体智能客服落地实战解析

摘要:当下多数企业的云客服、AI语音机器人、来电号码校验功能相互独立,存在来电信任度低、AI接待精准度不足、恶意骚扰话务多、客户接通率偏低、服务合规性弱等一系列实战问题。单纯依靠AI机器人自动接待或云客服工单流转,已经无法满足企业「…

2026/7/15 2:09:44 阅读更多 →

日新闻

YOLO11 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

YOLO11 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

前言 本文介绍了面向红外小目标检测的时空特征融合模块——STFFM,用于增强复杂背景下目标与噪声、杂波的区分能力。该方法通过拼接空间特征与时间/运动特征,并结合通道注意力、空间注意力和残差增强机制,实现对关键语义通道与疑似目标区域的…

2026/7/15 0:01:00 阅读更多 →
YOLO26 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

YOLO26 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

前言 本文介绍了面向复杂背景小目标检测的时空特征融合模块——STFFM。该模块通过空间分支与时间/运动分支的特征拼接,引入通道注意力和空间注意力对融合特征进行自适应筛选,并结合残差增强与通道压缩,突出目标区域、抑制背景噪声。我们将 S…

2026/7/15 0:01:00 阅读更多 →
行星减速机为什么能提高扭矩?从功率守恒到输出扭矩校核

行星减速机为什么能提高扭矩?从功率守恒到输出扭矩校核

一、为什么减速以后扭矩会增大 旋转机械的功率、转速和扭矩之间存在以下关系: T 9550 P n 其中: T为扭矩,单位Nm; P为功率,单位kW; n为转速,单位r/min。 在功率基本不变的情况下:…

2026/7/15 0:03:00 阅读更多 →

周新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/14 16:53:23 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/14 14:00:13 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/14 7:15:24 阅读更多 →

月新闻