fix: 修复代理状态检测不正确
This commit is contained in:
116
backend/main.py
116
backend/main.py
@@ -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:
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user