From f6d5fb27e1795d35c88160fe80b8e762f1c4a466 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Fri, 10 Jul 2026 08:57:25 +0000 Subject: [PATCH] Fix proxy test: URL-encode httpx credentials, use Proxy object, improve error detail DataImpulse username contains semicolons (;sessid.ID) that weren't being URL-encoded in httpx_proxy_url, causing silent parse failures. Also use httpx.Proxy object instead of raw string (consistent with httpx_proxy()), and capture repr(e) so empty-message exceptions show their type. Co-Authored-By: Claude Sonnet 4.6 --- backend/api/competitors.py | 11 ++++++----- backend/services/proxy.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/api/competitors.py b/backend/api/competitors.py index 02504fd..2acb113 100644 --- a/backend/api/competitors.py +++ b/backend/api/competitors.py @@ -280,17 +280,18 @@ async def test_proxy_config( 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_url, timeout=40.0) as client: + async with httpx.AsyncClient(proxy=proxy, timeout=40.0) as client: resp = await client.get("https://ipinfo.io/json") resp.raise_for_status() data = resp.json() except Exception as e: - logger.warning(f"Proxy test failed: {e}") - raise HTTPException(status_code=502, detail=f"Proxy test failed: {e}") + detail = repr(e) or str(e) or type(e).__name__ + logger.warning(f"Proxy test failed: {detail}") + raise HTTPException(status_code=400, detail=f"Proxy test failed: {detail}") return { 'ok': True, diff --git a/backend/services/proxy.py b/backend/services/proxy.py index b9313bc..336a71b 100644 --- a/backend/services/proxy.py +++ b/backend/services/proxy.py @@ -129,8 +129,9 @@ def httpx_proxy_url(cfg: dict, session_id: Optional[str] = None) -> Optional[str """Proxy URL for httpx.AsyncClient(proxy=...). None when disabled.""" if not is_enabled(cfg): return None + from urllib.parse import quote user, pwd = _build_auth(cfg, session_id) - return f"http://{user}:{pwd}@{cfg['host']}:{cfg['port']}" + return f"http://{quote(user, safe='')}:{quote(pwd, safe='')}@{cfg['host']}:{cfg['port']}" def direct_httpx_proxy(db):