Skip to content

视频号流式下载端点:两条已知的非阻塞缺陷(gate 第 6 轮,随 PR #10 一并接受) #11

Closed

Description

@zj1123581321

PR #10(视频号流式解密下载端点)在 owner 明确授权下绕过 gate 合并,
本 issue 记录合并时已知、经判定不阻塞的两条缺陷。

一、CDN 建连失败逃逸为框架级 500(gate 标 major,本仓判 P2)

app/api/stream.py:端点对「取 TikHub media 失败」和「_reconcile_cdn_offset 抛
UpstreamDisconnected」都做了异常转换,但 CDN opener 的 await 外没有对应处理。
_UPSTREAM_FAIL 只在响应体迭代器 _iter_decrypted 中使用。因此响应头发出前的 CDN 网络失败
会逃逸成框架级未处理异常,返回通用 500,而不是接口契约要求的 502 JSON。

本仓 P1 两问:

  • 会触发吗?会——CDN 网络失败是常见路径。
  • 后果能接受吗?客户端收到 500 而非 502 JSON。失败可观测,不产出损坏数据、不静默出错。
    按本仓 internal 红线(数据丢失 / 静默出错 / 崩溃 / 越权 / 损坏他人数据)不构成 P1,
    属于「错误响应形式不符合契约」。

修法:给 CDN opener 的 await 套上与 media 失败同形的异常转换,返回 502 JSON。改动很小。

二、_open_cdn_stream_httpx 重复构造 httpx 客户端(gate 标 minor)

同文件:函数先构造一个 httpx.AsyncClient 赋给 client,随后在未使用该实例的情况下
再次构造并覆盖同一变量。属于确定存在的重复对象构造,带来无谓开销与所有权歧义。

注意:这条是「预响应取消资源释放」那一轮修复引入的残留——把客户端所有权挪进
try/finally 时没删掉旧的构造语句。

修法:删掉第一次构造。

背景:为什么在此处停下

该端点的 gate 连续跑了 6 轮,每轮都有 finding,但严重性单调下降:

轮 finding 后果性质
1–3 重连逃逸 / 区间未对账 / 对账有豁免 产出能播但内容错位的 mp4(静默出错,真 P1)
4 对账维度不全 下载失败,可观测
5 预响应取消导致并发配额泄漏 功能不可用,可观测
6 本 issue 这两条 错误形式不规范 / 轻微浪费

真正会让调用方拿到坏文件而不自知的缺陷已在前五轮修完,并各自有变异注入验证过的测试锁死
(注入后精确变红、恢复后全绿)。合并时本地全量 235 passed,真实链路探活通过
(POST /api/resolve → Range: bytes=0-131071 → HTTP 206 / 131072 字节 / 偏移 4..8 为 ftyp)。

owner 据此判断止损,授权绕过 gate 合并,本 issue 承接剩余两条。

Activity

  1. zj1123581321 commented on Sep 23, 2026

    @zj1123581321
    CollaboratorAuthor

    分诊复核(2026-09-23,pi 主脑在 cb1e2b9 实测)

    两条缺陷都已在最新主干修复,本 issue 描述的是 PR #10 时期的形态(_iter_decrypted 那个函数如今已不存在)。实测证据:

    一、首窗 CDN 建连失败 → 已不再逃逸成 500

    # 探针:monkeypatch app.api.stream.open_cdn_stream 抛 httpx.ConnectError,再打端点
    GET /api/stream/wechat_channels/AOzokRxWHz
    → 502  application/json  {"detail":"upstream closed before range complete"}
    → open_cdn_stream 被调用 3 次(收成 UpstreamDisconnected 后按同窗重试)
    

    机理:_read_window 的 except _UPSTREAM_FAIL(httpx.TransportError 在内)+ 端点侧 except UpstreamDisconnected → _json_http_error(502, ...)。
    修复出处:ef9af01 fix(wechat_channels): 窗口 ConnectTimeout 纳入重试并去掉泄漏的 AsyncClient(#16) 与 8373dd1(分窗全读)。

    二、重复构造 httpx client → 已删

    $ grep -n "httpx.AsyncClient(" app/api/stream.py
    89:    client: Optional[httpx.AsyncClient] = httpx.AsyncClient(
    

    只剩一处构造(ef9af01 删掉了泄漏的那个);所有权由 test_pre_response_cancel_during_cdn_open_closes_client 锁住。

    结论:缺陷不可复现 —— 本 issue 的处置是关单,不派修复卡。
    差距只在「回归锁」:这两条目前没有专门的锁测试(既有测试覆盖的是首窗 CDN 500 与后窗 ConnectTimeout),已派补锁卡覆盖这两个缺口,落地后关单。

  2. added a commit that references this issue on Sep 23, 2026
  3. zj1123581321 commented on Sep 23, 2026

    @zj1123581321
    CollaboratorAuthor

    关单(2026-09-23,PR #36 合入 master c5cec29)

    补锁落地,缺陷锁死:

    • tests/test_stream_boundary_locks.py::test_first_window_connect_error_retries_and_returns_502_json —— 首窗 CDN 传输层建连失败:502、!= 500、application/json、open_cdn_stream 调用 ≥ 2(证明已被收进可重试链路而不是单次逃逸)。
    • #11-2 重复构造 client:master 上 grep -n "httpx.AsyncClient(" app/api/stream.py 仅剩第 89 行一处;所有权由既有 test_pre_response_cancel_during_cdn_open_closes_client 锁住,本轮未再加锁。
    • 红验(最小注入、单行还原):把 _read_window 的 except _UPSTREAM_FAIL 收窄成 except UpstreamDisconnected → 新用例转红 assert 500 == 502;还原后全绿。

    测试命令:uv run --extra dev python -m pytest tests/test_stream_boundary_locks.py -q → 2 passed(在合入后的 master 上跑)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions