fix(web): always drain visit request body to keep keep-alive connections aligned
This commit is contained in:
1 parent
a3e7589ad3
commit
e3bd2e601b
2 files changed
+33
-4
No files matched your search
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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 只读路由/鉴权/预览/访问统计/安全边界测试全部通过")
|
||||||
|
|||||||
Reference in new issue
Block a user