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_URL = "https://www.gstatic.com/generate_204"
|
||||||
DEFAULT_TEST_TIMEOUT_MS = 5000
|
DEFAULT_TEST_TIMEOUT_MS = 5000
|
||||||
DEFAULT_DELAY_CONCURRENCY = 4
|
DEFAULT_DELAY_CONCURRENCY = 4
|
||||||
|
MAX_JSON_RESPONSE_BYTES = 8 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
class ApiError(RuntimeError):
|
class ApiError(RuntimeError):
|
||||||
@@ -154,7 +155,13 @@ class ProxyMonitorClient:
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(request, timeout=self.timeout_seconds) as response:
|
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:
|
except urllib.error.HTTPError as exc:
|
||||||
try:
|
try:
|
||||||
detail = exc.read(1024).decode("utf-8", errors="replace").strip()
|
detail = exc.read(1024).decode("utf-8", errors="replace").strip()
|
||||||
@@ -169,6 +176,8 @@ class ProxyMonitorClient:
|
|||||||
|
|
||||||
if not expect_json:
|
if not expect_json:
|
||||||
return None
|
return None
|
||||||
|
if len(raw) > MAX_JSON_RESPONSE_BYTES:
|
||||||
|
raise ApiError("response exceeds 8 MiB limit")
|
||||||
if not raw:
|
if not raw:
|
||||||
raise ApiError("response did not contain JSON")
|
raise ApiError("response did not contain JSON")
|
||||||
try:
|
try:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "proxy-port-monitor-tui"
|
name = "proxy-port-monitor-tui"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
description = "Terminal UI for proxy-port-monitor and mihomo selector groups"
|
description = "Terminal UI for proxy-port-monitor and mihomo selector groups"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def load_module():
|
|||||||
|
|
||||||
class ApiFixtureHandler(BaseHTTPRequestHandler):
|
class ApiFixtureHandler(BaseHTTPRequestHandler):
|
||||||
requests: list[dict[str, Any]] = []
|
requests: list[dict[str, Any]] = []
|
||||||
|
large_status = False
|
||||||
|
|
||||||
def _json(self, status: int, payload: object) -> None:
|
def _json(self, status: int, payload: object) -> None:
|
||||||
body = json.dumps(payload).encode("utf-8")
|
body = json.dumps(payload).encode("utf-8")
|
||||||
@@ -36,7 +37,14 @@ class ApiFixtureHandler(BaseHTTPRequestHandler):
|
|||||||
parsed = urllib.parse.urlparse(self.path)
|
parsed = urllib.parse.urlparse(self.path)
|
||||||
type(self).requests.append({"method": "GET", "path": parsed.path, "query": urllib.parse.parse_qs(parsed.query)})
|
type(self).requests.append({"method": "GET", "path": parsed.path, "query": urllib.parse.parse_qs(parsed.query)})
|
||||||
if parsed.path == "/base/status.json":
|
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
|
return
|
||||||
if parsed.path == "/base/admin/pc%20win/api/proxies":
|
if parsed.path == "/base/admin/pc%20win/api/proxies":
|
||||||
self._json(
|
self._json(
|
||||||
@@ -88,6 +96,7 @@ class ProxyMonitorTUITests(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
ApiFixtureHandler.requests = []
|
ApiFixtureHandler.requests = []
|
||||||
|
ApiFixtureHandler.large_status = False
|
||||||
self.mod = load_module()
|
self.mod = load_module()
|
||||||
self.base_url = f"http://127.0.0.1:{self.server.server_port}/base"
|
self.base_url = f"http://127.0.0.1:{self.server.server_port}/base"
|
||||||
self.client = self.mod.ProxyMonitorClient(self.base_url, 2)
|
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:
|
def test_selector_groups_only_include_switchable_groups(self) -> None:
|
||||||
groups = self.mod.normalize_selector_groups(
|
groups = self.mod.normalize_selector_groups(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proxy-port-monitor-tui"
|
name = "proxy-port-monitor-tui"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "textual" },
|
{ name = "textual" },
|
||||||
|
|||||||
Reference in New Issue
Block a user