fix: 修复代理状态检测不正确

This commit is contained in:
Daniel
2026-03-24 19:38:44 +08:00
parent 6a68d5b66a
commit eb8d43841c
2 changed files with 71 additions and 54 deletions

View File

@@ -421,7 +421,42 @@ PROXY_CHECK_URL = os.getenv("PROXY_CHECK_URL", "https://httpbin.org/ip")
@app.get("/api/check-proxy") @app.get("/api/check-proxy")
async def api_check_proxy(proxy: Optional[str] = Query(None, description="可选,指定要检测的代理 URL不传则用环境变量/隧道/KDL")): async def api_check_proxy(proxy: Optional[str] = Query(None, description="可选,指定要检测的代理 URL不传则用环境变量/隧道/KDL")):
"""检测代理是否可用:用解析到的代理请求测试页,返回是否成功及来源""" """检测代理是否可用:返回连通状态、出口 IP若可解析与错误原因"""
def _extract_origin_ip(resp: httpx.Response) -> Optional[str]:
"""优先解析 httpbin /ip 的 origin 字段,解析失败返回 None。"""
try:
data = resp.json()
if isinstance(data, dict):
origin = (data.get("origin") or "").strip()
return origin or None
except Exception:
pass
return None
def _format_proxy_result(ok: bool, *, source: str, preview: str, status_code: Optional[int] = None,
origin_ip: Optional[str] = None, error: Optional[str] = None, note: Optional[str] = None) -> dict:
status = "available" if ok else "unavailable"
out = {
"ok": ok,
"status": status,
"source": source,
"proxy_preview": preview,
"check_url": PROXY_CHECK_URL,
}
if status_code is not None:
out["status_code"] = status_code
if origin_ip:
out["origin_ip"] = origin_ip
if note:
out["note"] = note
if error:
out["error"] = error
# 常见 socks 认证失败给出更可执行的提示
if "User was rejected by the SOCKS5 server" in error:
out["reason"] = "proxy_auth_rejected"
out["suggestion"] = "请检查代理用户名/密码、端口、以及代理服务商白名单配置"
return out
proxy_url = (proxy or "").strip() proxy_url = (proxy or "").strip()
source = "query" source = "query"
if not proxy_url: if not proxy_url:
@@ -439,11 +474,12 @@ async def api_check_proxy(proxy: Optional[str] = Query(None, description="可选
if proxy_url: if proxy_url:
logger.info("check-proxy: using env/body, len=%s", len(proxy_url)) logger.info("check-proxy: using env/body, len=%s", len(proxy_url))
if not proxy_url: if not proxy_url:
return { return _format_proxy_result(
"ok": False, False,
"source": "none", source="none",
"error": "未配置代理。请填写代理、或设置 HTTP_PROXY/HTTPS_PROXY、或配置 TUNNEL_PROXY固定隧道、或 KDL 代理 API。", preview="(empty)",
} error="未配置代理。请填写代理、或设置 HTTP_PROXY/HTTPS_PROXY、或配置 TUNNEL_PROXY固定隧道、或 KDL 代理 API。",
)
# 脱敏显示(不暴露密码) # 脱敏显示(不暴露密码)
def _preview(u: str) -> str: def _preview(u: str) -> str:
if not u or "@" not in u: if not u or "@" not in u:
@@ -475,66 +511,42 @@ async def api_check_proxy(proxy: Optional[str] = Query(None, description="可选
async with httpx.AsyncClient(trust_env=False, timeout=15.0, transport=transport) as client: async with httpx.AsyncClient(trust_env=False, timeout=15.0, transport=transport) as client:
resp = await client.get(PROXY_CHECK_URL) resp = await client.get(PROXY_CHECK_URL)
if resp.status_code == 200: if resp.status_code == 200:
origin_ip = _extract_origin_ip(resp)
logger.info("check-proxy: ok (socks), status=%s", resp.status_code) logger.info("check-proxy: ok (socks), status=%s", resp.status_code)
return { return _format_proxy_result(
"ok": True, True, source=source, preview=preview, status_code=resp.status_code, origin_ip=origin_ip
"source": source, )
"proxy_preview": preview,
"check_url": PROXY_CHECK_URL,
"status_code": resp.status_code,
}
logger.warning("check-proxy: fail (socks), status=%s", resp.status_code) logger.warning("check-proxy: fail (socks), status=%s", resp.status_code)
return { return _format_proxy_result(
"ok": False, False, source=source, preview=preview, status_code=resp.status_code,
"source": source, error=f"请求测试页返回 {resp.status_code}"
"proxy_preview": preview, )
"error": f"请求测试页返回 {resp.status_code}",
"status_code": resp.status_code,
}
except ImportError: except ImportError:
logger.info("check-proxy: socks5 已配置,跳过连通性检测(需 pip install httpx-socks 方可检测)") logger.info("check-proxy: socks5 已配置,跳过连通性检测(需 pip install httpx-socks 方可检测)")
return { return _format_proxy_result(
"ok": True, True, source=source, preview=preview,
"source": source, note="socks5 代理已配置;连通性检测需安装 pip install httpx-socks"
"proxy_preview": preview, )
"note": "socks5 代理已配置;连通性检测需安装 pip install httpx-socks",
}
except Exception as e: except Exception as e:
logger.warning("check-proxy: socks exception %s", e) logger.warning("check-proxy: socks exception %s", e)
return { return _format_proxy_result(False, source=source, preview=preview, error=str(e))
"ok": False,
"source": source,
"proxy_preview": preview,
"error": str(e),
}
try: try:
async with httpx.AsyncClient(trust_env=False, timeout=15.0, proxy=proxy_url) as client: async with httpx.AsyncClient(trust_env=False, timeout=15.0, proxy=proxy_url) as client:
resp = await client.get(PROXY_CHECK_URL) resp = await client.get(PROXY_CHECK_URL)
if resp.status_code == 200: if resp.status_code == 200:
origin_ip = _extract_origin_ip(resp)
logger.info("check-proxy: ok, status=%s", resp.status_code) logger.info("check-proxy: ok, status=%s", resp.status_code)
return { return _format_proxy_result(
"ok": True, True, source=source, preview=preview, status_code=resp.status_code, origin_ip=origin_ip
"source": source, )
"proxy_preview": preview,
"check_url": PROXY_CHECK_URL,
"status_code": resp.status_code,
}
logger.warning("check-proxy: fail, status=%s", resp.status_code) logger.warning("check-proxy: fail, status=%s", resp.status_code)
return { return _format_proxy_result(
"ok": False, False, source=source, preview=preview, status_code=resp.status_code,
"source": source, error=f"请求测试页返回 {resp.status_code}"
"proxy_preview": preview, )
"error": f"请求测试页返回 {resp.status_code}",
"status_code": resp.status_code,
}
except Exception as e: except Exception as e:
logger.warning("check-proxy: exception %s", e) logger.warning("check-proxy: exception %s", e)
return { return _format_proxy_result(False, source=source, preview=preview, error=str(e))
"ok": False,
"source": source,
"proxy_preview": preview,
"error": str(e),
}
def _proxy_from_env() -> str: def _proxy_from_env() -> str:

View File

@@ -1050,9 +1050,14 @@
try { try {
var data = await callApi(url); var data = await callApi(url);
if (data && data.ok) { if (data && data.ok) {
log('代理正常。来源: ' + (data.source || '') + (data.proxy_preview ? '代理: ' + data.proxy_preview : ''), 'ok'); var msg = '代理可用。状态: ' + (data.status || 'available') + '来源: ' + (data.source || '');
if (data.origin_ip) msg += '出口IP: ' + data.origin_ip;
if (data.proxy_preview) msg += ',代理: ' + data.proxy_preview;
log(msg, 'ok');
} else { } else {
log('代理不可用: ' + (data && data.error ? data.error : JSON.stringify(data)), 'error'); var err = data && data.error ? data.error : JSON.stringify(data);
var detail = data && data.reason ? (',原因: ' + data.reason) : '';
log('代理不可用: ' + err + detail, 'error');
} }
} catch (e) { } catch (e) {
log('检测代理失败: ' + (e.message || e), 'error'); log('检测代理失败: ' + (e.message || e), 'error');