feat: Windows v1.1.1 — 修复与改进(DSH 工作区归档)
This commit is contained in:
1 parent
4a5d676c7b
commit
e6880efdc6
21 files changed
+807
-106
No files matched your search
@@ -0,0 +1,112 @@
|
||||
"""验证 keep-alive 连接上「不读 body 的接口」不会污染下一个请求。
|
||||
|
||||
背景(真实 bug):`/api/devices/connect` 的 handler 从不读请求体。
|
||||
在 HTTP/1.1 keep-alive 连接上,没读走的 `{}` 会留在 socket 缓冲区里,
|
||||
被 BaseHTTPRequestHandler 当成下一个请求的请求行,于是登录到:
|
||||
|
||||
501 Unsupported method ('{}POST')
|
||||
|
||||
而且是**间歇性**的(取决于客户端是否复用连接),非常难查。
|
||||
修法:do_POST 进入路由前先把 body 读完缓存起来。
|
||||
|
||||
用法:先起一个 mock 服务,再跑本脚本。
|
||||
.venv\\Scripts\\python.exe tools\\_keepalive_probe.py --port 8855
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
|
||||
|
||||
def build_post(host: str, port: int, path: str, body: str) -> bytes:
|
||||
payload = body.encode("utf-8")
|
||||
head = (
|
||||
f"POST {path} HTTP/1.1\r\n"
|
||||
f"Host: {host}:{port}\r\n"
|
||||
f"Content-Type: application/json\r\n"
|
||||
f"Content-Length: {len(payload)}\r\n"
|
||||
f"Connection: keep-alive\r\n"
|
||||
f"\r\n"
|
||||
).encode("ascii")
|
||||
return head + payload
|
||||
|
||||
|
||||
def recv_response(sock: socket.socket) -> str:
|
||||
"""读一个完整响应(按 Content-Length)。"""
|
||||
data = b""
|
||||
while b"\r\n\r\n" not in data:
|
||||
chunk = sock.recv(4096)
|
||||
if not chunk:
|
||||
return data.decode("utf-8", "replace")
|
||||
data += chunk
|
||||
head, _, rest = data.partition(b"\r\n\r\n")
|
||||
length = 0
|
||||
for line in head.split(b"\r\n"):
|
||||
if line.lower().startswith(b"content-length:"):
|
||||
length = int(line.split(b":", 1)[1].strip())
|
||||
break
|
||||
while len(rest) < length:
|
||||
chunk = sock.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
rest += chunk
|
||||
return (head + b"\r\n\r\n" + rest).decode("utf-8", "replace")
|
||||
|
||||
|
||||
def status_of(response: str) -> int:
|
||||
first = response.split("\r\n", 1)[0]
|
||||
parts = first.split()
|
||||
try:
|
||||
return int(parts[1])
|
||||
except (IndexError, ValueError):
|
||||
return -1
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--host", default="127.0.0.1")
|
||||
parser.add_argument("--port", type=int, default=8855)
|
||||
args = parser.parse_args()
|
||||
|
||||
failures: list[str] = []
|
||||
|
||||
def check(name: str, ok: bool, detail: str = "") -> None:
|
||||
print(f" [{'PASS' if ok else 'FAIL'}] {name}" + (f" — {detail}" if detail else ""))
|
||||
if not ok:
|
||||
failures.append(name)
|
||||
|
||||
with socket.create_connection((args.host, args.port), timeout=15) as sock:
|
||||
# 第 1 个请求:body 为 {} 且服务端 handler 不读它
|
||||
sock.sendall(build_post(args.host, args.port, "/api/devices/connect", "{}"))
|
||||
r1 = recv_response(sock)
|
||||
s1 = status_of(r1)
|
||||
check("第 1 个请求(connect) 成功", s1 in (200, 409), f"status={s1}")
|
||||
print(f" 第 1 个响应前 120 字符:{r1[:120]!r}")
|
||||
|
||||
# 第 2 个请求:如果残留的 {} 没被排空,请求行会被污染 → 501
|
||||
body2 = json.dumps({"lat": 39.9087, "lng": 116.3975})
|
||||
sock.sendall(build_post(args.host, args.port, "/api/location", body2))
|
||||
r2 = recv_response(sock)
|
||||
s2 = status_of(r2)
|
||||
check("同连接第 2 个请求(location) 未被污染", s2 == 200, f"status={s2}")
|
||||
if s2 != 200:
|
||||
print(f" 第 2 个响应:{r2[:300]!r}")
|
||||
|
||||
# 第 3 个请求:再压一次,确认连续多请求也稳
|
||||
sock.sendall(build_post(args.host, args.port, "/api/location", body2))
|
||||
r3 = recv_response(sock)
|
||||
s3 = status_of(r3)
|
||||
check("同连接第 3 个请求仍然正常", s3 == 200, f"status={s3}")
|
||||
|
||||
print()
|
||||
if failures:
|
||||
print(f"失败 {len(failures)} 项:{failures}")
|
||||
return 1
|
||||
print("全部通过:keep-alive 上不会串请求")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""把打包好的 .app 制作成可分发的 DMG 安装包。
|
||||
|
||||
产物:dist/iOSLocationTrack-1.1.0_mac.dmg
|
||||
产物:dist/iOSLocationTrack-1.1.1_mac.dmg
|
||||
内容:应用本身 + Applications 快捷方式(拖进去即安装)+ 中文安装说明
|
||||
|
||||
特点:
|
||||
@@ -25,7 +25,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
APP_NAME = "虚拟定位轨迹"
|
||||
VERSION = "1.1.0"
|
||||
VERSION = "1.1.1"
|
||||
APP = ROOT / "dist" / f"{APP_NAME}.app"
|
||||
# 产物名带 _mac 后缀,与 Windows 的 _win 对应,Release 页面上一眼能分清平台。
|
||||
# 用 ASCII 文件名:GitHub Release 会把非 ASCII 字符剥掉(v1.0.0 首次上传时踩过)。
|
||||
|
||||
Reference in new issue
Block a user