fix: 代理改用 https.request+HttpsProxyAgent 直连,绕过 undici fetch 兼容问题

This commit is contained in:
thzxx
2026-06-10 16:22:13 +08:00
parent c718d99108
commit 7f3f4d3914
+39 -21
View File
@@ -207,30 +207,48 @@ function buildFetchHeaders(url: string, useMobileUA: boolean): Record<string, st
}; };
} }
/** 带超时的 fetch 封装 */ /** 带超时的 fetch 封装。有代理时走 https.request + HttpsProxyAgent */
async function fetchWithTimeout(url: string, timeout = HTTP_TIMEOUT, headers?: Record<string,string>): Promise<Response | null> { async function fetchWithTimeout(url: string, timeout = HTTP_TIMEOUT, headers?: Record<string,string>): Promise<Response | null> {
const _headers = headers || buildFetchHeaders(url, false); const _headers = headers || buildFetchHeaders(url, false);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout); // 有代理时用 https.request(兼容 https-proxy-agent
try { const proxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || '';
const agent = getProxyAgent(); if (proxy) {
const fetchOpts: any = { try {
headers: _headers, const { HttpsProxyAgent } = require('https-proxy-agent');
signal: controller.signal, return new Promise((resolve) => {
redirect: 'follow', const mod = require(url.startsWith('https:') ? 'https' : 'http');
}; const u = new URL(url);
if (agent) fetchOpts.dispatcher = agent; const req = mod.request({
const resp = await fetch(url, fetchOpts); hostname: u.hostname, port: u.port || (url.startsWith('https:') ? 443 : 80),
clearTimeout(timeoutId); path: u.pathname + u.search, method: 'GET',
return resp; headers: { ..._headers, Host: u.hostname },
} catch (err) { agent: new HttpsProxyAgent(proxy), timeout, rejectUnauthorized: false,
clearTimeout(timeoutId); }, (res: any) => {
if (_proxyUrl) { let body = '';
const msg = (err as Error).message || ''; res.on('data', (d: Buffer) => body += d.toString());
sendLog('warn', `🌐 fetch 失败`, `${url.slice(0, 80)} | 代理=${_proxyUrl} | ${msg.slice(0, 100)}`); res.on('end', () => resolve({
} ok: res.statusCode >= 200 && res.statusCode < 400,
return null; status: res.statusCode, statusText: res.statusMessage,
headers: new Headers(res.headers as Record<string,string>),
text: () => Promise.resolve(body),
arrayBuffer: () => Promise.resolve(Buffer.from(body).buffer),
} as unknown as Response));
});
req.on('timeout', () => req.destroy());
req.on('error', (e: Error) => { sendLog('warn', `🌐 代理请求失败`, `${url.slice(0,80)} | ${e.message.slice(0,100)}`); resolve(null); });
req.end();
});
} catch { /* 代理包不可用,回退到原生 fetch */ }
} }
// 无代理 / 代理包不可用 → 原生 fetch
const controller = new AbortController();
const tid = setTimeout(() => controller.abort(), timeout);
try {
const resp = await fetch(url, { headers: _headers, signal: controller.signal, redirect: 'follow' });
clearTimeout(tid); return resp;
} catch { clearTimeout(tid); return null; }
} }
/** 完整 HTML 实体映射(常见实体) */ /** 完整 HTML 实体映射(常见实体) */