Files
ykxmz/_tests/test_admin_button.py
T
xmz7891 566d9e384f 阶段1+2:恢复 HTTP/2 加速 + 重做官网
阶段1 网络加速
- 发现隧道在跑 QUIC(ExecStart 丢了 --protocol http2),
  实测 QUIC 可用率 48% vs HTTP/2 80%+,是重大性能回退。
  恢复后公网延迟从 4141ms 降到 690ms(快 6 倍),可用率 92%。
- 优选 IP 结论:无效。hosts 覆盖确实生效了(日志里连的就是指定 IP),
  但落点仍是 lax —— 运营商把所有 Cloudflare anycast 都路由到美西。
  已清掉这段无效配置。
- BBR 不可用:设备是自定义内核 6.4.0-rc4-jsbsbxjxh66,
  没编 tcp_bbr 模块也没配置项。改用 cubic + 调优:
  TCP 缓冲 4MB、tcp_fastopen=3、tcp_slow_start_after_idle=0
  (长轮询场景关键)、tcp_tw_reuse=1。

阶段2 官网重做
- 拆出独立模块 relay/landing.py(relay.py 已两千多行,
  再塞 HTML 没法看)
- 信息结构参考成熟产品站:吸顶导航 / Hero / 九宫格功能 /
  三步上手 / 下载 / FAQ / 页脚
- 保持白色简洁:底白、浅灰面板、细边框、大量留白
- logo 用内联 SVG(ICO 放 img 里实测是破图)
- 管理入口从官网移除(管理不对外开放)

过程中被自己的测试抓到两个 bug:
- 模块叫 site.py 会和 Python 标准库重名,import site 拿到的是标准库
- _logo() 里把 % 格式化和字符串拼接混用,直接抛 TypeError
2026-10-06 23:53:19 +08:00

191 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
"""模拟浏览器:管理页的"删除"按钮为什么没反应。
用户反馈:"中转站那边输入密码以后,这个删除键没反应,是个幽灵按键"
浏览器实际做的是:
1. POST /admin/login 拿 lr_admin Cookie
2. GET / 看管理页
3. 点删除 -> fetch('/v1/admin/delete', {method POST, body {deviceId}})
浏览器会自动带上 Cookie
所以这个测试完全照这个路径走一遍,看断在哪。
"""
from __future__ import annotations
import http.cookiejar
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
_UA = {"User-Agent": "LanRemote/1.4.0"}
ROOT = Path(__file__).resolve().parent.parent
PORT = 8097
BASE = f"http://127.0.0.1:{PORT}"
passed = 0
failed = 0
def check(name, ok, detail=""):
global passed, failed
if ok:
passed += 1
print(" [PASS] " + name)
else:
failed += 1
print(" [FAIL] " + name + " " + str(detail))
def main() -> int:
work = Path(tempfile.mkdtemp(prefix="admin_button_"))
relay = None
try:
data = work / "data"
data.mkdir(parents=True, exist_ok=True)
env = dict(os.environ)
env.update({
"RELAY_PORT": str(PORT), "RELAY_BIND": "127.0.0.1",
"RELAY_DATA": str(data), "RELAY_POLL": "6",
"PYTHONIOENCODING": "utf-8", "PYTHONUNBUFFERED": "1",
})
relay = subprocess.Popen(
[sys.executable, "-u", str(ROOT / "relay" / "relay.py")], env=env,
stdout=open(work / "relay.log", "wb"), stderr=subprocess.STDOUT)
up = False
for _ in range(40):
time.sleep(0.5)
try:
req = urllib.request.Request(BASE + "/v1/health", headers=_UA)
with urllib.request.urlopen(req, timeout=2) as resp:
if resp.status == 200:
up = True
break
except Exception: # noqa: BLE001
continue
check("本地中转起来了", up)
if not up:
print((work / "relay.log").read_text(encoding="utf-8",
errors="replace")[-500:])
return 1
# 造两台设备
for i in (1, 2):
body = json.dumps({"deviceId": f"pc-{i}", "code": f"11111{i}",
"name": f"电脑{i}", "version": "1.4.0"}
).encode()
req = urllib.request.Request(
BASE + "/v1/agent/register", data=body, method="POST",
headers={**_UA, "Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=10).read()
# ---------------- 模拟浏览器 ----------------
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar))
print()
print("一、登录拿 Cookie")
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode()
req = urllib.request.Request(BASE + "/admin/login", data=form,
method="POST", headers=_UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
try:
opener.open(req, timeout=20).read()
except urllib.error.HTTPError:
pass
cookies = [c.name for c in jar]
check("种下了 lr_admin Cookie", "lr_admin" in cookies, cookies)
print()
print("二、拉管理页,检查删除按钮的 HTML/JS")
req = urllib.request.Request(BASE + "/zhongzhuanzhan", headers=_UA)
html = opener.open(req, timeout=20).read().decode("utf-8", "replace")
# 删除按钮的 class 改过(del -> "btn del"),
# 用 data-device 判定更稳 —— 删除按钮一定带它。
check("有删除按钮", "data-device" in html and "删除" in html)
check("删除全部按钮在", "delAll" in html)
has_fetch = "/v1/admin/delete" in html
check("JS 里的删除接口路径正确", has_fetch,
re.findall(r"fetch\('[^']+'", html)[:4])
# 抽出来看看那段 JS 长什么样(容易出错的地方)
js_start = html.find("<script>")
js_end = html.find("</script>", js_start)
script = html[js_start:js_end] if js_start >= 0 else ""
print(f" JS 长度 {len(script)} 字符")
# 花括号有没有配平(f-string 里双写 {{ }},容易多写或少写)
opens = script.count("{")
closes = script.count("}")
check("JS 花括号配平(f-string 双写容易错)", opens == closes,
f"{{ {opens} vs }} {closes}")
print()
print("三、用 Cookie 调删除接口(浏览器就是这么调的)")
body = json.dumps({"deviceId": "pc-1"}).encode()
req = urllib.request.Request(BASE + "/v1/admin/delete", data=body,
method="POST", headers=_UA)
req.add_header("Content-Type", "application/json")
try:
resp = opener.open(req, timeout=20)
result = json.loads(resp.read().decode("utf-8"))
status = resp.status
except urllib.error.HTTPError as exc:
status = exc.code
raw = exc.read().decode("utf-8", "replace")
try:
result = json.loads(raw)
except Exception: # noqa: BLE001
result = {"raw": raw[:150]}
check("带 Cookie 的删除请求被接受(不是 401/403)",
status == 200, f"HTTP {status} {result}")
check("删除成功", result.get("ok") is True, result)
print()
print("四、删完全部接口也要能用")
req = urllib.request.Request(BASE + "/v1/admin/delete_all",
data=b"{}", method="POST", headers=_UA)
req.add_header("Content-Type", "application/json")
try:
resp = opener.open(req, timeout=20)
result2 = json.loads(resp.read().decode("utf-8"))
status2 = resp.status
except urllib.error.HTTPError as exc:
status2 = exc.code
result2 = {"raw": exc.read().decode("utf-8", "replace")[:150]}
check("delete_all 带 Cookie 也能用", status2 == 200, f"HTTP {status2}")
check("delete_all 成功", result2.get("ok") is True, result2)
print()
print("=" * 62)
print(f"通过 {passed} 项,失败 {failed} 项")
return 0 if failed == 0 else 1
finally:
if relay is not None:
try:
relay.terminate()
relay.wait(timeout=8)
except Exception: # noqa: BLE001
try:
relay.kill()
except Exception: # noqa: BLE001
pass
time.sleep(0.5)
shutil.rmtree(work, ignore_errors=True)
if __name__ == "__main__":
raise SystemExit(main())