From e9fda55adfe83c6624f8fea156445911b2e0254b Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Tue, 5 May 2026 06:47:51 +0330 Subject: [PATCH] refactor: update configuration keys and improve documentation for HTTP proxy settings --- README.md | 38 ++++++++++++---- README_FA.md | 37 ++++++++++++--- config.example.json | 91 +++++++------------------------------ main.py | 25 +++++++--- setup.py | 34 ++++++-------- src/core/constants.py | 11 +++++ src/proxy/proxy_server.py | 23 +++++++--- src/relay/domain_fronter.py | 9 ++-- src/relay/relay_response.py | 2 +- 9 files changed, 140 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index 8778806..c029448 100644 --- a/README.md +++ b/README.md @@ -155,14 +155,12 @@ It'll prompt for your Deployment ID, generate a random `auth_key`, and write 2. Open `config.json` in any text editor and fill in your values: ```json { - "mode": "apps_script", "google_ip": "216.239.38.120", "front_domain": "www.google.com", "script_id": "PASTE_YOUR_DEPLOYMENT_ID_HERE", "auth_key": "your-secret-password-here", "listen_host": "127.0.0.1", - "listen_port": 8085, - "socks5_enabled": true, + "http_port": 8085, "socks5_port": 1080, "log_level": "INFO", "verify_ssl": true @@ -301,7 +299,7 @@ By default, the proxy only listens on `127.0.0.1` (localhost), meaning only your { "lan_sharing": true, "listen_host": "0.0.0.0", - "listen_port": 8085 + "http_port": 8085 } ``` @@ -326,7 +324,7 @@ This project is centered on the **Apps Script** relay (free, no VPS needed). For | `auth_key` | Password shared between your computer and the relay | | `script_id` | Your Google Apps Script Deployment ID | | `listen_host` | Where to listen (`127.0.0.1` = only this computer, `0.0.0.0` = all interfaces for LAN sharing) | -| `listen_port` | Which port to listen on (default: `8085`) | +| `http_port` | Which HTTP proxy port to listen on (default: `8085`) | | `lan_sharing` | Enable LAN sharing to allow other devices on your network to use the proxy (`false` by default) | | `log_level` | How much detail to show: `DEBUG`, `INFO`, `WARNING`, `ERROR` | @@ -340,21 +338,46 @@ This project is centered on the **Apps Script** relay (free, no VPS needed). For | `relay_timeout` | `25` | Total timeout for one relayed request before it fails | | `tls_connect_timeout` | `15` | Timeout for the proxy's TLS connection to the fronted Google/CDN endpoint | | `tcp_connect_timeout` | `10` | Timeout for direct TCP tunnels and outbound SNI-rewrite connects | -| `max_response_body_bytes` | `209715200` | Hard cap for a single relay response body after buffering/decoding | | `script_ids` | — | Multiple Script IDs for load balancing (array) | | `chunked_download_extensions` | see [config.example.json](config.example.json) | File extensions that should use parallel range downloading. Supports `".*"` to probe all GET downloads. | | `chunked_download_min_size` | `5242880` | Minimum total file size (5 MB) before range-parallel download stays enabled | | `chunked_download_chunk_size` | `524288` | Per-range chunk size used by parallel downloads | | `chunked_download_max_parallel` | `8` | Maximum simultaneous range requests for one download | | `chunked_download_max_chunks` | `256` | Soft upper bound for total chunk requests; chunk size is raised automatically for very large files | +| `hosts` | `{}` | Manual DNS override map (`hostname` or `.suffix` -> IP). Example: `{ "example.org": "93.184.216.34", ".internal.lan": "192.168.1.10" }`. | | `block_hosts` | `[]` | Hosts that must never be tunneled (return HTTP 403). Supports exact names (`ads.example.com`) or leading-dot suffixes (`.doubleclick.net`). | +| `direct_hosts` | `[]` | Hosts that must always go direct (no MITM and no relay/domain-fronting). Supports exact names and leading-dot suffixes. | | `bypass_hosts` | `["localhost", ".local", ".lan", ".home.arpa"]` | Hosts that go direct (no MITM, no relay). Useful for LAN resources or sites that break under MITM. | | `direct_google_exclude` | see [config.example.json](config.example.json) | Google apps that must use the MITM relay path instead of the fast direct tunnel. | -| `hosts` | `{}` | Manual DNS override: map a hostname to a specific IP. | | `youtube_via_relay` | `false` | Route YouTube (`youtube.com`, `youtu.be`, `youtube-nocookie.com`) through the Apps Script relay instead of the SNI-rewrite path. The SNI-rewrite path uses Google's frontend IP which enforces SafeSearch and can cause **"Video Unavailable"** errors. Setting this to `true` fixes playback at the cost of using more Apps Script executions and slightly higher latency. | | `exit_node.provider` | `cloudflare` | Selected exit-node backend: `cloudflare`, `deno`, `vps`, or `custom`. | | `exit_node.url` | `""` | Beginner-friendly single URL for the selected provider. | +Practical host-policy example: + +```json +{ + "block_hosts": [ + "ads.example.com", + ".doubleclick.net" + ], + "direct_hosts": [ + "chat.openai.com", + ".openai.com" + ], + "hosts": { + "example.org": "93.184.216.34", + ".internal.lan": "192.168.1.10" + } +} +``` + +- `block_hosts`: deny requests entirely (`403`) for exact names or full suffix trees. +- `direct_hosts`: force plain direct tunnel only (no MITM, no relay fronting). +- `hosts`: force DNS mapping before any real lookup (useful for testing/split-DNS workarounds). + +Note: the relay response body cap is now a code constant (`MAX_RESPONSE_BODY_BYTES`) in [src/core/constants.py](src/core/constants.py), not a user config key. + ### Optional Dependencies Install everything from [`requirements.txt`](requirements.txt). All listed packages are optional — the proxy runs with no third-party dependencies in basic modes, but without them you lose features: @@ -395,7 +418,6 @@ If you change `Code.gs`, you must **create a new deployment** in Google Apps Scr python3 main.py # Normal start python3 main.py -p 9090 # Use HTTP port 9090 instead python3 main.py --socks5-port 1081 # Use SOCKS5 port 1081 -python3 main.py --disable-socks5 # Disable SOCKS5 listener python3 main.py --log-level DEBUG # Show detailed logs python3 main.py -c /path/to/config.json # Use a different config file python3 main.py --install-cert # Install MITM CA certificate and exit diff --git a/README_FA.md b/README_FA.md index 9b97c61..7829de6 100644 --- a/README_FA.md +++ b/README_FA.md @@ -116,14 +116,12 @@ cp config.example.json config.json ```json { - "mode": "apps_script", "google_ip": "216.239.38.120", "front_domain": "www.google.com", "script_id": "PASTE_YOUR_DEPLOYMENT_ID_HERE", "auth_key": "your-secret-password-here", "listen_host": "127.0.0.1", - "listen_port": 8085, - "socks5_enabled": true, + "http_port": 8085, "socks5_port": 1080, "log_level": "INFO", "verify_ssl": true @@ -254,7 +252,7 @@ json { "lan_sharing": true, "listen_host": "0.0.0.0", - "listen_port": 8085 + "http_port": 8085 } **هشدار امنیتی:** وقتی اشتراک‌گذاری در شبکه محلی فعال باشد، هر کسی در شبکه محلی شما می‌تواند از پروکسی شما استفاده کند. اطمینان حاصل کنید که شبکه شما مورد اعتماد است و اقدامات امنیتی بیشتری را در نظر بگیرید. @@ -270,7 +268,7 @@ json | `auth_key` | رمز مشترک بین کامپیوتر شما و رله | | `script_id` | شناسه Deployment مربوط به Google Apps Script شما | | `listen_host` | محل گوش دادن (`127.0.0.1` = فقط همین کامپیوتر، `0.0.0.0` = همه اینترفیس‌ها برای اشتراک‌گذاری LAN) | -| `listen_port` | پورتی که پروکسی روی آن اجرا می‌شود (پیش‌فرض: `8085`) | +| `http_port` | پورت HTTP پروکسی (پیش‌فرض: `8085`) | | `lan_sharing` | فعال‌سازی اشتراک‌گذاری LAN تا دستگاه‌های دیگر در شبکه شما بتوانند از پروکسی استفاده کنند (به‌صورت پیش‌فرض `false`) | | `log_level` | میزان جزئیات لاگ: `DEBUG`، `INFO`، `WARNING`، `ERROR` | @@ -284,20 +282,46 @@ json | `relay_timeout` | `25` | مهلت کل برای هر درخواست relay قبل از fail شدن | | `tls_connect_timeout` | `15` | مهلت اتصال TLS پروکسی به endpoint fronted روی Google/CDN | | `tcp_connect_timeout` | `10` | مهلت اتصال برای tunnel مستقیم و SNI-rewrite | -| `max_response_body_bytes` | `209715200` | سقف نهایی برای اندازه body هر پاسخ relay بعد از buffer/decode | | `script_ids` | - | چند Deployment ID برای load balancing | | `chunked_download_extensions` | مطابق [config.example.json](config.example.json) | پسوند فایل‌هایی که باید از دانلود range-parallel استفاده کنند. از `".*"` هم برای probe همه دانلودهای GET پشتیبانی می‌شود. | | `chunked_download_min_size` | `5242880` | حداقل اندازه کل فایل (۵ مگابایت) برای فعال ماندن دانلود موازی | | `chunked_download_chunk_size` | `524288` | اندازه هر chunk در دانلود موازی | | `chunked_download_max_parallel` | `8` | حداکثر تعداد range request همزمان برای یک دانلود | | `chunked_download_max_chunks` | `256` | سقف نرم برای تعداد کل chunk request ها؛ برای فایل‌های خیلی بزرگ اندازه chunk به‌صورت خودکار بیشتر می‌شود | +| `hosts` | `{}` | نگاشت دستی DNS (`hostname` یا `.suffix` به IP). مثال: `{ "example.org": "93.184.216.34", ".internal.lan": "192.168.1.10" }`. | | `block_hosts` | `[]` | هاست‌هایی که هرگز نباید tunnel شوند (پاسخ 403). نام دقیق (`ads.example.com`) یا پسوند با نقطه‌ی ابتدایی (`.doubleclick.net`). | +| `direct_hosts` | `[]` | دامنه‌هایی که همیشه باید مستقیم بروند (بدون MITM و بدون relay/domain-fronting). از نام دقیق یا پسوند نقطه‌دار پشتیبانی می‌کند. | | `bypass_hosts` | `["localhost", ".local", ".lan", ".home.arpa"]` | هاست‌هایی که مستقیم می‌روند (بدون MITM و بدون رله). برای منابع داخلی شبکه یا سایت‌هایی که با MITM مشکل دارند. | | `direct_google_exclude` | مراجعه به [config.example.json](config.example.json) | اپ‌های Google که باید از مسیر MITM برای رله استفاده کنند به‌جای tunnel مستقیم. | | `youtube_via_relay` | `false` | مسیردهی YouTube (`youtube.com`، `youtu.be`، `youtube-nocookie.com`) از طریق رله Apps Script به‌جای مسیر SNI-rewrite. مسیر SNI-rewrite از IP فرانت‌اند Google عبور می‌کند که SafeSearch را اجباری می‌کند و می‌تواند باعث خطای **«ویدیو در دسترس نیست»** شود. با فعال کردن این گزینه، پخش ویدیو درست می‌شود اما تعداد اجراهای Apps Script بیشتر و تأخیر اندکی بالاتر می‌رود. | | `exit_node.provider` | `cloudflare` | backend انتخاب‌شده برای exit node: `cloudflare`، `deno`، `vps` یا `custom`. | | `exit_node.url` | `""` | آدرس ساده و اصلی برای provider انتخاب‌شده. | +نمونه کاربردی برای policy ها: + +```json +{ + "block_hosts": [ + "ads.example.com", + ".doubleclick.net" + ], + "direct_hosts": [ + "chat.openai.com", + ".openai.com" + ], + "hosts": { + "example.org": "93.184.216.34", + ".internal.lan": "192.168.1.10" + } +} +``` + +- `block_hosts`: این دامنه‌ها کامل مسدود می‌شوند (پاسخ `403`). +- `direct_hosts`: این دامنه‌ها همیشه مستقیم می‌روند (بدون MITM و بدون relay fronting). +- `hosts`: قبل از DNS واقعی، نگاشت دستی اعمال می‌شود (برای تست یا split-DNS workaround). + +نکته: سقف اندازه پاسخ relay حالا یک مقدار ثابت کدی (`MAX_RESPONSE_BODY_BYTES`) در [src/core/constants.py](src/core/constants.py) است و دیگر گزینه‌ی کاربری config نیست. + ### وابستگی‌های اختیاری همه وابستگی‌های [`requirements.txt`](requirements.txt) اختیاری هستند — در حالت پایه بدون هیچ‌کدام کار می‌کند، ولی با نصب آن‌ها امکانات بیشتری در دسترس است: @@ -337,7 +361,6 @@ json python3 main.py python3 main.py -p 9090 python3 main.py --socks5-port 1081 -python3 main.py --disable-socks5 python3 main.py --log-level DEBUG python3 main.py -c /path/to/config.json python3 main.py --install-cert # نصب گواهی CA و خروج diff --git a/config.example.json b/config.example.json index 3539dec..49e3c8f 100644 --- a/config.example.json +++ b/config.example.json @@ -1,90 +1,30 @@ { - "mode": "apps_script", "google_ip": "216.239.38.120", "front_domain": "www.google.com", "script_id": "YOUR_APPS_SCRIPT_DEPLOYMENT_ID", "auth_key": "CHANGE_ME_TO_A_STRONG_SECRET", "listen_host": "127.0.0.1", - "socks5_enabled": true, - "listen_port": 8085, + "http_port": 8085, "socks5_port": 1080, - "log_level": "INFO", "verify_ssl": true, - "lan_sharing": true, + "lan_sharing": false, "relay_timeout": 25, "tls_connect_timeout": 15, "tcp_connect_timeout": 10, - "max_response_body_bytes": 209715200, "parallel_relay": 1, - "chunked_download_extensions": [ - ".bin", - ".zip", - ".tar", - ".gz", - ".bz2", - ".xz", - ".7z", - ".rar", - ".exe", - ".msi", - ".dmg", - ".deb", - ".rpm", - ".apk", - ".iso", - ".img", - ".mp4", - ".mkv", - ".avi", - ".mov", - ".webm", - ".mp3", - ".flac", - ".wav", - ".aac", - ".pdf", - ".doc", - ".docx", - ".ppt", - ".pptx", - ".wasm" + "block_hosts": [ + "ads.example.com", + ".doubleclick.net" ], - "chunked_download_min_size": 5242880, - "chunked_download_chunk_size": 524288, - "chunked_download_max_parallel": 8, - "chunked_download_max_chunks": 256, - "block_hosts": [], - "bypass_hosts": [ - "localhost", - ".local", - ".lan", - ".home.arpa" - ], - "direct_google_exclude": [ - "gemini.google.com", - "aistudio.google.com", - "notebooklm.google.com", - "labs.google.com", - "meet.google.com", - "accounts.google.com", - "ogs.google.com", - "mail.google.com", - "calendar.google.com", - "drive.google.com", - "docs.google.com", - "chat.google.com", - "maps.google.com", - "play.google.com", - "translate.google.com", - "assistant.google.com", - "lens.google.com" - ], - "direct_google_allow": [ - "www.google.com", - "safebrowsing.google.com" + "direct_hosts": [ + "rubika.ir", + "soft98.ir" ], "youtube_via_relay": false, - "hosts": {}, + "hosts": { + "example.org": "93.184.216.34", + ".internal.lan": "192.168.1.10" + }, "exit_node": { "enabled": false, "provider": "cloudflare", @@ -92,8 +32,9 @@ "psk": "", "mode": "full", "hosts": [ - "example.com", - "example.org" + ".chatgpt.com", + ".openai.com" ] - } + }, + "log_level": "INFO" } diff --git a/main.py b/main.py index 61712d0..fa819bc 100644 --- a/main.py +++ b/main.py @@ -50,7 +50,7 @@ def parse_args(): "-p", "--port", type=int, default=None, - help="Override listen port (env: DFT_PORT)", + help="Override HTTP proxy port (env: DFT_HTTP_PORT, legacy: DFT_PORT)", ) parser.add_argument( "--host", @@ -66,7 +66,7 @@ def parse_args(): parser.add_argument( "--disable-socks5", action="store_true", - help="Disable the built-in SOCKS5 listener.", + help="Deprecated: SOCKS5 listener is always enabled.", ) parser.add_argument( "--log-level", @@ -170,9 +170,15 @@ def main(): # CLI argument overrides if args.port is not None: - config["listen_port"] = args.port + config["http_port"] = args.port + elif os.environ.get("DFT_HTTP_PORT"): + config["http_port"] = int(os.environ["DFT_HTTP_PORT"]) elif os.environ.get("DFT_PORT"): - config["listen_port"] = int(os.environ["DFT_PORT"]) + config["http_port"] = int(os.environ["DFT_PORT"]) + + # Backward compatibility for older config files. + if "http_port" not in config: + config["http_port"] = int(config.get("listen_port", 8080)) if args.host is not None: config["listen_host"] = args.host @@ -185,7 +191,12 @@ def main(): config["socks5_port"] = int(os.environ["DFT_SOCKS5_PORT"]) if args.disable_socks5: - config["socks5_enabled"] = False + logging.getLogger("Main").warning( + "--disable-socks5 is deprecated and ignored: SOCKS5 is always enabled." + ) + + # Keep runtime behavior fixed regardless of user config values. + config["socks5_enabled"] = True if args.log_level is not None: config["log_level"] = args.log_level @@ -273,8 +284,8 @@ def main(): # print concrete IPv4 addresses users can use on other devices. lan_mode = lan_sharing or listen_host in ("0.0.0.0", "::") if lan_mode: - socks_port = config.get("socks5_port", 1080) if config.get("socks5_enabled", True) else None - log_lan_access(config.get("listen_port", 8080), socks_port) + socks_port = config.get("socks5_port", 1080) + log_lan_access(config.get("http_port", config.get("listen_port", 8080)), socks_port) try: asyncio.run(_run(config)) diff --git a/setup.py b/setup.py index 2f3166d..0bb2f48 100644 --- a/setup.py +++ b/setup.py @@ -77,12 +77,10 @@ def load_base_config() -> dict: except Exception: pass return { - "mode": "apps_script", "google_ip": "216.239.38.120", "front_domain": "www.google.com", "listen_host": "127.0.0.1", - "listen_port": 8085, - "socks5_enabled": True, + "http_port": 8085, "socks5_port": 1080, "log_level": "INFO", "verify_ssl": True, @@ -90,11 +88,7 @@ def load_base_config() -> dict: "relay_timeout": 25, "tls_connect_timeout": 15, "tcp_connect_timeout": 10, - "max_response_body_bytes": 200 * 1024 * 1024, - "chunked_download_min_size": 5 * 1024 * 1024, - "chunked_download_chunk_size": 512 * 1024, - "chunked_download_max_parallel": 8, - "chunked_download_max_chunks": 256, + "direct_hosts": [], "hosts": {}, } @@ -137,20 +131,21 @@ def configure_network(cfg: dict) -> dict: default_host = "0.0.0.0" cfg["listen_host"] = prompt("Listen host", default=default_host) - port = prompt("HTTP proxy port", default=str(cfg.get("listen_port", 8085))) + port = prompt( + "HTTP proxy port", + default=str(cfg.get("http_port", cfg.get("listen_port", 8085))), + ) try: - cfg["listen_port"] = int(port) + cfg["http_port"] = int(port) except ValueError: - cfg["listen_port"] = 8085 + cfg["http_port"] = 8085 - socks5 = prompt_yes_no("Enable SOCKS5 proxy?", default=bool(cfg.get("socks5_enabled", True))) - cfg["socks5_enabled"] = socks5 - if socks5: - sport = prompt("SOCKS5 port", default=str(cfg.get("socks5_port", 1080))) - try: - cfg["socks5_port"] = int(sport) - except ValueError: - cfg["socks5_port"] = 1080 + # SOCKS5 is always enabled at runtime; only port is configurable. + sport = prompt("SOCKS5 port", default=str(cfg.get("socks5_port", 1080))) + try: + cfg["socks5_port"] = int(sport) + except ValueError: + cfg["socks5_port"] = 1080 return cfg @@ -175,7 +170,6 @@ def main() -> int: return 0 cfg = load_base_config() - cfg["mode"] = "apps_script" suggested_key = random_auth_key() print() diff --git a/src/core/constants.py b/src/core/constants.py index 766fde2..03bf06f 100644 --- a/src/core/constants.py +++ b/src/core/constants.py @@ -107,6 +107,17 @@ FRONT_SNI_POOL_GOOGLE: tuple[str, ...] = ( ) +# ── Bypass hosts (direct, no MITM/relay) ──────────────────────────────── +# Applied when bypass_hosts is omitted from config.json. +# Advanced users can override this list in config.json under "bypass_hosts". +DEFAULT_BYPASS_HOSTS: tuple[str, ...] = ( + "localhost", + ".local", + ".lan", + ".home.arpa", +) + + # ── Per-host stats ──────────────────────────────────────────────────────── STATS_LOG_INTERVAL = 300.0 # seconds — how often to log per-host totals STATS_LOG_TOP_N = 10 # how many hosts to include in the log diff --git a/src/proxy/proxy_server.py b/src/proxy/proxy_server.py index 28c0bae..d48c118 100644 --- a/src/proxy/proxy_server.py +++ b/src/proxy/proxy_server.py @@ -22,6 +22,7 @@ except Exception: # optional dependency fallback from core.constants import ( CACHE_MAX_MB, CLIENT_IDLE_TIMEOUT, + DEFAULT_BYPASS_HOSTS, GOOGLE_DIRECT_ALLOW_EXACT, GOOGLE_DIRECT_ALLOW_SUFFIXES, GOOGLE_DIRECT_EXACT_EXCLUDE, @@ -73,14 +74,15 @@ class ProxyServer: def __init__(self, config: dict): self.host = config.get("listen_host", "127.0.0.1") - self.port = config.get("listen_port", 8080) - self.socks_enabled = config.get("socks5_enabled", True) + # Prefer the new key (http_port) but keep listen_port for old configs. + self.port = config.get("http_port", config.get("listen_port", 8080)) + self.socks_enabled = True self.socks_host = config.get("socks5_host", self.host) self.socks_port = config.get("socks5_port", 1080) if self.socks_enabled and self.socks_host == self.host \ and int(self.socks_port) == int(self.port): raise ValueError( - f"listen_port and socks5_port must differ on the same host " + f"http_port and socks5_port must differ on the same host " f"(both set to {self.port} on {self.host}). " f"Change one of them in config.json." ) @@ -137,12 +139,19 @@ class ProxyServer: } # ── Per-host policy ──────────────────────────────────────── - # block_hosts — refuse traffic entirely (close or 403) - # bypass_hosts — route directly (no MITM, no relay) + # block_hosts — refuse traffic entirely (close or 403) + # direct_hosts — route directly (no MITM, no relay) + # bypass_hosts — legacy alias kept for backward compatibility # Both accept exact hostnames and leading-dot suffix patterns, # e.g. ".local" matches any *.local domain. self._block_hosts = load_host_rules(config.get("block_hosts", [])) - self._bypass_hosts = load_host_rules(config.get("bypass_hosts", [])) + direct_hosts = config.get("direct_hosts", []) + bypass_hosts = config.get("bypass_hosts") + if bypass_hosts is None: + bypass_hosts = list(DEFAULT_BYPASS_HOSTS) + self._bypass_hosts = load_host_rules( + list(bypass_hosts) + list(direct_hosts) + ) # Route YouTube through the relay when requested; the Google frontend # IP can enforce SafeSearch on the SNI-rewrite path. @@ -428,7 +437,7 @@ class ProxyServer: return if self._is_bypassed(host): - log.info("Bypass tunnel → %s:%d (matches bypass_hosts)", host, port) + log.info("Direct tunnel → %s:%d (matches direct_hosts/bypass_hosts)", host, port) await self._do_direct_tunnel(host, port, reader, writer) return diff --git a/src/relay/domain_fronter.py b/src/relay/domain_fronter.py index ec208ca..03440a6 100644 --- a/src/relay/domain_fronter.py +++ b/src/relay/domain_fronter.py @@ -137,10 +137,9 @@ class DomainFronter: config, "tls_connect_timeout", TLS_CONNECT_TIMEOUT, minimum=1.0, ) self._sni_probe_timeout = min(self._tls_connect_timeout, 4.0) - self._max_response_body_bytes = self._cfg_int( - config, "max_response_body_bytes", MAX_RESPONSE_BODY_BYTES, - minimum=1024, - ) + # Keep response cap as a code-level constant to avoid exposing an + # advanced memory-safety knob in end-user config. + self._max_response_body_bytes = MAX_RESPONSE_BODY_BYTES # Connection pool — TTL-based, pre-warmed, with concurrency control self._pool: list[tuple[asyncio.StreamReader, asyncio.StreamWriter, float]] = [] @@ -1418,7 +1417,7 @@ class DomainFronter: 502, "Relay response exceeds cap " f"({self._max_response_body_bytes} bytes). " - "Increase max_response_body_bytes if your system has enough RAM.", + "Increase MAX_RESPONSE_BODY_BYTES in src/core/constants.py if your system has enough RAM.", ) if min_size > 0 and total_size < min_size: return self._rewrite_206_to_200(first_resp) diff --git a/src/relay/relay_response.py b/src/relay/relay_response.py index 6caedcb..504b6e4 100644 --- a/src/relay/relay_response.py +++ b/src/relay/relay_response.py @@ -224,7 +224,7 @@ def parse_relay_json(data: dict, max_body_bytes: int) -> bytes: return error_response( 502, f"Relay response exceeds cap ({max_body_bytes} bytes). " - "Increase max_response_body_bytes if your system has enough RAM.", + "Increase MAX_RESPONSE_BODY_BYTES in src/core/constants.py if your system has enough RAM.", ) status_text = {