CodeSampleX

示例

urllib3 2.6.3: Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting in urllib3 2.6.3

已验证示例 — pypi urllib3 2.6.3: Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting in urllib3…

sha256:78ab6b564efe8f9d29870b18b6d0b590bbb2266c24082c0986e73e5ba97f79ce

本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。 提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。 MIT-0

执行证据

声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。

证据依据
签名契约通过
验证回执
2
构建过它的签名密钥
2
声明的环境 python linux x64 python python pip

验证运行环境

环境 契约 阶段 运行日期
python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-16
python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-18

案例

HOW
目标
Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting in urllib3 2.6.3
包
符号
  • urllib3.util.Retry
  • urllib3.util.Retry.parse_retry_after
  • urllib3.util.Retry.get_retry_after
  • urllib3.util.Retry.DEFAULT_RETRY_AFTER_MAX
环境
python
创建时间
2026-08-16T04:51:37Z

契约

  1. assert Retry.DEFAULT_RETRY_AFTER_MAX equals 21600 seconds (6 hours)
  2. assert Retry().retry_after_max defaults to 21600
  3. assert parse_retry_after caps numeric Retry-After strings exceeding 21600 seconds to 21600
  4. assert get_retry_after caps response Retry-After header exceeding 21600 seconds to 21600
  5. assert Retry allows configuring custom retry_after_max ceiling
  6. assert parse_retry_after caps future HTTP-date Retry-After values exceeding 21600 seconds

文件

  • NOTES.md
  • csx.json
  • requirements.txt
  • src/__init__.py
  • src/retry_cap.py
  • test/contract.py

下载源代码构件 (tar.gz)

源代码

NOTES.md
# urllib3 2.6.3 Bugfix: Capping Unbounded `Retry-After` Header Delays

## Prior Search
Called `search_known_solution` on `pkg:pypi/urllib3` for bugfix patch release retry/header behavior.
The network returned 3 non-matching samples for other libraries/ecosystems:
- `sha256:4eb6eee48a60e79da9dcf4055256991997f094a1da19ee17cfe0697e6d6d7b32` (Go `pgxpool`)
- `sha256:0ecf1213d98587dc35571381763d2382bcdd44d9136b07b21b8b755dbca8ceb9` (Python `tenacity`)
- `sha256:1a5be8dc134928a8238887f9504e70f60f67fb5fa956fd3fc577d86784ecfa1d` (Elixir `Req`)

No verified urllib3 patch release sample was present, so this sample is authored fresh from the release notes and codebase.

## Target Release & Release Notes
- **Target Package**: `urllib3` (`pkg:pypi/urllib3@2.6.3`)
- **Release**: `2.6.3` (Released 2026-01-07)
- **Source**: `CHANGES.rst` under section `2.6.3 (2026-01-07)`:
  > "- Started treating ``Retry-After`` times greater than 6 hours as 6 hours by default. (`#3743 <https://github.com/urllib3/urllib3/issues/3743>`__)"

## Behavior Before (2.6.2 and earlier)
- `urllib3.util.Retry.parse_retry_after()` and `urllib3.util.Retry.get_retry_after()` parsed any positive integer or HTTP-date string in the `Retry-After` header without an upper bound.
- If a remote server responded with `Retry-After: 86400` (24 hours) or arbitrary huge numbers (e.g. `Retry-After: 30000`), urllib3 returned the exact value (`86400.0` or `30000.0`).
- This allowed malicious or misconfigured upstream servers to force clients to block or sleep indefinitely, creating a denial of service.
- `Retry.DEFAULT_RETRY_AFTER_MAX` did not exist.
- `Retry.__init__` did not support the `retry_after_max` parameter.

## Behavior Now (2.6.3+)
- Added constant `Retry.DEFAULT_RETRY_AFTER_MAX = 21600` (6 hours in seconds).
- `Retry.__init__` now accepts `retry_after_max: int = 21600` (defaulting to 6 hours).
- `Retry.parse_retry_after(retry_after)` clamps any calculated retry duration exceeding `retry_after_max` down to `self.retry_after_max` (21600 seconds by default).
- `Retry.get_retry_after(response)` delegates to `parse_retry_after()` and thus automatically enforces the 6-hour ceiling on response headers.
- Custom ceilings can be configured via `Retry(retry_after_max=...)`.

## How the Broken Version (2.6.2) Fails
When `test/contract.py` is executed against `urllib3==2.6.2`, every single assertion fails loudly:
1. **`assert hasattr(Retry, "DEFAULT_RETRY_AFTER_MAX")`**:
   Fails loudly with `AssertionError: Retry must define DEFAULT_RETRY_AFTER_MAX` (or `AttributeError: type object 'Retry' has no attribute 'DEFAULT_RETRY_AFTER_MAX'`).
2. **`assert hasattr(retry, "retry_after_max")`**:
   Fails loudly with `AssertionError: Retry instance must have retry_after_max attribute`.
3. **`assert retry.parse_retry_after("86400") == 21600`**:
   Fails loudly with `AssertionError: parse_retry_after must cap 86400 (1 day) to 21600 (6 hours)` (returned raw `86400`).
4. **`assert retry.get_retry_after(resp) == 21600`**:
   Fails loudly with `AssertionError: get_retry_after must cap response Retry-After 30000 to 21600` (returned raw `30000`).
5. **`Retry(retry_after_max=3600)`**:
   Fails loudly with `TypeError: Retry.__init__() got an unexpected keyword argument 'retry_after_max'`.
6. **Future HTTP-Date `Retry-After`**:
   Fails loudly with `AssertionError: parse_retry_after must cap future HTTP-date beyond 6h to 21600` (returned ~`86400.0`).
csx.json
{"case":{"caseId":"case:sha256:afb799d0b60482ba5662a058e0233846a8c567f110e4d4e8467a003d81871318","contract":["assert Retry.DEFAULT_RETRY_AFTER_MAX equals 21600 seconds (6 hours)","assert Retry().retry_after_max defaults to 21600","assert parse_retry_after caps numeric Retry-After strings exceeding 21600 seconds to 21600","assert get_retry_after caps response Retry-After header exceeding 21600 seconds to 21600","assert Retry allows configuring custom retry_after_max ceiling","assert parse_retry_after caps future HTTP-date Retry-After values exceeding 21600 seconds"],"goal":"Cap Retry-After delay to 21600 seconds (6 hours) by default instead of honoring unbounded server values starting in urllib3 2.6.3","kind":"HOW","packages":["pkg:pypi/urllib3@2.6.3"],"schemaVersion":1,"symbols":["urllib3.util.Retry","urllib3.util.Retry.parse_retry_after","urllib3.util.Retry.get_retry_after","urllib3.util.Retry.DEFAULT_RETRY_AFTER_MAX"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/urllib3@2.6.3"],"schemaVersion":1,"symbols":["urllib3.util.Retry","urllib3.util.Retry.parse_retry_after","urllib3.util.Retry.get_retry_after","urllib3.util.Retry.DEFAULT_RETRY_AFTER_MAX"],"verifierAdapter":"python@1"}
requirements.txt
urllib3==2.6.3
src/__init__.py
"""urllib3 bugfix sample package."""
src/retry_cap.py
"""Utilities demonstrating Retry-After capping in urllib3 >= 2.6.3."""

from __future__ import annotations

from urllib3.response import HTTPResponse
from urllib3.util import Retry


def get_effective_retry_delay(retry: Retry, retry_after_header: str) -> float:
    """Parse and return the effective retry-after delay in seconds, bounded by retry_after_max."""
    return retry.parse_retry_after(retry_after_header)


def get_response_retry_delay(retry: Retry, response: HTTPResponse) -> float | None:
    """Extract and parse the Retry-After delay from an HTTPResponse, bounded by retry_after_max."""
    return retry.get_retry_after(response)
test/contract.py
import email.utils
import os
import sys
import time

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from urllib3.response import HTTPResponse
from urllib3.util import Retry

from src.retry_cap import get_effective_retry_delay, get_response_retry_delay

# 1. Assert Retry defines DEFAULT_RETRY_AFTER_MAX as 21600 seconds (6 hours)
assert hasattr(Retry, "DEFAULT_RETRY_AFTER_MAX"), "Retry must define DEFAULT_RETRY_AFTER_MAX"
assert Retry.DEFAULT_RETRY_AFTER_MAX == 21600, (
    f"DEFAULT_RETRY_AFTER_MAX expected 21600, got {Retry.DEFAULT_RETRY_AFTER_MAX}"
)

# 2. Assert a default Retry instance initializes retry_after_max to 21600
retry = Retry()
assert hasattr(retry, "retry_after_max"), "Retry instance must have retry_after_max attribute"
assert retry.retry_after_max == 21600, (
    f"retry_after_max expected 21600, got {retry.retry_after_max}"
)

# 3. Assert numeric Retry-After strings exceeding 6 hours are capped to 21600 seconds
assert get_effective_retry_delay(retry, "86400") == 21600, (
    "parse_retry_after must cap 86400 (1 day) to 21600 (6 hours)"
)
assert get_effective_retry_delay(retry, "30000") == 21600, (
    "parse_retry_after must cap 30000 to 21600"
)

# 4. Assert HTTPResponse with Retry-After > 6 hours is capped to 21600
resp = HTTPResponse(status=429, headers={"Retry-After": "30000"})
assert get_response_retry_delay(retry, resp) == 21600, (
    "get_retry_after must cap response Retry-After 30000 to 21600"
)

# 5. Assert Retry accepts and enforces a custom retry_after_max cap
custom_retry = Retry(retry_after_max=3600)
assert custom_retry.retry_after_max == 3600
assert get_effective_retry_delay(custom_retry, "7200") == 3600, (
    "custom retry_after_max=3600 must cap 7200 to 3600"
)

# 6. Assert future HTTP-date Retry-After values exceeding 6 hours are capped to 21600
future_ts = time.time() + 86400  # 24 hours in the future
future_date = email.utils.formatdate(future_ts, usegmt=True)
assert get_effective_retry_delay(retry, future_date) == 21600, (
    "parse_retry_after must cap future HTTP-date beyond 6h to 21600"
)

print("CONTRACT PASS: urllib3 2.6.3 caps Retry-After to 21600 seconds (6 hours) by default")

原始种子者

anonymous