From 1a701f6503ad77e06c0bf47e3bdaf84acf8bf311 Mon Sep 17 00:00:00 2001 From: swigger Date: Thu, 16 Jul 2026 11:12:55 +0800 Subject: [PATCH] http: use env proxy when agent has no proxyEnv option When a user creates a custom agent without specifying proxyEnv but Node.js is configured to use a proxy from the environment (via --use-env-proxy or NODE_USE_ENV_PROXY), fall back to process.env. A developer can still explicitly disable proxying for an agent even when Node.js is configured to use a proxy at runtime, by passing a falsy proxyEnv explicitly. For example: const agent = new https.Agent({ proxyEnv: null }); Signed-off-by: swigger --- doc/api/http.md | 16 +++- lib/_http_agent.js | 5 +- .../client-proxy/test-use-env-proxy-agent.mjs | 75 +++++++++++++++++++ test/fixtures/agent-request-and-log.js | 28 +++++++ 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 test/client-proxy/test-use-env-proxy-agent.mjs create mode 100644 test/fixtures/agent-request-and-log.js diff --git a/doc/api/http.md b/doc/api/http.md index 3cdcab71361260..05d1bd0231dc7d 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -208,7 +208,9 @@ changes: * `timeout` {number} Socket timeout in milliseconds. This will set the timeout when the socket is created. * `proxyEnv` {Object|undefined} Environment variables for proxy configuration. - See [Built-in Proxy Support][] for details. **Default:** `undefined` + See [Built-in Proxy Support][] for details. **Default:** `undefined`; when + Node.js is configured to use the environment proxy, defaults to `process.env` + unless a falsy value (such as `null`) is passed to opt out. * `HTTP_PROXY` {string|undefined} URL for the proxy server that HTTP requests should use. If undefined, no proxy is used for HTTP requests. * `HTTPS_PROXY` {string|undefined} URL for the proxy server that HTTPS requests should use. @@ -4554,12 +4556,22 @@ When Node.js creates the global agent, if the `NODE_USE_ENV_PROXY` environment v set to `1` or `--use-env-proxy` is enabled, the global agent will be constructed with `proxyEnv: process.env`, enabling proxy support based on the environment variables. +The same fallback applies to any agent constructed without an explicit `proxyEnv` +option, not just the global agent. To opt a specific agent out, pass a falsy +`proxyEnv` value such as `null`: + +```js +// Never uses the environment proxy, regardless of --use-env-proxy or NODE_USE_ENV_PROXY. +const agent = new Agent({ proxyEnv: null }); +``` + To enable proxy support dynamically and globally, use [`http.setGlobalProxyFromEnv()`][]. Custom agents can also be created with proxy support by passing a `proxyEnv` option when constructing the agent. The value can be `process.env` if they just want to inherit the configuration from the environment variables, -or an object with specific setting overriding the environment. +or an object with specific setting overriding the environment, or null +to explicitly disable proxy support. The following properties of the `proxyEnv` are checked to configure proxy support. diff --git a/lib/_http_agent.js b/lib/_http_agent.js index eb58650f885105..5f560c9d90b4e0 100644 --- a/lib/_http_agent.js +++ b/lib/_http_agent.js @@ -24,6 +24,7 @@ const { NumberIsFinite, NumberParseInt, + ObjectHasOwn, ObjectKeys, ObjectSetPrototypeOf, ObjectValues, @@ -184,7 +185,9 @@ function Agent(options) { this.options.agentKeepAliveTimeoutBuffer : 1000; - const proxyEnv = this.options.proxyEnv; + const proxyEnv = ObjectHasOwn(this.options, 'proxyEnv') ? + this.options.proxyEnv : + (getOptionValue('--use-env-proxy') ? process.env : undefined); if (typeof proxyEnv === 'object' && proxyEnv !== null) { this[kProxyConfig] = parseProxyConfigFromEnv(proxyEnv, this.protocol, this.keepAlive); debug(`new ${this.protocol} agent with proxy config`, this[kProxyConfig]); diff --git a/test/client-proxy/test-use-env-proxy-agent.mjs b/test/client-proxy/test-use-env-proxy-agent.mjs new file mode 100644 index 00000000000000..5e02028f63dd03 --- /dev/null +++ b/test/client-proxy/test-use-env-proxy-agent.mjs @@ -0,0 +1,75 @@ +// This tests that when Node.js is configured to use the environment proxy +// (via --use-env-proxy or NODE_USE_ENV_PROXY), a custom agent created without +// a `proxyEnv` option falls back to process.env and goes through the proxy, +// while an agent created with an explicit `proxyEnv: null` bypasses it. + +import * as common from '../common/index.mjs'; +import assert from 'node:assert'; +import http from 'node:http'; +import { once } from 'events'; +import fixtures from '../common/fixtures.js'; +import { createProxyServer } from '../common/proxy-server.js'; + +// Start a minimal proxy server. +const { proxy, logs } = createProxyServer(); +proxy.listen(0); +await once(proxy, 'listening'); + +delete process.env.NODE_USE_ENV_PROXY; // Ensure the environment variable is not set. + +// Start a HTTP server to process the final request. +const server = http.createServer(common.mustCall((req, res) => { + res.end('Hello world'); +}, 2)); +server.on('error', common.mustNotCall((err) => { console.error('Server error', err); })); +server.listen(0); +await once(server, 'listening'); + +const serverHost = `localhost:${server.address().port}`; +const requestUrl = `http://${serverHost}/test`; +const script = fixtures.path('agent-request-and-log.js'); + +function runAgentRequest(env, cliArgs = []) { + return common.spawnPromisified(process.execPath, [...cliArgs, script], { + env: { + ...process.env, + REQUEST_URL: requestUrl, + HTTP_PROXY: `http://localhost:${proxy.address().port}`, + ...env, + }, + }); +} + +// A plain agent without a `proxyEnv` option should use the environment proxy +// when Node.js is started with --use-env-proxy. +{ + const { code, signal, stderr, stdout } = await runAgentRequest({}, ['--use-env-proxy']); + assert.strictEqual(stderr.trim(), ''); + assert.match(stdout, /Hello world/); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + // The request should go through the proxy. + assert.strictEqual(logs.length, 1); + assert.strictEqual(logs[0].method, 'GET'); + assert.strictEqual(logs[0].url, requestUrl); +} + +// An agent created with an explicit `proxyEnv: null` should bypass the proxy +// even when Node.js is configured to use the environment proxy. The same must +// hold whether the proxy is enabled via NODE_USE_ENV_PROXY or --use-env-proxy. +{ + logs.splice(0, logs.length); + const { code, signal, stderr, stdout } = await runAgentRequest({ + NODE_USE_ENV_PROXY: '1', + PROXY_ENV_NULL: '1', + }); + assert.strictEqual(stderr.trim(), ''); + assert.match(stdout, /Hello world/); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + // The request should reach the server directly, so the proxy sees nothing. + assert.strictEqual(logs.length, 0); +} + +server.close(); +proxy.close(); diff --git a/test/fixtures/agent-request-and-log.js b/test/fixtures/agent-request-and-log.js new file mode 100644 index 00000000000000..61aa21ba24adc6 --- /dev/null +++ b/test/fixtures/agent-request-and-log.js @@ -0,0 +1,28 @@ +'use strict'; + +// A minimal fixture that issues a single HTTP GET request using a +// user-provided agent. It is used to exercise how an agent picks up the +// environment proxy configuration when Node.js is started with +// --use-env-proxy or NODE_USE_ENV_PROXY. +// +// When PROXY_ENV_NULL is set, the agent is created with an explicit +// `proxyEnv: null`, which should disable proxying even when Node.js is +// configured to use the environment proxy. Otherwise a plain agent is +// created without any `proxyEnv` option, which should fall back to +// process.env when the environment proxy is enabled. + +const http = require('http'); + +const url = process.env.REQUEST_URL; + +const agent = process.env.PROXY_ENV_NULL ? + new http.Agent({ proxyEnv: null }) : + new http.Agent(); + +const req = http.get(url, { agent }, (res) => { + res.pipe(process.stdout); +}); + +req.on('error', (e) => { + console.error('Request Error', e); +});