mirror of
https://github.com/masterking32/MasterHttpRelayVPN.git
synced 2026-05-17 21:24:37 +03:00
fix: Enhance Apps Script language handling and improve response parsing logic
This commit is contained in:
@@ -110,6 +110,7 @@ class DomainFronter:
|
|||||||
"sec-fetch-site",
|
"sec-fetch-site",
|
||||||
)
|
)
|
||||||
_SAFE_RETRY_METHODS = {"GET", "HEAD", "OPTIONS"}
|
_SAFE_RETRY_METHODS = {"GET", "HEAD", "OPTIONS"}
|
||||||
|
_APPS_SCRIPT_DEFAULT_LANG = "en"
|
||||||
|
|
||||||
def __init__(self, config: dict):
|
def __init__(self, config: dict):
|
||||||
self.connect_host = config.get("google_ip", "216.239.38.120")
|
self.connect_host = config.get("google_ip", "216.239.38.120")
|
||||||
@@ -128,6 +129,9 @@ class DomainFronter:
|
|||||||
self._script_idx = 0
|
self._script_idx = 0
|
||||||
self.script_id = self._script_ids[0] # backward compat / logging
|
self.script_id = self._script_ids[0] # backward compat / logging
|
||||||
self._dev_available = False # True if /dev endpoint works (no redirect, ~400ms faster)
|
self._dev_available = False # True if /dev endpoint works (no redirect, ~400ms faster)
|
||||||
|
self._apps_script_lang = str(
|
||||||
|
config.get("apps_script_lang", self._APPS_SCRIPT_DEFAULT_LANG)
|
||||||
|
).strip().lower() or self._APPS_SCRIPT_DEFAULT_LANG
|
||||||
|
|
||||||
# Simple execution monitor: log total consumed Apps Script executions.
|
# Simple execution monitor: log total consumed Apps Script executions.
|
||||||
self._execution_report_interval = 5.0
|
self._execution_report_interval = 5.0
|
||||||
@@ -597,10 +601,13 @@ class DomainFronter:
|
|||||||
payload = json.dumps(
|
payload = json.dumps(
|
||||||
{"m": "GET", "u": "http://example.com/", "k": self.auth_key}
|
{"m": "GET", "u": "http://example.com/", "k": self.auth_key}
|
||||||
).encode()
|
).encode()
|
||||||
|
path = f"/macros/s/{sid}/exec?hl={self._apps_script_lang}"
|
||||||
request = (
|
request = (
|
||||||
f"POST /macros/s/{sid}/exec HTTP/1.1\r\n"
|
f"POST {path} HTTP/1.1\r\n"
|
||||||
f"Host: {self.http_host}\r\n"
|
f"Host: {self.http_host}\r\n"
|
||||||
"Content-Type: application/json\r\n"
|
"Content-Type: application/json\r\n"
|
||||||
|
"Accept: application/json,text/plain,*/*\r\n"
|
||||||
|
"Accept-Language: en-US,en;q=0.9\r\n"
|
||||||
f"Content-Length: {len(payload)}\r\n"
|
f"Content-Length: {len(payload)}\r\n"
|
||||||
"Connection: close\r\n\r\n"
|
"Connection: close\r\n\r\n"
|
||||||
).encode() + payload
|
).encode() + payload
|
||||||
@@ -952,7 +959,17 @@ class DomainFronter:
|
|||||||
|
|
||||||
def _exec_path_for_sid(self, sid: str) -> str:
|
def _exec_path_for_sid(self, sid: str) -> str:
|
||||||
"""Build the /macros/s/<sid>/(dev|exec) path for a specific script ID."""
|
"""Build the /macros/s/<sid>/(dev|exec) path for a specific script ID."""
|
||||||
return f"/macros/s/{sid}/{'dev' if self._dev_available else 'exec'}"
|
endpoint = "dev" if self._dev_available else "exec"
|
||||||
|
# Force Google Apps Script UI/errors to English for stable diagnostics.
|
||||||
|
return f"/macros/s/{sid}/{endpoint}?hl={self._apps_script_lang}"
|
||||||
|
|
||||||
|
def _apps_script_headers(self) -> dict[str, str]:
|
||||||
|
"""Headers for Apps Script relay calls (control-plane, not target origin)."""
|
||||||
|
return {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"accept": "application/json,text/plain,*/*",
|
||||||
|
"accept-language": "en-US,en;q=0.9",
|
||||||
|
}
|
||||||
async def _flush_pool(self):
|
async def _flush_pool(self):
|
||||||
"""Close all pooled connections (they may be stale after errors)."""
|
"""Close all pooled connections (they may be stale after errors)."""
|
||||||
async with self._pool_lock:
|
async with self._pool_lock:
|
||||||
@@ -1140,13 +1157,13 @@ class DomainFronter:
|
|||||||
payload = json.dumps(
|
payload = json.dumps(
|
||||||
{"m": "GET", "u": "http://example.com/", "k": self.auth_key}
|
{"m": "GET", "u": "http://example.com/", "k": self.auth_key}
|
||||||
).encode()
|
).encode()
|
||||||
hdrs = {"content-type": "application/json"}
|
hdrs = self._apps_script_headers()
|
||||||
sid = self._script_ids[0]
|
sid = self._script_ids[0]
|
||||||
|
|
||||||
# Test /dev endpoint — returns data inline (no 302 redirect).
|
# Test /dev endpoint — returns data inline (no 302 redirect).
|
||||||
# If it works, saves ~400ms per request by eliminating one round trip.
|
# If it works, saves ~400ms per request by eliminating one round trip.
|
||||||
try:
|
try:
|
||||||
dev_path = f"/macros/s/{sid}/dev"
|
dev_path = f"/macros/s/{sid}/dev?hl={self._apps_script_lang}"
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
self._record_execution(sid)
|
self._record_execution(sid)
|
||||||
status, _, body = await asyncio.wait_for(
|
status, _, body = await asyncio.wait_for(
|
||||||
@@ -1167,7 +1184,7 @@ class DomainFronter:
|
|||||||
|
|
||||||
# Fallback: warm up with /exec
|
# Fallback: warm up with /exec
|
||||||
try:
|
try:
|
||||||
exec_path = f"/macros/s/{sid}/exec"
|
exec_path = f"/macros/s/{sid}/exec?hl={self._apps_script_lang}"
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
self._record_execution(sid)
|
self._record_execution(sid)
|
||||||
await asyncio.wait_for(
|
await asyncio.wait_for(
|
||||||
@@ -1233,7 +1250,7 @@ class DomainFronter:
|
|||||||
await asyncio.wait_for(
|
await asyncio.wait_for(
|
||||||
self._h2.request(
|
self._h2.request(
|
||||||
method="POST", path=path, host=self.http_host,
|
method="POST", path=path, host=self.http_host,
|
||||||
headers={"content-type": "application/json"},
|
headers=self._apps_script_headers(),
|
||||||
body=json.dumps(payload).encode(),
|
body=json.dumps(payload).encode(),
|
||||||
),
|
),
|
||||||
timeout=20,
|
timeout=20,
|
||||||
@@ -2595,7 +2612,7 @@ class DomainFronter:
|
|||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
status, headers, body = await (self._pick_h2() or self._h2).request(
|
status, headers, body = await (self._pick_h2() or self._h2).request(
|
||||||
method="POST", path=path, host=self.http_host,
|
method="POST", path=path, host=self.http_host,
|
||||||
headers={"content-type": "application/json"},
|
headers=self._apps_script_headers(),
|
||||||
body=json_body,
|
body=json_body,
|
||||||
timeout=self._relay_timeout,
|
timeout=self._relay_timeout,
|
||||||
)
|
)
|
||||||
@@ -2626,7 +2643,7 @@ class DomainFronter:
|
|||||||
|
|
||||||
status, headers, body = await (self._pick_h2() or self._h2).request(
|
status, headers, body = await (self._pick_h2() or self._h2).request(
|
||||||
method="POST", path=path, host=self.http_host,
|
method="POST", path=path, host=self.http_host,
|
||||||
headers={"content-type": "application/json"},
|
headers=self._apps_script_headers(),
|
||||||
body=json_body,
|
body=json_body,
|
||||||
timeout=self._relay_timeout,
|
timeout=self._relay_timeout,
|
||||||
)
|
)
|
||||||
@@ -2664,6 +2681,8 @@ class DomainFronter:
|
|||||||
request_lines = [
|
request_lines = [
|
||||||
f"{redirect_method} {rpath} HTTP/1.1",
|
f"{redirect_method} {rpath} HTTP/1.1",
|
||||||
f"Host: {parsed.netloc}",
|
f"Host: {parsed.netloc}",
|
||||||
|
"Accept: application/json,text/plain,*/*",
|
||||||
|
"Accept-Language: en-US,en;q=0.9",
|
||||||
"Accept-Encoding: gzip",
|
"Accept-Encoding: gzip",
|
||||||
"Connection: keep-alive",
|
"Connection: keep-alive",
|
||||||
]
|
]
|
||||||
@@ -2693,6 +2712,8 @@ class DomainFronter:
|
|||||||
f"POST {path} HTTP/1.1\r\n"
|
f"POST {path} HTTP/1.1\r\n"
|
||||||
f"Host: {self.http_host}\r\n"
|
f"Host: {self.http_host}\r\n"
|
||||||
f"Content-Type: application/json\r\n"
|
f"Content-Type: application/json\r\n"
|
||||||
|
f"Accept: application/json,text/plain,*/*\r\n"
|
||||||
|
f"Accept-Language: en-US,en;q=0.9\r\n"
|
||||||
f"Content-Length: {len(json_body)}\r\n"
|
f"Content-Length: {len(json_body)}\r\n"
|
||||||
f"Accept-Encoding: gzip\r\n"
|
f"Accept-Encoding: gzip\r\n"
|
||||||
f"Connection: keep-alive\r\n"
|
f"Connection: keep-alive\r\n"
|
||||||
@@ -2745,7 +2766,7 @@ class DomainFronter:
|
|||||||
status, headers, body = await asyncio.wait_for(
|
status, headers, body = await asyncio.wait_for(
|
||||||
(self._pick_h2() or self._h2).request(
|
(self._pick_h2() or self._h2).request(
|
||||||
method="POST", path=path, host=self.http_host,
|
method="POST", path=path, host=self.http_host,
|
||||||
headers={"content-type": "application/json"},
|
headers=self._apps_script_headers(),
|
||||||
body=json_body,
|
body=json_body,
|
||||||
timeout=batch_timeout,
|
timeout=batch_timeout,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -787,11 +787,13 @@ def parse_relay_response(body: bytes, max_body_bytes: int) -> bytes:
|
|||||||
elif preview_lower.startswith("<"):
|
elif preview_lower.startswith("<"):
|
||||||
# HTML response from script.google.com usually indicates that
|
# HTML response from script.google.com usually indicates that
|
||||||
# Deployment ID is wrong/archived or the deployment was not updated.
|
# Deployment ID is wrong/archived or the deployment was not updated.
|
||||||
# This signature commonly appears as a generic Google Docs wrapper.
|
# Match only Apps Script-specific wrappers to avoid false positives
|
||||||
|
# when the destination site itself is RTL or hosted on docs.google.com.
|
||||||
if any(sig in preview_lower for sig in (
|
if any(sig in preview_lower for sig in (
|
||||||
"web word processing, presentations and spreadsheets",
|
"web word processing, presentations and spreadsheets",
|
||||||
"docs.google.com",
|
"goog.script.init",
|
||||||
"google docs",
|
"script.google.com/macros",
|
||||||
|
"/macros/s/",
|
||||||
)):
|
)):
|
||||||
error_msg = (
|
error_msg = (
|
||||||
"Wrong Apps Script deployment (script_id). "
|
"Wrong Apps Script deployment (script_id). "
|
||||||
|
|||||||
Reference in New Issue
Block a user