From 28c4dd9f54bed6eda4e3655a1f626cdb4c14b3a8 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Fri, 10 Jul 2026 02:16:04 +0000 Subject: [PATCH] =?UTF-8?q?Revert=20proxy=20auth=20change=20=E2=80=94=20UR?= =?UTF-8?q?L-embedded=20credentials=20are=20correct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Header-based auth made no difference; the real issue is the hotel network firewall blocking HTTPS CONNECT tunnels on port 823. Reverted to URL-embedded credentials (original approach). Fix requires DataImpulse to enable port 443. Co-Authored-By: Claude Sonnet 4.6 --- backend/api/competitors.py | 9 +++++---- backend/services/proxy.py | 31 +++++++++++++------------------ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/backend/api/competitors.py b/backend/api/competitors.py index 44f8fad..02504fd 100644 --- a/backend/api/competitors.py +++ b/backend/api/competitors.py @@ -277,13 +277,14 @@ async def test_proxy_config( 'password': (raw.get('booking_proxy_password') or '').strip(), 'country': (raw.get('booking_proxy_country') or 'gb').strip(), } - if not proxy_util.is_enabled(cfg): - raise HTTPException(status_code=400, detail="Proxy host and username must be saved first.") + if not (proxy_util.is_enabled(cfg) and cfg['password']): + raise HTTPException(status_code=400, detail="Proxy host, username and password must be saved first.") + + proxy_url = proxy_util.httpx_proxy_url(cfg, proxy_util.new_session_id()) import httpx - proxy = proxy_util.httpx_proxy(cfg, proxy_util.new_session_id()) try: - async with httpx.AsyncClient(proxy=proxy, timeout=40.0) as client: + async with httpx.AsyncClient(proxy=proxy_url, timeout=40.0) as client: resp = await client.get("https://ipinfo.io/json") resp.raise_for_status() data = resp.json() diff --git a/backend/services/proxy.py b/backend/services/proxy.py index c4ced2b..75edb7b 100644 --- a/backend/services/proxy.py +++ b/backend/services/proxy.py @@ -80,40 +80,35 @@ def username(cfg: dict, session_id: Optional[str] = None) -> str: def playwright_proxy(cfg: dict, session_id: Optional[str] = None) -> Optional[dict]: """Proxy dict for new_context(proxy=...). None when disabled. - Uses separate username/password fields so Chromium sends a - Proxy-Authorization header rather than embedding credentials in the URL. - With IP whitelisting on DataImpulse, the 407 round-trip is skipped entirely - (the proxy accepts on IP alone), so there is no speed penalty. + Credentials are embedded in the server URL rather than passed as separate + fields. Separate fields cause Chromium to wait for a 407 challenge before + sending auth — DataImpulse takes ~14s to issue that challenge, making every + page.goto() timeout. Embedded credentials are sent on the first CONNECT + request, bypassing the round-trip entirely. """ if not is_enabled(cfg): return None + from urllib.parse import quote + user = quote(username(cfg, session_id), safe='') + pwd = quote(cfg.get('password', ''), safe='') return { - 'server': f"http://{cfg['host']}:{cfg['port']}", - 'username': username(cfg, session_id), - 'password': cfg.get('password', ''), + 'server': f"http://{user}:{pwd}@{cfg['host']}:{cfg['port']}", } def httpx_proxy(cfg: dict, session_id: Optional[str] = None): - """httpx.Proxy object for AsyncClient(proxy=...). None when disabled. - - Uses the auth= kwarg so credentials are sent as a Proxy-Authorization - header rather than embedded in the URL. - """ + """httpx.Proxy object for AsyncClient(proxy=...). None when disabled.""" if not is_enabled(cfg): return None import httpx - return httpx.Proxy( - f"http://{cfg['host']}:{cfg['port']}", - auth=(username(cfg, session_id), cfg.get('password', '')), - ) + return httpx.Proxy(httpx_proxy_url(cfg, session_id)) def httpx_proxy_url(cfg: dict, session_id: Optional[str] = None) -> Optional[str]: - """Legacy URL form — prefer httpx_proxy() for new callers.""" + """Proxy URL for httpx.AsyncClient(proxy=...). None when disabled.""" if not is_enabled(cfg): return None - return f"http://{cfg['host']}:{cfg['port']}" + return f"http://{username(cfg, session_id)}:{cfg.get('password', '')}@{cfg['host']}:{cfg['port']}" def direct_httpx_proxy(db):