W76:修假后端 keep-alive 下请求体缓存跨请求串用(设备实测「3 张图只投出 2 张」的真凶)
`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 条独立消息落库。
This commit is contained in:
1 parent
8babd1d2bb
commit
b35b1b53ab
1 file changed
+21
-1
@@ -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 只能读一次)。"""
|
||||
|
||||
Reference in new issue
Block a user