Handle large proxy status responses
This commit is contained in:
+10
-1
@@ -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
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user