DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

urllib3 第二次建连报 Context cannot be mutated again

注入 PyOpenSSL 26.2 后,共享上下文在首次连接后被锁定,urllib3 再设 verify_mode 会失败。按进程启动边界恢复标准库 TLS,并保留 CA、身份校验与客户端证书配置。

同一个 HTTPS 连接池第一次请求完成,随后需要新建连接时却报:

ValueError: Context has already been used to create a Connection, it cannot be mutated again

如果程序调用过 urllib3.contrib.pyopenssl.inject_into_urllib3(),并把一个共享上下文传给连接池,需要检查 PyOpenSSL 26.2.0 起的上下文使用限制。这个场景有 urllib3 2.7.0 的明确案例:第一次连接被关闭后,第二次创建新连接失败;并不是每次复用同一个现存 socket 都必然触发。

失败发生在 TLS 参数设置阶段

PyOpenSSL 的 Context 创建过 Connection 后,不再允许若干会改变上下文的操作。urllib3 为新连接设置 context.verify_mode,PyOpenSSL 适配层又将它转成 Context.set_verify(),于是撞上“已经使用,不可再变更”的保护。

即使准备设置的校验模式看起来与之前相同,也仍然经过 setter。当前 urllib3 2.8.0 的这条正式路径仍会设置 verify_mode,不能只看到版本升级就认定所有共享 PyOpenSSL 上下文已经兼容。

先核对完整 traceback、是否有注入、上下文在哪创建,以及哪些依赖会自动进行注入。若错误只是证书验证失败、hostname 不匹配或服务器主动断开,应沿相应 TLS 分支处理,不属于这次 Context 状态冲突。

不再需要注入时,恢复标准库后端

支持现代 Python 的项目,应先确认是否仍有依赖要求 PyOpenSSL 注入。若已不需要,在应用启动配置中移除这次全局注入,创建新的标准库 SSLContext 与连接池,再用受控重启替换旧进程。

下面是标准库上下文与 urllib3 连接池的配置示意;ca_bundle 必须指向原项目已核实的 CA 文件,切换后端时应保留原 CA 配置:

import ssl
import urllib3

context = ssl.create_default_context(cafile=ca_bundle)
pool = urllib3.PoolManager(ssl_context=context)

如有客户端证书、私有 CA、TLS 最低版本、代理及服务器身份约束,应逐项迁移并验证。标准库上下文也需要在使用前配置完成;这段示意没有替项目补齐双向 TLS 或 HTTPS 代理的独立上下文。

注入会影响进程级别的 urllib3 行为。不要在并发请求之间反复调用注入/撤销函数,也不要在已有连接池仍活动时只替换一个模块变量,留下新旧后端混用。若其他依赖仍会重新注入,应先修正那条初始化入口。

确实必须保留 PyOpenSSL 时,需要由上层库提供兼容的上下文生命周期或完成兼容修复。不要通过改 _used、删除保护装饰器、关闭证书验证或长期回退安全依赖绕过它;这些动作不能保证共享上下文的行为正确。

验收必须强制覆盖第二次新建连接

只在同一条长连接上重复请求,可能完全碰不到触发点。应在获准的测试服务上先完成一次 HTTPS 请求,让连接按受控方式关闭,再通过同一份新配置创建后续连接,核对 TLS 握手与业务响应。

另外检查原来要求的 CA 验证、主机名验证、代理和客户端证书路径,确保恢复标准库后端没有放宽任何条件。保留原依赖与启动配置供恢复,若失败,不要在半替换的进程里继续切换全局后端。