Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bedaaa1

Browse filesBrowse files
joyeecheungaduh95
authored andcommitted
http: support http proxy for fetch under NODE_USE_ENV_PROXY
When enabled, Node.js parses the `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables during startup, and tunnels requests over the specified proxy. This currently only affects requests sent over `fetch()`. Support for other built-in `http` and `https` methods is under way. PR-URL: #57165 Refs: nodejs/undici#1650 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 660d573 commit bedaaa1
Copy full SHA for bedaaa1

File tree

Expand file treeCollapse file tree

7 files changed

+275
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+275
-0
lines changed
Open diff view settings
Collapse file

‎doc/api/cli.md‎

Copy file name to clipboardExpand all lines: doc/api/cli.md
+15Lines changed: 15 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -3629,6 +3629,21 @@ If `value` equals `'0'`, certificate validation is disabled for TLS connections.
36293629
This makes TLS, and HTTPS by extension, insecure. The use of this environment
36303630
variable is strongly discouraged.
36313631

3632+
### `NODE_USE_ENV_PROXY=1`
3633+
3634+
<!-- YAML
3635+
added: REPLACEME
3636+
-->
3637+
3638+
> Stability: 1.1 - Active Development
3639+
3640+
When enabled, Node.js parses the `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY`
3641+
environment variables during startup, and tunnels requests over the
3642+
specified proxy.
3643+
3644+
This currently only affects requests sent over `fetch()`. Support for other
3645+
built-in `http` and `https` methods is under way.
3646+
36323647
### `NODE_USE_SYSTEM_CA=1`
36333648

36343649
<!-- YAML
Collapse file

‎doc/node.1‎

Copy file name to clipboardExpand all lines: doc/node.1
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,19 @@ When set to
842842
.Ar 0 ,
843843
TLS certificate validation is disabled.
844844
.
845+
.It Ev NODE_USE_ENV_PROXY
846+
When enabled, Node.js parses the
847+
.Ar HTTP_PROXY
848+
,
849+
.Ar HTTPS_PROXY
850+
and
851+
.Ar NO_PROXY
852+
environment variables during startup, and tunnels requests over the specified proxy.
853+
.Pp
854+
This currently only affects requests sent over
855+
.Ar fetch() .
856+
Support for other built-in http and https methods is under way.
857+
.
845858
.It Ev NODE_USE_SYSTEM_CA
846859
Similar to
847860
.Fl -use-system-ca .
Collapse file

‎lib/internal/process/pre_execution.js‎

Copy file name to clipboardExpand all lines: lib/internal/process/pre_execution.js
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ function prepareExecution(options) {
127127
initializeConfigFileSupport();
128128

129129
require('internal/dns/utils').initializeDns();
130+
setupHttpProxy();
130131

131132
setupSymbolDisposePolyfill();
132133

@@ -189,6 +190,21 @@ function setupSymbolDisposePolyfill() {
189190
}
190191
}
191192

193+
function setupHttpProxy() {
194+
if (process.env.NODE_USE_ENV_PROXY &&
195+
(process.env.HTTP_PROXY || process.env.HTTPS_PROXY ||
196+
process.env.http_proxy || process.env.https_proxy)) {
197+
const { setGlobalDispatcher, EnvHttpProxyAgent } = require('internal/deps/undici/undici');
198+
const envHttpProxyAgent = new EnvHttpProxyAgent();
199+
setGlobalDispatcher(envHttpProxyAgent);
200+
// TODO(joyeecheung): This currently only affects fetch. Implement handling in the
201+
// http/https Agent constructor too.
202+
// TODO(joyeecheung): This is currently guarded with NODE_USE_ENV_PROXY. Investigate whether
203+
// it's possible to enable it by default without stepping on other existing libraries that
204+
// sets the global dispatcher or monkey patches the global agent.
205+
}
206+
}
207+
192208
function setupUserModules(forceDefaultLoader = false) {
193209
initializeCJSLoader();
194210
initializeESMLoader(forceDefaultLoader);
Collapse file

‎test/common/proxy-server.js‎

Copy file name to clipboard
+100Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use strict';
2+
3+
const net = require('net');
4+
const http = require('http');
5+
const assert = require('assert');
6+
7+
function logRequest(logs, req) {
8+
logs.push({
9+
method: req.method,
10+
url: req.url,
11+
headers: { ...req.headers },
12+
});
13+
}
14+
15+
// This creates a minimal proxy server that logs the requests it gets
16+
// to an array before performing proxying.
17+
exports.createProxyServer = function() {
18+
const logs = [];
19+
20+
const proxy = http.createServer();
21+
proxy.on('request', (req, res) => {
22+
logRequest(logs, req);
23+
const [hostname, port] = req.headers.host.split(':');
24+
const targetPort = port || 80;
25+
26+
const options = {
27+
hostname: hostname,
28+
port: targetPort,
29+
path: req.url,
30+
method: req.method,
31+
headers: req.headers,
32+
};
33+
34+
const proxyReq = http.request(options, (proxyRes) => {
35+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
36+
proxyRes.pipe(res, { end: true });
37+
});
38+
39+
proxyReq.on('error', (err) => {
40+
logs.push({ error: err, source: 'proxy request' });
41+
res.writeHead(500);
42+
res.end('Proxy error: ' + err.message);
43+
});
44+
45+
req.pipe(proxyReq, { end: true });
46+
});
47+
48+
proxy.on('connect', (req, res, head) => {
49+
logRequest(logs, req);
50+
51+
const [hostname, port] = req.url.split(':');
52+
const proxyReq = net.connect(port, hostname, () => {
53+
res.write(
54+
'HTTP/1.1 200 Connection Established\r\n' +
55+
'Proxy-agent: Node.js-Proxy\r\n' +
56+
'\r\n',
57+
);
58+
proxyReq.write(head);
59+
res.pipe(proxyReq);
60+
proxyReq.pipe(res);
61+
});
62+
63+
proxyReq.on('error', (err) => {
64+
logs.push({ error: err, source: 'proxy request' });
65+
res.write('HTTP/1.1 500 Connection Error\r\n\r\n');
66+
res.end('Proxy error: ' + err.message);
67+
});
68+
});
69+
70+
proxy.on('error', (err) => {
71+
logs.push({ error: err, source: 'proxy server' });
72+
});
73+
74+
return { proxy, logs };
75+
};
76+
77+
exports.checkProxiedRequest = async function(envExtension, expectation) {
78+
const { spawnPromisified } = require('./');
79+
const fixtures = require('./fixtures');
80+
const { code, signal, stdout, stderr } = await spawnPromisified(
81+
process.execPath,
82+
['--no-warnings', fixtures.path('fetch-and-log.mjs')], {
83+
env: {
84+
...process.env,
85+
...envExtension,
86+
},
87+
});
88+
89+
assert.deepStrictEqual({
90+
stderr: stderr.trim(),
91+
stdout: stdout.trim(),
92+
code,
93+
signal,
94+
}, {
95+
stderr: '',
96+
code: 0,
97+
signal: null,
98+
...expectation,
99+
});
100+
};
Collapse file

‎test/fixtures/fetch-and-log.mjs‎

Copy file name to clipboard
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const response = await fetch(process.env.FETCH_URL);
2+
const body = await response.text();
3+
console.log(body);
Collapse file
+61Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { once } = require('events');
6+
const http = require('http');
7+
const { createProxyServer, checkProxiedRequest } = require('../common/proxy-server');
8+
9+
(async () => {
10+
// Start a server to process the final request.
11+
const server = http.createServer(common.mustCall((req, res) => {
12+
res.end('Hello world');
13+
}, 2));
14+
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
15+
server.listen(0);
16+
await once(server, 'listening');
17+
18+
// Start a minimal proxy server.
19+
const { proxy, logs } = createProxyServer();
20+
proxy.listen(0);
21+
await once(proxy, 'listening');
22+
23+
const serverHost = `localhost:${server.address().port}`;
24+
25+
// FIXME(undici:4083): undici currently always tunnels the request over
26+
// CONNECT, no matter it's HTTP traffic or not, which is different from e.g.
27+
// how curl behaves.
28+
const expectedLogs = [{
29+
method: 'CONNECT',
30+
url: serverHost,
31+
headers: {
32+
// FIXME(undici:4086): this should be keep-alive.
33+
connection: 'close',
34+
host: serverHost
35+
}
36+
}];
37+
38+
// Check upper-cased HTTPS_PROXY environment variable.
39+
await checkProxiedRequest({
40+
NODE_USE_ENV_PROXY: 1,
41+
FETCH_URL: `http://${serverHost}/test`,
42+
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
43+
}, {
44+
stdout: 'Hello world',
45+
});
46+
assert.deepStrictEqual(logs, expectedLogs);
47+
48+
// Check lower-cased https_proxy environment variable.
49+
logs.splice(0, logs.length);
50+
await checkProxiedRequest({
51+
NODE_USE_ENV_PROXY: 1,
52+
FETCH_URL: `http://${serverHost}/test`,
53+
http_proxy: `http://localhost:${proxy.address().port}`,
54+
}, {
55+
stdout: 'Hello world',
56+
});
57+
assert.deepStrictEqual(logs, expectedLogs);
58+
59+
proxy.close();
60+
server.close();
61+
})().then(common.mustCall());
Collapse file
+67Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const fixtures = require('../common/fixtures');
8+
const assert = require('assert');
9+
const https = require('https');
10+
const { once } = require('events');
11+
const { createProxyServer, checkProxiedRequest } = require('../common/proxy-server');
12+
13+
(async () => {
14+
// Start a server to process the final request.
15+
const server = https.createServer({
16+
cert: fixtures.readKey('agent8-cert.pem'),
17+
key: fixtures.readKey('agent8-key.pem'),
18+
}, common.mustCall((req, res) => {
19+
res.end('Hello world');
20+
}, 2));
21+
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
22+
server.listen(0);
23+
await once(server, 'listening');
24+
25+
// Start a minimal proxy server.
26+
const { proxy, logs } = createProxyServer();
27+
proxy.listen(0);
28+
await once(proxy, 'listening');
29+
30+
const serverHost = `localhost:${server.address().port}`;
31+
32+
const expectedLogs = [{
33+
method: 'CONNECT',
34+
url: serverHost,
35+
headers: {
36+
// FIXME(undici:4086): this should be keep-alive.
37+
connection: 'close',
38+
host: serverHost
39+
}
40+
}];
41+
42+
// Check upper-cased HTTPS_PROXY environment variable.
43+
await checkProxiedRequest({
44+
NODE_USE_ENV_PROXY: 1,
45+
FETCH_URL: `https://${serverHost}/test`,
46+
HTTPS_PROXY: `http://localhost:${proxy.address().port}`,
47+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
48+
}, {
49+
stdout: 'Hello world',
50+
});
51+
assert.deepStrictEqual(logs, expectedLogs);
52+
53+
// Check lower-cased https_proxy environment variable.
54+
logs.splice(0, logs.length);
55+
await checkProxiedRequest({
56+
NODE_USE_ENV_PROXY: 1,
57+
FETCH_URL: `https://${serverHost}/test`,
58+
https_proxy: `http://localhost:${proxy.address().port}`,
59+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
60+
}, {
61+
stdout: 'Hello world',
62+
});
63+
assert.deepStrictEqual(logs, expectedLogs);
64+
65+
proxy.close();
66+
server.close();
67+
})().then(common.mustCall());

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.