起点
为单次 fetch 调用使用一个 dispatcher,使代理配置显式化,进程中的其他网络客户端则保留各自的配置。
选择兼容的运行时与 dispatcher
这个服务端示例面向 Node.js 24 和 Undici 7。在一个单独的示例项目中运行 npm install undici@7。Node 内置的 fetch 接受与 Undici 兼容的 dispatcher。这与浏览器中的 fetch 不同,页面 JavaScript 无法任意选择系统代理。
在 PROXY_URL 中使用提供商文档给出的 HTTP(S) 网关,例如刻意无法工作的 https://proxy.example.invalid:8443。通过部署环境的密钥管理器注入 PROXY_USERNAME 和 PROXY_PASSWORD。不要把网关凭据放进前端代码。
把代理认证放在 dispatcher 上
保存为 proxy-check.mjs 并运行 node proxy-check.mjs。Basic 令牌属于代理 agent,而不是目标站点的 Authorization 请求头。该请求有 30 秒的中止期限,并且不会自动跟随重定向。
proxy-check.mjs · Node.js 24 + Undici 7
import { ProxyAgent } from 'undici';
function required(name) {
const value = process.env[name];
if (!value) throw new Error('Missing environment variable: ' + name);
return value;
}
const gateway = new URL(required('PROXY_URL'));
if (!['http:', 'https:'].includes(gateway.protocol) || gateway.username || gateway.password) {
throw new Error('Use a credential-free HTTP(S) proxy URL');
}
const credentials = required('PROXY_USERNAME') + ':' + required('PROXY_PASSWORD');
const dispatcher = new ProxyAgent({
uri: gateway.href,
token: 'Basic ' + Buffer.from(credentials).toString('base64'),
});
try {
const response = await fetch('https://example.com/', {
dispatcher,
signal: AbortSignal.timeout(30_000),
redirect: 'manual',
});
await response.body?.cancel();
if (!response.ok) throw new Error('Destination HTTP ' + response.status);
console.log({ status: response.status });
} finally {
await dispatcher.close();
}从诊断脚本走向工作进程
这个脚本刻意取消了响应体,因为诊断只需要状态码。使用结果的工作进程应在其预算内消费响应体。在继续之前务必消费或取消它,并在工作进程关闭时关闭 dispatcher。
对于重复的任务,复用一个并发受限的 dispatcher,而不是为每个请求创建一个。让每个任务的期限彼此独立。除非有意改变每个受影响客户端的路由,否则不要安装进程级的 dispatcher。
- HTTP 错误是一个响应;fetch 不会仅仅因为状态码是 4xx 或 5xx 而 reject。
- CONNECT 被拒绝可能在目标站点的 Response 存在之前就表现为 fetch 失败。
- 记录脱敏后的错误类别;不要记录 Basic 令牌或包含凭据的完整 URL。
上线之前
- 使用文档中给出的 Node 和 Undici 主版本。
- 显式传入 dispatcher。
- 消费或取消响应体,并关闭 agent。
参考来源与延伸阅读
本指南参考的技术资料。请以你所安装版本的文档以及代理服务商支持的配置为准。