修管理页 Content-Type(浏览器显示成一屏源码)

用户截图:打开 relay.xxmxxs.top/zhongzhuanzhan 看到的**不是页面,
而是整页 HTML 源码**。

根因:_send() 的 content_type 参数默认是 application/json,
我重做管理页时调它没传 text/html —— 浏览器按 JSON/纯文本处理,
就把 HTML 原样显示了。

⚠ 更该反省的是:**我当时没发现。**
   验证脚本只看了 HTTP 200 和字节数,两个都对,就以为没问题。
   200 不等于页面能看 —— 响应头也得看。

顺手修的第二个问题:
   HEAD 请求一律返回 501 Unsupported method(没实现 do_HEAD)。
   浏览器用 GET 所以页面照开,但 Cloudflare 健康检查、
   很多监控/代理探测都用 HEAD,curl -I 也是。
   补了 do_HEAD:按路径算出类型和长度,只回头不回体。

新增测试 _build/test_response_headers.py(9 项):
  * HEAD 各路径不能是 501
  * 官网/管理页 Content-Type 必须是 text/html,不能是 json
  * favicon、产品截图必须是 image/*
并加进全量套件(阶段 3b6)。

验证:该测试 9/9 通过;全量套件 33 个阶段全部通过。
This commit is contained in:
xmz7891
2026-10-07 09:53:43 +08:00
parent 160dc3765e
commit fe3b9ada12
11 changed files with 400 additions and 25 deletions
+53 -1
View File
@@ -1026,6 +1026,52 @@ class Handler(BaseHTTPRequestHandler):
# ---------------- 路由 ----------------
def do_HEAD(self): # noqa: N802
"""HEAD 请求只回头,不回体。
之前没实现,HEAD 一律 501 Unsupported method。
浏览器用 GET 所以页面照开,但:
* Cloudflare 的健康检查、很多监控/代理探测会用 HEAD
* curl -I 也是 HEAD,排查时看不到响应头很别扭
这里按 GET 算好状态和长度,把体丢掉就行。
"""
RELAY.total_requests += 1
path = self.path.split("?")[0]
try:
body, ctype = self._head_info(path)
except Exception: # noqa: BLE001
body, ctype = b"", "text/plain; charset=utf-8"
self.send_response(200 if body is not None else 404)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(body or b"")))
self.send_header("Cache-Control", "no-store")
self.end_headers()
def _head_info(self, path: str) -> tuple:
"""给 HEAD 用:算这个路径的响应类型和长度(不真的发体)。"""
if path == "/":
return _site_page().encode("utf-8"), "text/html; charset=utf-8"
if path == "/v1/health":
return b"{}", "application/json"
if path in ("/url", "/v1/url"):
return (public_url() or "").encode("utf-8"), \
"text/plain; charset=utf-8"
if path == "/favicon.ico":
icon = Path(__file__).resolve().parent / "favicon.ico"
return (icon.read_bytes() if icon.is_file() else b""), \
"image/x-icon"
if path == ADMIN_PATH:
return b"", "text/html; charset=utf-8"
if path.startswith("/shot/"):
name = path[len("/shot/"):]
if ".." in name or "/" in name or "\\" in name:
return b"", "text/plain; charset=utf-8"
target = Path(__file__).resolve().parent / "site_shots" / name
if target.is_file() and name.lower().endswith(
(".png", ".jpg", ".jpeg", ".webp")):
return target.read_bytes(), "image/png"
return b"", "text/plain; charset=utf-8"
def do_GET(self): # noqa: N802
RELAY.total_requests += 1
path = self.path.split("?")[0]
@@ -1510,6 +1556,12 @@ class Handler(BaseHTTPRequestHandler):
实际渲染在 relay/admin.py —— 原来几百行 HTML/CSS/JS
内联在这里的 f-string 里,改一处样式要在一堆转义花括号里翻,
没法维护。拆出去之后这里只负责把数据递过去。
⚠ Content-Type 必须显式写 text/html。
_send() 默认是 application/json;不传的话浏览器会把整页
HTML 当纯文本显示出来 —— 用户看到的是一屏源码。
(这个坑真踩了:改完只测了 HTTP 200 和字节数,
没看响应头,结果页面在浏览器里是白的。)
"""
from admin import render_admin
snap = RELAY.snapshot()
@@ -1519,7 +1571,7 @@ class Handler(BaseHTTPRequestHandler):
admin_path=ADMIN_PATH,
codes=len(RELAY.codes),
downloads_fn=_list_downloads,
).encode("utf-8"))
).encode("utf-8"), "text/html; charset=utf-8")
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB