fix(web): always drain visit request body to keep keep-alive connections aligned

This commit is contained in:
kairee committed 2026-11-01 15:41:47 +08:00
1 parent a3e7589ad3
commit e3bd2e601b
2 files changed
+33 -4

No files matched your search

+5 -4
View File
@@ -323,10 +323,9 @@ border-color:#3a2c22;color:#f3e9dc}button{background:#e9b97c;color:#2a1a10}}
return return
path_value = _param(query, "path", "") path_value = _param(query, "path", "")
if not path_value: body = self._read_small_body()
body = self._read_small_body() if not path_value and body:
if body: path_value = _json_path(body)
path_value = _json_path(body)
user_agent = self.headers.get("User-Agent") user_agent = self.headers.get("User-Agent")
if not track.count_bots and is_bot(user_agent): if not track.count_bots and is_bot(user_agent):
@@ -360,6 +359,8 @@ border-color:#3a2c22;color:#f3e9dc}button{background:#e9b97c;color:#2a1a10}}
if length <= 0: if length <= 0:
return b"" return b""
if length > VISIT_BODY_LIMIT: if length > VISIT_BODY_LIMIT:
# 不读取超长请求体,直接关闭连接,避免 keep-alive 上残留字节错位。
self.close_connection = True
raise ApiError(HTTPStatus.BAD_REQUEST, "bad_request", "请求体过大") raise ApiError(HTTPStatus.BAD_REQUEST, "bad_request", "请求体过大")
return self.rfile.read(length) return self.rfile.read(length)
+28
View File
@@ -270,6 +270,33 @@ def test_visit_endpoint_and_stats():
running.close() running.close()
def test_visit_query_path_drains_body():
"""path 也可来自查询串;请求体必须被读走,避免 keep-alive 连接错位。"""
with tempfile.TemporaryDirectory() as tmp:
running = RunningServer(tmp)
try:
ua = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/155.0"}
conn = http.client.HTTPConnection("127.0.0.1", running.port, timeout=5)
conn.request(
"POST", "/api/visit?path=/about",
body=json.dumps({"path": "/ignored"}),
headers={**ua, "Content-Type": "application/json"},
)
first = conn.getresponse()
first_payload = json.loads(first.read())
assert first.status == 200
assert first_payload["data"]["route"] == "/about"
# 同一连接继续发请求:若上一次请求体未被读走,这里会解析错位
conn.request("GET", "/api/health")
second = conn.getresponse()
health = json.loads(second.read())
conn.close()
assert second.status == 200 and health["ok"] is True
finally:
running.close()
def test_token_auth_and_login(): def test_token_auth_and_login():
with tempfile.TemporaryDirectory() as tmp: with tempfile.TemporaryDirectory() as tmp:
running = RunningServer(tmp, token="test-token") running = RunningServer(tmp, token="test-token")
@@ -385,6 +412,7 @@ def test_preview_and_loopback_guards():
def main_test(): def main_test():
test_readonly_routes_and_static() test_readonly_routes_and_static()
test_visit_endpoint_and_stats() test_visit_endpoint_and_stats()
test_visit_query_path_drains_body()
test_token_auth_and_login() test_token_auth_and_login()
test_preview_and_loopback_guards() test_preview_and_loopback_guards()
print("OK: Web 只读路由/鉴权/预览/访问统计/安全边界测试全部通过") print("OK: Web 只读路由/鉴权/预览/访问统计/安全边界测试全部通过")