Handle large proxy status responses

This commit is contained in:
Shujakuin
2026-07-20 14:02:08 +08:00
parent 1bc4120531
commit 8094aa7233
4 changed files with 29 additions and 4 deletions
+10 -1
View File
@@ -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:
+1 -1
View File
@@ -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"
+17 -1
View File
@@ -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(
{
Generated
+1 -1
View File
@@ -63,7 +63,7 @@ wheels = [
[[package]]
name = "proxy-port-monitor-tui"
version = "0.1.0"
version = "0.1.1"
source = { editable = "." }
dependencies = [
{ name = "textual" },