Node.js v20+如何为特定URL的Fetch API全局设置代理Agent
问题:全局拦截fetch请求,为特定域名选择性添加代理Agent
是否可以全局拦截fetch请求,仅针对特定域名的fetch请求选择性传入agent?我希望能拦截依赖的第三方代码发起的fetch请求。示例伪代码如下:
https.globalAgent.on('request', (request) => { if(request.url.includes('microsoft.com')){ // 伪代码思路:获取原始请求后,选择以下两种方式之一: // 1) 克隆并取消原始请求,用新的agent重新发起请求 // 2) 直接修改原始请求,添加`agent`参数 new Request(request.clone(), {agent: MY_CUSTOM_HTTPS_PROXY_AGENT}) } })
背景信息
- 使用Node.js v20+版本
- 依赖的第三方库会发起fetch请求到公网进行身份验证(例如microsoft.com)
- 公司服务器仅允许向内部域名(如mycompany.com、test.mycompany.com等)发起HTTP请求,外部域名请求需通过代理转发
当前困境
使用的身份验证库会向microsoft.com发起fetch请求,该域名不在公司允许列表内。目前临时方案是复刻/分叉该认证库,在请求中添加代理Agent:
let response = await fetch("some_domain.com", { method: "GET", headers: { Authorization: `Bearer ${accessToken}`, }, agent: MY_CUSTOM_HTTPS_PROXY_AGENT // 🟡 在分叉的认证库中添加了这一行 });
但不想维护分叉版本避免版本不一致,未来考虑提交PR但因涉及两个依赖库修改且时间紧张暂时搁置。
以下是可用的自定义HTTPS代理代码:
import net from 'net'; import tls from 'tls'; import https from 'https'; /* @example const agent = new HttpsProxyAgent({ proxy: { host: PROXY_HOST, port: PROXY_PORT }, key: fs.readFileSync(KEY_PATH), cert: fs.readFileSync(CERT_PATH), ca: fs.readFileSync(CA_CERT_PATH), }); */ class CustomHttpsProxyAgent extends https.Agent { constructor(options) { super(options); this.proxy = options.proxy; this.key = options.key; this.cert = options.cert; this.ca = options.ca; } createConnection(options, callback) { const proxySocket = net.connect(this.proxy); proxySocket.on('connect', () => { const connectRequest = `CONNECT ${options.host}:${options.port} HTTP/1.1\r\n\r\n`; proxySocket.write(connectRequest); proxySocket.once('data', () => { const tlsSocket = tls.connect({ socket: proxySocket, servername: options.host, key: this.key, cert: this.cert, ca: this.ca, }); callback(null, tlsSocket); }); }); } } export default CustomHttpsProxyAgent;
解决方案
在Node.js v20+中,你可以通过重写全局fetch函数实现全局拦截并选择性添加代理Agent,无需修改第三方库代码,这种方式能覆盖所有调用fetch的场景(包括第三方库发起的请求)。
步骤1:定义代理域名规则
先明确需要走代理的域名,编写判断逻辑:
const NEED_PROXY_DOMAINS = [ 'microsoft.com', // 可添加其他需要代理的外部域名 ]; // 判断URL是否需要走代理的工具函数 function shouldUseProxy(url) { const urlObj = new URL(url); return NEED_PROXY_DOMAINS.some(domain => urlObj.hostname.endsWith(domain)); }
步骤2:初始化自定义代理Agent
根据提供的CustomHttpsProxyAgent代码,初始化实例:
import fs from 'fs'; import CustomHttpsProxyAgent from './path/to/CustomHttpsProxyAgent.js'; const PROXY_HOST = 'your-proxy-host'; const PROXY_PORT = 8080; // 替换为实际代理端口 const KEY_PATH = './path/to/key.pem'; const CERT_PATH = './path/to/cert.pem'; const CA_CERT_PATH = './path/to/ca.pem'; const MY_CUSTOM_HTTPS_PROXY_AGENT = new CustomHttpsProxyAgent({ proxy: { host: PROXY_HOST, port: PROXY_PORT }, key: fs.readFileSync(KEY_PATH), cert: fs.readFileSync(CERT_PATH), ca: fs.readFileSync(CA_CERT_PATH), });
步骤3:重写全局fetch函数
保存原始fetch引用,包装成带代理逻辑的新函数:
// 保存原始fetch引用 const originalFetch = globalThis.fetch; // 重写全局fetch globalThis.fetch = async function(resource, options = {}) { // 提取请求URL:兼容字符串和Request对象两种传入方式 const requestUrl = typeof resource === 'string' ? resource : resource.url; // 判断是否需要添加代理 if (shouldUseProxy(requestUrl)) { const modifiedOptions = { ...options, agent: MY_CUSTOM_HTTPS_PROXY_AGENT }; // Request对象不可变,需克隆后传入新配置 if (resource instanceof Request) { return originalFetch(new Request(resource, modifiedOptions)); } else { return originalFetch(resource, modifiedOptions); } } // 不需要代理的请求,直接调用原始fetch return originalFetch(resource, options); };
关键注意事项
- 执行时机:这段重写代码必须在第三方库加载之前执行,确保所有fetch调用都经过包装。
- Request对象处理:若第三方库传入的是
Request实例,必须通过new Request(resource, modifiedOptions)克隆修改,因为Request对象本身是不可变的。 - HTTP请求兼容:如果需要处理HTTP(非HTTPS)请求,可仿照
CustomHttpsProxyAgent实现CustomHttpProxyAgent,并在判断逻辑中区分协议。 - 错误处理:可根据需求添加错误捕获逻辑,避免代理配置问题影响正常内部请求。
为什么不直接用https.globalAgent?
Node.js v20+的fetch底层基于undici库(而非传统http/https模块),监听https.globalAgent的事件无法拦截fetch请求,重写全局fetch是更可靠的方案。
内容的提问来源于stack exchange,提问作者Clifford Fajardo
相关产品推荐
相关产品推荐

