DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →

urllib3 的 allowed_methods 设为空集,POST 为什么仍会重试

urllib3 2.x 把空 allowed_methods 当作不限方法,2.8.0 只增加弃用警告。修正集合运算和重试预算,区分方法筛选与连接错误、重定向路径。

给 urllib3 的 Retry 配了 allowed_methods=set(),原以为不会再重试任何 HTTP 方法,却发现 POST 仍可能在服务端错误后重新发送。urllib3 2.x 中,空集合并不表示“没有允许的方法”。

在 urllib3 2.8.0,空集合会触发 FutureWarning,但当前行为仍未改成拒绝所有方法。不能只升级、忽略警告,再继续依赖空集实现禁用。

一个交集笔误可能清空方法集

下面的表达式不会把 POST 加入默认集合:

from urllib3.util import Retry

methods = Retry.DEFAULT_ALLOWED_METHODS & {"POST"}

& 是交集,默认方法集不包含 POST,结果为空。需要做并集时才使用 |,但是否允许 POST 重试应由业务幂等性决定,不能因为发现笔误就直接把写请求加进去。

2.8.0 的方法判断先测试 allowed_methods 是否为真,再判断当前方法是否在里面。空集为假,于是跳过成员检查,与 None 一样不限制方法。状态码、错误类别和剩余预算仍会决定最终是否真的再次请求;空集本身不会让每一次调用都重试。

明确限制方法,或明确关闭预算

只允许 GET、HEAD 的状态码和读取错误重试时,可使用非空大写方法集。下面仅构造策略,需按原项目入口交给 PoolManager、连接池或 requests adapter:

from urllib3.util import Retry

retry = Retry(
    total=2,
    connect=0,
    other=0,
    allowed_methods=frozenset({"GET", "HEAD"}),
    status_forcelist={502, 503, 504},
    backoff_factor=0.5,
)

total=2 是首次尝试之后最多两次重试,不是总共只发送两次。示例关闭连接建立错误与其他错误的额外重试,是为了让方法筛选的范围更清楚;项目若本来允许安全的连接错误重试,应单独评估并保留预算。

allowed_methods 不控制所有失败分支。连接建立错误有独立处理,重定向也有自己的计数,不能把这个集合当作“任何情况下都不得再次发送”的总开关。确实要求关闭这层重试,可用 Retry(total=0) 并核对上层客户端是否另有重试或重定向逻辑;需要禁止跳转时,还须在实际请求入口明确配置重定向策略。

如果意图是让所有方法都可按条件重试,使用文档明确的 allowed_methods=None,但先确认写操作的幂等键、结果确认和请求体是否可重放。不要继续以空集合表达这个意思,后续主版本计划改变它的语义。

验证禁止分支,比只看 GET 成功更重要

在受控无副作用端点核对 GET 遇到指定状态码时的调用次数,以及 POST 遇到同样状态码时是否只发送一次。再分别检查连接错误、读取错误和重定向,因为它们并不共享同一个筛选条件。

若日志中的多次调用来自外层任务、requests 的另一层 adapter 或业务重试循环,修改这一份 Retry 不会全部消除。对于已经重复提交的写请求,先按业务身份确认结果,不能靠改配置假定先前操作没有执行。