fix(core): respect NO_PROXY in global fetch dispatcher (#27216)

This commit is contained in:
Coco Sheng
2026-05-19 13:56:59 -04:00
committed by GitHub
parent fcc8c62b8b
commit 37f3a4c90a
5 changed files with 162 additions and 17 deletions
+74 -7
View File
@@ -10,16 +10,16 @@ import * as dnsPromises from 'node:dns/promises';
import type { LookupAddress, LookupAllOptions } from 'node:dns';
import ipaddr from 'ipaddr.js';
const { setGlobalDispatcher, Agent, ProxyAgent } = vi.hoisted(() => ({
const { setGlobalDispatcher, Agent, EnvHttpProxyAgent } = vi.hoisted(() => ({
setGlobalDispatcher: vi.fn(),
Agent: vi.fn(),
ProxyAgent: vi.fn(),
EnvHttpProxyAgent: vi.fn(),
}));
vi.mock('undici', () => ({
setGlobalDispatcher,
Agent,
ProxyAgent,
EnvHttpProxyAgent,
}));
vi.mock('node:dns/promises', () => ({
@@ -33,6 +33,7 @@ const {
isAddressPrivate,
fetchWithTimeout,
setGlobalProxy,
createSafeProxyAgent,
} = await import('./fetch.js');
interface ErrorWithCode extends Error {
code?: string;
@@ -54,6 +55,8 @@ describe('fetch utils', () => {
}
return [{ address: '93.184.216.34', family: 4 }];
});
vi.unstubAllEnvs();
updateGlobalFetchTimeouts(60000);
});
afterEach(() => {
@@ -237,17 +240,81 @@ describe('fetch utils', () => {
});
describe('setGlobalProxy', () => {
it('should configure ProxyAgent with experiment flag timeout', () => {
const proxyUrl = 'http://proxy.example.com';
it('should configure EnvHttpProxyAgent with experiment flag timeout and noProxy', () => {
const proxyUrl = ' http://proxy.example.com ';
const noProxyValue = ' localhost,127.0.0.1 ';
vi.stubEnv('NO_PROXY', noProxyValue);
updateGlobalFetchTimeouts(45773134);
setGlobalProxy(proxyUrl);
expect(ProxyAgent).toHaveBeenCalledWith({
uri: proxyUrl,
expect(EnvHttpProxyAgent).toHaveBeenCalledWith({
httpProxy: 'http://proxy.example.com',
httpsProxy: 'http://proxy.example.com',
noProxy: 'localhost,127.0.0.1',
headersTimeout: 45773134,
bodyTimeout: 300000,
});
expect(setGlobalDispatcher).toHaveBeenCalled();
});
it('should fall back to no_proxy if NO_PROXY is not set', () => {
const proxyUrl = 'http://proxy.example.com';
const noProxyValue = 'localhost,127.0.0.1';
vi.stubEnv('no_proxy', noProxyValue);
setGlobalProxy(proxyUrl);
expect(EnvHttpProxyAgent).toHaveBeenCalledWith(
expect.objectContaining({
noProxy: noProxyValue,
}),
);
});
it('should handle empty NO_PROXY', () => {
const proxyUrl = 'http://proxy.example.com';
vi.stubEnv('NO_PROXY', '');
setGlobalProxy(proxyUrl);
expect(EnvHttpProxyAgent).toHaveBeenCalledWith(
expect.objectContaining({
noProxy: '',
}),
);
});
it('should handle multi-entry NO_PROXY with trimming', () => {
const proxyUrl = 'http://proxy.example.com';
const noProxyValue = ' google.com, 127.0.0.1 , localhost ';
vi.stubEnv('NO_PROXY', noProxyValue);
setGlobalProxy(proxyUrl);
expect(EnvHttpProxyAgent).toHaveBeenCalledWith(
expect.objectContaining({
noProxy: 'google.com, 127.0.0.1 , localhost',
}),
);
});
});
describe('createSafeProxyAgent', () => {
it('should create an EnvHttpProxyAgent with trimmed values and default timeouts', () => {
const proxyUrl = ' http://proxy.example.com ';
const noProxyValue = ' localhost,127.0.0.1 ';
vi.stubEnv('NO_PROXY', noProxyValue);
createSafeProxyAgent(proxyUrl);
expect(EnvHttpProxyAgent).toHaveBeenCalledWith({
httpProxy: 'http://proxy.example.com',
httpsProxy: 'http://proxy.example.com',
noProxy: 'localhost,127.0.0.1',
headersTimeout: 60000,
bodyTimeout: 300000,
});
});
});
});
+26 -8
View File
@@ -6,7 +6,7 @@
import { getErrorMessage, isAbortError } from './errors.js';
import { URL } from 'node:url';
import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici';
import { Agent, EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
import ipaddr from 'ipaddr.js';
import { lookup } from 'node:dns/promises';
@@ -169,11 +169,21 @@ export async function isPrivateIpAsync(url: string): Promise<boolean> {
}
/**
* Creates an undici ProxyAgent that incorporates safe DNS lookup.
* Creates an undici EnvHttpProxyAgent that incorporates safe DNS lookup.
*/
export function createSafeProxyAgent(proxyUrl: string): ProxyAgent {
return new ProxyAgent({
uri: proxyUrl,
export function createSafeProxyAgent(proxyUrl: string): EnvHttpProxyAgent {
const trimmedProxy = proxyUrl.trim();
const noProxy = (
process.env['NO_PROXY'] ??
process.env['no_proxy'] ??
''
)?.trim();
return new EnvHttpProxyAgent({
httpProxy: trimmedProxy,
httpsProxy: trimmedProxy,
noProxy,
headersTimeout: defaultHeadersTimeout,
bodyTimeout: defaultBodyTimeout,
});
}
@@ -220,10 +230,18 @@ export async function fetchWithTimeout(
}
export function setGlobalProxy(proxy: string) {
currentProxy = proxy;
const trimmedProxy = proxy.trim();
currentProxy = trimmedProxy;
const noProxy = (
process.env['NO_PROXY'] ??
process.env['no_proxy'] ??
''
)?.trim();
setGlobalDispatcher(
new ProxyAgent({
uri: proxy,
new EnvHttpProxyAgent({
httpProxy: trimmedProxy,
httpsProxy: trimmedProxy,
noProxy,
headersTimeout: defaultHeadersTimeout,
bodyTimeout: defaultBodyTimeout,
}),