requests 不读取 REQUESTS_CA_BUNDLE 与代理环境变量?prepared request 流程与 trust_env 的排查方法
【免费下载链接】requestsA simple, yet elegant, HTTP library.项目地址: https://gitcode.com/GitHub_Trending/re/requests
用 requests 时设置了REQUESTS_CA_BUNDLE(自签名/内部 CA 证书包)或http_proxy、https_proxy等代理环境变量,请求却像没读到这些配置一样,HTTPS 请求抛出SSL: CERTIFICATE_VERIFY_FAILED,或流量根本没走代理。官方文档明确指出这类问题最常见于prepared request 流程:该流程不会自动读取环境变量。本文基于 requests(当前仓库版本 2.34.2)的 Advanced Usage、Authentication 文档及 Session 源码,给出确认现象、定位原因和修复的完整路径。
第一步:确认普通请求流程确实会读环境变量
先排除“环境变量本身没生效”的可能。requests 在普通调用路径(requests.get()或Session.request())中会读取以下环境变量:
- CA 证书包:
REQUESTS_CA_BUNDLE;未设置时回退到CURL_CA_BUNDLE(见 SSL Cert Verification 一节)。 - 代理:
http_proxy、https_proxy、no_proxy、all_proxy,大写变体同样支持;代理 URL 中可用http://user:password@host/语法带认证(见 Proxies 一节)。
文档给出的验证方式是导出变量后直接发一个请求:
$ export REQUESTS_CA_BUNDLE="/usr/local/myproxy_info/cacert.pem" $ export https_proxy="http://10.10.1.10:1080" $ python >>> import requests >>> requests.get('https://example.org')其中/usr/local/myproxy_info/cacert.pem与http://10.10.1.10:1080是文档示例值,替换成你自己的 CA 文件路径和代理地址。判断标准:普通调用下 TLS 校验通过、请求正常返回,说明环境变量设置本身没有问题。
还可以查一下当前环境默认信任的证书包路径,确认REQUESTS_CA_BUNDLE覆盖的是哪份默认列表(Proxies 一节):
from requests.utils import DEFAULT_CA_BUNDLE_PATH print(DEFAULT_CA_BUNDLE_PATH)结论分两种:普通流程正常、只有 prepared 流程报错,问题出在 prepared 流程本身(下一节);所有流程都不读环境变量,检查是否把trust_env设成了False(最后一节)。
为什么 prepared request 流程不读环境变量
Prepared Requests 一节 给出了标准流程:构造Request→prepare()得到PreparedRequest→ 直接s.send(prepped, ...):
from requests import Request, Session s = Session() req = Request('GET', url) prepped = req.prepare() resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout )文档对此有明确警告(advanced.rst 第 190–193 行):
When you are using the prepared request flow, keep in mind that it does not take into account the environment. This can cause problems if you are using environment variables to change the behaviour of requests. For example: Self-signed SSL certificates specified in
REQUESTS_CA_BUNDLEwill not be taken into account. As a result anSSL: CERTIFICATE_VERIFY_FAILEDis thrown.
原因可以从源码对上:普通路径Session.request()在send()之前会主动调用merge_environment_settings()把环境变量合并进请求参数(sessions.py 第 641–651 行):
settings = self.merge_environment_settings( prep.url, proxies, stream, verify, cert ) send_kwargs.update(settings) resp = self.send(prep, **send_kwargs)而手写 prepared 流程直接调send(),跳过了这一步:verify不会换成REQUESTS_CA_BUNDLE指向的证书包,于是内部 CA 校验失败。代理方面同理——send()只有在proxies未显式传入时才会用resolve_proxies()补一次环境代理(sessions.py 第 762–763 行、utils.py 第 911–939 行),prepared 示例里proxies=proxies往往是调用方自己传的空值,环境变量就此被绕过。
另外注意文档的第二个警告:用req.prepare()而不是s.prepare_request(req)时,Session 级状态(如 cookies)也不会应用到请求上。需要 Session 状态时改用s.prepare_request(req),advanced.rst 第 158–188 行 有完整示例。
修复:用 merge_environment_settings 显式合并环境设置
文档给出的解决方案是在send()前手动合并环境设置(advanced.rst 第 194–207 行):
from requests import Request, Session s = Session() req = Request('GET', url) prepped = s.prepare_request(req) # Merge environment settings into session settings = s.merge_environment_settings(prepped.url, {}, None, None, None) resp = s.send(prepped, **settings) print(resp.status_code)其中url替换为你的目标 URL;merge_environment_settings的五个参数依次是url、proxies、stream、verify、cert(sessions.py 第 831–868 行),传{} / None / None / None表示这几项都交给“环境变量 + Session 默认值”去决定。在trust_env为True时,该方法会:
- 读取
http_proxy/https_proxy等环境代理并合并进proxies(no_proxy会参与剔除匹配 URL); - 在
verify为True或None时,用REQUESTS_CA_BUNDLE或回退的CURL_CA_BUNDLE替换verify。
它返回{"proxies", "stream", "verify", "cert"}四项,直接展开传给send()。
验证方式就是文档示例的最后一行:修复后s.send()不再抛SSL: CERTIFICATE_VERIFY_FAILED,print(resp.status_code)正常输出状态码。对照关系是:修复前该 prepared 流程在指向内部 CA 的 HTTPS 端点上报CERTIFICATE_VERIFY_FAILED,修复后同一请求走通 TLS 校验。
trust_env 如何控制环境变量读取
Session有一个trust_env开关,源码中默认True,注释为 “Trust environment settings for proxy configuration, default authentication and similar”(sessions.py 第 490–492 行)。它同时控制三处环境变量读取:
merge_environment_settings():trust_env为False时不合并环境代理,也不读REQUESTS_CA_BUNDLE(sessions.py 第 845–860 行)——这是“所有请求都不读环境变量”时最直接的检查点;send()内对resolve_proxies()的调用传入self.trust_env,为False时不再补环境代理(utils.py 第 932 行);prepare_request()中的 netrc 默认认证:只有trust_env为True且未显式给auth时才会查~/.netrc(sessions.py 第 535–538 行)。Authentication 文档 给出的显式用法是:
>>> s = requests.Session() >>> s.trust_env = False >>> s.get('https://httpbin.org/basic-auth/user/pass')所以排查顺序是:先看代码里有没有人把这个 Session(或派生 Session)的trust_env改成False——改过之后环境变量按设计就不再被读取,包括 CA bundle、代理和 netrc;如果这是你想要的隔离效果(不让进程环境变量影响请求),那它就是正确配置而非 bug,此时应改为通过verify=、proxies=参数显式传值。
反向情况:显式设置的 session.proxies 被环境变量覆盖
还有一种现象方向相反:你在代码里设了session.proxies,却仍走了环境变量里的代理。Proxies 一节 的警告说明了原因:session.proxies中的值会被环境代理(由urllib.request.getproxies取得)覆盖。文档给出的处理方式是:在存在环境代理的环境下,不要依赖session.proxies,而是在每个请求上显式传proxies=参数:
import requests proxies = { 'http': 'http://10.10.1.10:3128', 'https': 'http://10.10.1.10:1080', } requests.get('http://example.org', proxies=proxies)这是文档标注的确保代理按代码意图生效的做法(文档引用了 upstream issue #2018 作为细节来源)。
限制与安全边界
verify=False可以绕过证书校验,但文档明确警告:requests 会接受服务器出示的任何 TLS 证书,忽略主机名不匹配和过期证书,使应用暴露在中间人攻击之下,只建议本地开发或测试使用(advanced.rst SSL Cert Verification 一节)。排查 CA bundle 问题时应修证书,而不是关校验。- 不要把用户名、密码写进
HTTPS_PROXY="http://user:pass@..."这类环境变量或入库文件,文档将其列为安全风险并强烈不建议(Proxies 一节)。 REQUESTS_CA_BUNDLE未设置时才回退CURL_CA_BUNDLE,两者都未设置时使用 certifi 提供的证书包(CA Certificates 一节);文档建议频繁升级 certifi 保持证书包更新。- 以上行为以本仓库当前源码(requests 2.34.2)为准;旧版本 requests 的环境变量读取路径可能有差异,排查前确认实际安装版本。
按“普通流程验证 → prepared 流程加merge_environment_settings→ 检查trust_env→ 显式proxies=参数”这条路径走完,REQUESTS_CA_BUNDLE与代理环境变量不生效的几类原因都能落到文档明确描述的机制上,而不是笼统地“改个参数试试”。
【免费下载链接】requestsA simple, yet elegant, HTTP library.项目地址: https://gitcode.com/GitHub_Trending/re/requests
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考