Files
CloudSearch/packages/backend/src/utils/proxy-agent.ts

144 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 统一代理工具 — 支持 HTTP/HTTPS/SOCKS5/SOCKS5h 协议
*
* Node 20+ 原生 fetch() 使用 undici Dispatcher但 socks-proxy-agent 不实现此接口。
* 解决方案:使用 http.Agent 接口 + http/https.request()。
*/
let HttpsProxyAgent: any;
let SocksProxyAgent: any;
try {
HttpsProxyAgent = require('https-proxy-agent').HttpsProxyAgent;
} catch {
try { HttpsProxyAgent = require('https-proxy-agent'); } catch {}
}
try {
SocksProxyAgent = require('socks-proxy-agent').SocksProxyAgent;
} catch {
try { SocksProxyAgent = require('socks-proxy-agent'); } catch {}
}
/** Create an http.Agent for the given proxy URL (works with https.request) */
function createProxyAgent(proxyUrl: string): any | null {
if (!proxyUrl || typeof proxyUrl !== 'string') return null;
const trimmed = proxyUrl.trim();
if (!trimmed) return null;
const lower = trimmed.toLowerCase();
try {
if (lower.startsWith('socks5://') || lower.startsWith('socks5h://')) {
if (!SocksProxyAgent) {
console.warn('[Proxy] socks-proxy-agent not installed');
return null;
}
return new SocksProxyAgent(trimmed);
}
if (lower.startsWith('http://') || lower.startsWith('https://')) {
if (!HttpsProxyAgent) {
console.warn('[Proxy] No HTTP proxy agent available');
return null;
}
return new HttpsProxyAgent(trimmed);
}
// Unknown scheme — try as HTTP proxy
if (HttpsProxyAgent) return new HttpsProxyAgent(trimmed);
return null;
} catch (err: any) {
console.error(`[Proxy] Failed to create proxy agent: ${err.message}`);
return null;
}
}
/**
* Fetch with proxy support.
* Uses native fetch() when no proxy, or http/https.request() with agent when proxy is set.
*/
export async function proxiedFetch(
url: string,
init?: RequestInit,
proxyUrl?: string,
): Promise<Response> {
if (!proxyUrl) return fetch(url, init);
const agent = createProxyAgent(proxyUrl);
if (!agent) return fetch(url, init);
const parsedUrl = new URL(url);
const mod = parsedUrl.protocol === 'https:' ? require('https') : require('http');
return new Promise((resolve, reject) => {
const headers: Record<string, string> = {};
if (init?.headers) {
const h = init.headers as any;
if (h instanceof Headers) {
h.forEach((v, k) => { headers[k] = v; });
} else if (typeof h === 'object') {
Object.assign(headers, h);
}
}
const options: any = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
path: parsedUrl.pathname + parsedUrl.search,
method: init?.method || 'GET',
headers,
agent,
};
const req = mod.request(options, (res: any) => {
const chunks: Buffer[] = [];
res.on('data', (c: Buffer) => chunks.push(c));
res.on('end', () => {
const body = Buffer.concat(chunks);
resolve(new Response(body, {
status: res.statusCode || 502,
statusText: res.statusMessage || '',
headers: new Headers(res.headers || {}),
}));
});
});
req.on('error', reject);
if (init?.signal) {
init.signal.addEventListener('abort', () => req.destroy());
}
if (init?.body) {
req.write(
typeof init.body === 'string' ? init.body :
init.body instanceof Buffer ? init.body :
init.body instanceof ArrayBuffer ? Buffer.from(init.body) :
Buffer.from(String(init.body))
);
}
req.end();
});
}
export async function testProxyConnection(
proxyUrl: string,
testUrl?: string,
): Promise<{ ok: boolean; latency: number; info: string }> {
const target = testUrl || 'https://www.baidu.com';
const start = Date.now();
try {
const res = await proxiedFetch(target, {
signal: AbortSignal.timeout(10000),
}, proxyUrl);
const latency = Date.now() - start;
return { ok: true, latency, info: `连接成功 (${res.status})` };
} catch (err: any) {
return { ok: false, latency: Date.now() - start, info: `代理连接失败: ${err.message}` };
}
}
// Legacy compat — no longer returns dispatcher, kept for type compatibility
export function createProxyDispatcher(proxyUrl: string): { agent?: any } | null {
const agent = createProxyAgent(proxyUrl);
return agent ? { agent } : null;
}