From 8094aa723390dec2cc1e0322bfd8e91693de6409 Mon Sep 17 00:00:00 2001 From: Shujakuin Date: Mon, 20 Jul 2026 14:02:08 +0800 Subject: [PATCH] Handle large proxy status responses --- proxy_monitor_tui.py | 11 ++++++++++- pyproject.toml | 2 +- tests/test_proxy_monitor_tui.py | 18 +++++++++++++++++- uv.lock | 2 +- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/proxy_monitor_tui.py b/proxy_monitor_tui.py index adc6aec..22363f2 100644 --- a/proxy_monitor_tui.py +++ b/proxy_monitor_tui.py @@ -22,6 +22,7 @@ DEFAULT_TIMEOUT_SECONDS = 10 DEFAULT_TEST_URL = "https://www.gstatic.com/generate_204" DEFAULT_TEST_TIMEOUT_MS = 5000 DEFAULT_DELAY_CONCURRENCY = 4 +MAX_JSON_RESPONSE_BYTES = 8 * 1024 * 1024 class ApiError(RuntimeError): @@ -154,7 +155,13 @@ class ProxyMonitorClient: ) try: with urllib.request.urlopen(request, timeout=self.timeout_seconds) as response: - raw = response.read(1_048_576) + try: + content_length = int(response.headers.get("Content-Length", "0")) + except ValueError: + content_length = 0 + if content_length > MAX_JSON_RESPONSE_BYTES: + raise ApiError("response exceeds 8 MiB limit") + raw = response.read(MAX_JSON_RESPONSE_BYTES + 1) except urllib.error.HTTPError as exc: try: detail = exc.read(1024).decode("utf-8", errors="replace").strip() @@ -169,6 +176,8 @@ class ProxyMonitorClient: if not expect_json: return None + if len(raw) > MAX_JSON_RESPONSE_BYTES: + raise ApiError("response exceeds 8 MiB limit") if not raw: raise ApiError("response did not contain JSON") try: diff --git a/pyproject.toml b/pyproject.toml index cf0e1f1..f2e01f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "proxy-port-monitor-tui" -version = "0.1.0" +version = "0.1.1" description = "Terminal UI for proxy-port-monitor and mihomo selector groups" readme = "README.md" requires-python = ">=3.11" diff --git a/tests/test_proxy_monitor_tui.py b/tests/test_proxy_monitor_tui.py index 2823bb3..ece29e9 100644 --- a/tests/test_proxy_monitor_tui.py +++ b/tests/test_proxy_monitor_tui.py @@ -23,6 +23,7 @@ def load_module(): class ApiFixtureHandler(BaseHTTPRequestHandler): requests: list[dict[str, Any]] = [] + large_status = False def _json(self, status: int, payload: object) -> None: body = json.dumps(payload).encode("utf-8") @@ -36,7 +37,14 @@ class ApiFixtureHandler(BaseHTTPRequestHandler): parsed = urllib.parse.urlparse(self.path) type(self).requests.append({"method": "GET", "path": parsed.path, "query": urllib.parse.parse_qs(parsed.query)}) if parsed.path == "/base/status.json": - self._json(200, {"targets_up": 1, "targets_total": 1, "targets": [{"name": "pc", "ok": True}]}) + payload: dict[str, object] = { + "targets_up": 1, + "targets_total": 1, + "targets": [{"name": "pc", "ok": True}], + } + if type(self).large_status: + payload["padding"] = "x" * 1_100_000 + self._json(200, payload) return if parsed.path == "/base/admin/pc%20win/api/proxies": self._json( @@ -88,6 +96,7 @@ class ProxyMonitorTUITests(unittest.TestCase): def setUp(self) -> None: ApiFixtureHandler.requests = [] + ApiFixtureHandler.large_status = False self.mod = load_module() self.base_url = f"http://127.0.0.1:{self.server.server_port}/base" self.client = self.mod.ProxyMonitorClient(self.base_url, 2) @@ -124,6 +133,13 @@ class ProxyMonitorTUITests(unittest.TestCase): ], ) + def test_status_response_larger_than_one_mebibyte_is_not_truncated(self) -> None: + ApiFixtureHandler.large_status = True + + status = self.client.fetch_status() + + self.assertEqual(len(status["padding"]), 1_100_000) + def test_selector_groups_only_include_switchable_groups(self) -> None: groups = self.mod.normalize_selector_groups( { diff --git a/uv.lock b/uv.lock index d3790ba..20f24e0 100644 --- a/uv.lock +++ b/uv.lock @@ -63,7 +63,7 @@ wheels = [ [[package]] name = "proxy-port-monitor-tui" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "textual" },