From b35b1b53ab9d649057a962fa976b17179c84d132 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 26 Oct 2026 23:27:04 +0800 Subject: [PATCH] =?UTF-8?q?W76=EF=BC=9A=E4=BF=AE=E5=81=87=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=20keep-alive=20=E4=B8=8B=E8=AF=B7=E6=B1=82=E4=BD=93?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E8=B7=A8=E8=AF=B7=E6=B1=82=E4=B8=B2=E7=94=A8?= =?UTF-8?q?=EF=BC=88=E8=AE=BE=E5=A4=87=E5=AE=9E=E6=B5=8B=E3=80=8C3=20?= =?UTF-8?q?=E5=BC=A0=E5=9B=BE=E5=8F=AA=E6=8A=95=E5=87=BA=202=20=E5=BC=A0?= =?UTF-8?q?=E3=80=8D=E7=9A=84=E7=9C=9F=E5=87=B6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `protocol_version = "HTTP/1.1"` ⇒ 同一条 keep-alive 连接上的多个请求由**同一个 Handler 实例**依次处理(`handle_one_request` 循环),而上一轮给 `_read_body` 加的缓存 (`_body_read` / `_body_cache`)从不在请求之间重置。后果两条: - 第 2 个请求拿到第 1 个请求的 body ⇒ `out/http.jsonl` 里的摘要记成上一份; - 第 2 个请求自己那份 body **没人从 `rfile` 读走**,残留字节留在内核缓冲里,被下一个 请求的请求行一并吃掉 ⇒ `BaseHTTPRequestHandler` 回 `501 Unsupported method ('{"sessionId":...}POST')`。 W76 设备实测里这表现成「系统分享 3 张图,只投出 2 条消息」,很容易被误判成 App 批量 投递的缺陷。修法是在 `handle_one_request` 入口把缓存清掉,按请求隔离。 验证: 1. 重启假后端后,在同一条 keep-alive 连接上连发 3 个**不同 body** 的 `POST /api/v1/chat`,三发全 202,`out/http.jsonl` 记下三个不同 body(同一 client port)。 2. 完整设备重放(DocumentsUI 选 3 张图 → 分享 → Cyrene → 发送)三发三 202, 3 条独立消息落库。 --- devtools/fake-backend/fake_backend.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/devtools/fake-backend/fake_backend.py b/devtools/fake-backend/fake_backend.py index d49b02e..8677000 100644 --- a/devtools/fake-backend/fake_backend.py +++ b/devtools/fake-backend/fake_backend.py @@ -319,11 +319,17 @@ class JsonlLog: def __init__(self, path: str): self.path = path self.lock = threading.Lock() - os.makedirs(os.path.dirname(path), exist_ok=True) + self.dir = os.path.dirname(path) + os.makedirs(self.dir, exist_ok=True) def append(self, record: dict) -> None: line = json.dumps(record, ensure_ascii=False) with self.lock: + # 目录**每次写都补一次**:README 说「删掉 `out/` 即清空」,而人在服务跑着的时候删掉 + # 它就再也不会自己回来——之后每一条请求都会在写日志时抛 FileNotFoundError, + # 表现成「假后端活着但什么都不回」(W76 实测踩到)。重建目录只是一次 stat, + # 代价可以忽略;让它自己长回来,比要求人先停服务再删要可靠。 + os.makedirs(self.dir, exist_ok=True) with open(self.path, "a", encoding="utf-8", newline="\n") as handle: handle.write(line + "\n") @@ -1084,6 +1090,20 @@ class Handler(BaseHTTPRequestHandler): query = parse_qs(parts.query, keep_blank_values=True) return parts.path, query + def handle_one_request(self): + """每个请求进来时**先把 body 缓存清掉**。 + + `protocol_version` 是 HTTP/1.1,同一条 keep-alive 连接上的多个请求由**同一个 Handler 实例** + 依次处理(`handle_one_request` 循环),所以 `_read_body` 的缓存必须按请求隔离。少了这一步, + 第 2 个请求会拿到第 1 个请求的 body(摘要记成上一份),而它自己那份**根本没人从 `rfile` 读走** + ——残留字节留在内核缓冲里,被下一个请求的请求行一并吃掉,`BaseHTTPRequestHandler` 于是回 + `501 Unsupported method ('{"sessionId":...}POST')`。客户端表现成「3 张图只投出去 2 张」, + 而真正的锅在这里。 + """ + self._body_read = False + self._body_cache = b"" + super().handle_one_request() + def _read_body(self): """读出请求体,**并缓存**:`_handle` 记录摘要时要读它,各 handler 里的 `_parse_json_body` 随后再读——不缓存的话第二次只会读到空串(body 只能读一次)。"""