GeezerVison is showing github.com — http: support http proxy for fetch under NODE_USE_ENV_PROXY · nodejs/node@bedaaa1
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -3629,6 +3629,21 @@ If `value` equals `'0'`, certificate validation is disabled for TLS connections. | 3629 | 3629 | This makes TLS, and HTTPS by extension, insecure. The use of this environment | 3630 | 3630 | variable is strongly discouraged. | 3631 | 3631 |
| | 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 | + | 3632 | 3647 | ### `NODE_USE_SYSTEM_CA=1` | 3633 | 3648 |
| 3634 | 3649 | <!-- YAML | |
Collapse file Copy file name to clipboardExpand all lines: doc/node.1 +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -842,6 +842,19 @@ When set to | 842 | 842 | .Ar 0 , | 843 | 843 | TLS certificate validation is disabled. | 844 | 844 | . | | 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 | +. | 845 | 858 | .It Ev NODE_USE_SYSTEM_CA | 846 | 859 | Similar to | 847 | 860 | .Fl -use-system-ca . | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/pre_execution.js +16Lines changed: 16 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -127,6 +127,7 @@ function prepareExecution(options) { | 127 | 127 | initializeConfigFileSupport(); | 128 | 128 |
| 129 | 129 | require('internal/dns/utils').initializeDns(); | | 130 | + setupHttpProxy(); | 130 | 131 |
| 131 | 132 | setupSymbolDisposePolyfill(); | 132 | 133 |
| @@ -189,6 +190,21 @@ function setupSymbolDisposePolyfill() { | 189 | 190 | } | 190 | 191 | } | 191 | 192 |
| | 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 | + | 192 | 208 | function setupUserModules(forceDefaultLoader = false) { | 193 | 209 | initializeCJSLoader(); | 194 | 210 | initializeESMLoader(forceDefaultLoader); | |
Collapse file Copy file name to clipboard +100Lines changed: 100 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 Copy file name to clipboard +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +const response = await fetch(process.env.FETCH_URL); | | 2 | +const body = await response.text(); | | 3 | +console.log(body); |
Collapse file Copy file name to clipboard +61Lines changed: 61 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 Copy file name to clipboard +67Lines changed: 67 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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()); |
|