mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
http: add http.setGlobalProxyFromEnv()
This adds an API to dynamically enable built-in proxy support for all of fetch() and http.request()/https.request(), so that users do not have to be aware of them all and configure them one by one. PR-URL: https://github.com/nodejs/node/pull/60953 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
This commit is contained in:
1 parent
32ea48d749
commit
c0108bfc55
25 files changed
+807
-44
No files matched your search
+110
@@ -4361,6 +4361,32 @@ added:
|
||||
|
||||
Set the maximum number of idle HTTP parsers.
|
||||
|
||||
## `http.setGlobalProxyFromEnv([proxyEnv])`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- REPLACEME
|
||||
-->
|
||||
|
||||
* `proxyEnv` {Object} An object containing proxy configuration. This accepts the
|
||||
same options as the `proxyEnv` option accepted by [`Agent`][]. **Default:**
|
||||
`process.env`.
|
||||
* Returns: {Function} A function that restores the original agent and dispatcher
|
||||
settings to the state before this `http.setGlobalProxyFromEnv()` is invoked.
|
||||
|
||||
Dynamically resets the global configurations to enable built-in proxy support for
|
||||
`fetch()` and `http.request()`/`https.request()` at runtime, as an alternative
|
||||
to using the `--use-env-proxy` flag or `NODE_USE_ENV_PROXY` environment variable.
|
||||
It can also be used to override settings configured from the environment variables.
|
||||
|
||||
As this function resets the global configurations, any previously configured
|
||||
`http.globalAgent`, `https.globalAgent` or undici global dispatcher would be
|
||||
overridden after this function is invoked. It's recommended to invoke it before any
|
||||
requests are made and avoid invoking it in the middle of any requests.
|
||||
|
||||
See [Built-in Proxy Support][] for details on proxy URL formats and `NO_PROXY`
|
||||
syntax.
|
||||
|
||||
## Class: `WebSocket`
|
||||
|
||||
<!-- YAML
|
||||
@@ -4383,6 +4409,9 @@ added:
|
||||
When Node.js creates the global agent, if the `NODE_USE_ENV_PROXY` environment variable is
|
||||
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.
|
||||
|
||||
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,
|
||||
@@ -4438,6 +4467,86 @@ Or the `--use-env-proxy` flag.
|
||||
HTTP_PROXY=http://proxy.example.com:8080 NO_PROXY=localhost,127.0.0.1 node --use-env-proxy client.js
|
||||
```
|
||||
|
||||
To enable proxy support dynamically and globally with `process.env` (the default option of `http.setGlobalProxyFromEnv()`):
|
||||
|
||||
```cjs
|
||||
const http = require('node:http');
|
||||
|
||||
// Reads proxy-related environment variables from process.env
|
||||
const restore = http.setGlobalProxyFromEnv();
|
||||
|
||||
// Subsequent requests will use the configured proxies from environment variables
|
||||
http.get('http://www.example.com', (res) => {
|
||||
// This request will be proxied if HTTP_PROXY or http_proxy is set
|
||||
});
|
||||
|
||||
fetch('https://www.example.com', (res) => {
|
||||
// This request will be proxied if HTTPS_PROXY or https_proxy is set
|
||||
});
|
||||
|
||||
// To restore the original global agent and dispatcher settings, call the returned function.
|
||||
// restore();
|
||||
```
|
||||
|
||||
```mjs
|
||||
import http from 'node:http';
|
||||
|
||||
// Reads proxy-related environment variables from process.env
|
||||
http.setGlobalProxyFromEnv();
|
||||
|
||||
// Subsequent requests will use the configured proxies from environment variables
|
||||
http.get('http://www.example.com', (res) => {
|
||||
// This request will be proxied if HTTP_PROXY or http_proxy is set
|
||||
});
|
||||
|
||||
fetch('https://www.example.com', (res) => {
|
||||
// This request will be proxied if HTTPS_PROXY or https_proxy is set
|
||||
});
|
||||
|
||||
// To restore the original global agent and dispatcher settings, call the returned function.
|
||||
// restore();
|
||||
```
|
||||
|
||||
To enable proxy support dynamically and globally with custom settings:
|
||||
|
||||
```cjs
|
||||
const http = require('node:http');
|
||||
|
||||
const restore = http.setGlobalProxyFromEnv({
|
||||
http_proxy: 'http://proxy.example.com:8080',
|
||||
https_proxy: 'https://proxy.example.com:8443',
|
||||
no_proxy: 'localhost,127.0.0.1,.internal.example.com',
|
||||
});
|
||||
|
||||
// Subsequent requests will use the configured proxies
|
||||
http.get('http://www.example.com', (res) => {
|
||||
// This request will be proxied through proxy.example.com:8080
|
||||
});
|
||||
|
||||
fetch('https://www.example.com', (res) => {
|
||||
// This request will be proxied through proxy.example.com:8443
|
||||
});
|
||||
```
|
||||
|
||||
```mjs
|
||||
import http from 'node:http';
|
||||
|
||||
http.setGlobalProxyFromEnv({
|
||||
http_proxy: 'http://proxy.example.com:8080',
|
||||
https_proxy: 'https://proxy.example.com:8443',
|
||||
no_proxy: 'localhost,127.0.0.1,.internal.example.com',
|
||||
});
|
||||
|
||||
// Subsequent requests will use the configured proxies
|
||||
http.get('http://www.example.com', (res) => {
|
||||
// This request will be proxied through proxy.example.com:8080
|
||||
});
|
||||
|
||||
fetch('https://www.example.com', (res) => {
|
||||
// This request will be proxied through proxy.example.com:8443
|
||||
});
|
||||
```
|
||||
|
||||
To create a custom agent with built-in proxy support:
|
||||
|
||||
```cjs
|
||||
@@ -4501,6 +4610,7 @@ const agent2 = new http.Agent({ proxyEnv: process.env });
|
||||
[`http.get()`]: #httpgetoptions-callback
|
||||
[`http.globalAgent`]: #httpglobalagent
|
||||
[`http.request()`]: #httprequestoptions-callback
|
||||
[`http.setGlobalProxyFromEnv()`]: #httpsetglobalproxyfromenvproxyenv
|
||||
[`message.headers`]: #messageheaders
|
||||
[`message.rawHeaders`]: #messagerawheaders
|
||||
[`message.socket`]: #messagesocket
|
||||
|
||||
+2
-6
@@ -40,7 +40,7 @@ const {
|
||||
kProxyConfig,
|
||||
checkShouldUseProxy,
|
||||
kWaitForProxyTunnel,
|
||||
filterEnvForProxies,
|
||||
getGlobalAgent,
|
||||
} = require('internal/http');
|
||||
const { AsyncResource } = require('async_hooks');
|
||||
const { async_id_symbol } = require('internal/async_hooks').symbols;
|
||||
@@ -627,9 +627,5 @@ function asyncResetHandle(socket) {
|
||||
|
||||
module.exports = {
|
||||
Agent,
|
||||
globalAgent: new Agent({
|
||||
keepAlive: true, scheduling: 'lifo', timeout: 5000,
|
||||
// This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings.
|
||||
proxyEnv: getOptionValue('--use-env-proxy') ? filterEnvForProxies(process.env) : undefined,
|
||||
}),
|
||||
globalAgent: getGlobalAgent(getOptionValue('--use-env-proxy') ? process.env : undefined, Agent),
|
||||
};
|
||||
+60
-1
@@ -25,11 +25,12 @@ const {
|
||||
ObjectDefineProperty,
|
||||
} = primordials;
|
||||
|
||||
const { validateInteger } = require('internal/validators');
|
||||
const { validateInteger, validateObject } = require('internal/validators');
|
||||
const httpAgent = require('_http_agent');
|
||||
const { ClientRequest } = require('_http_client');
|
||||
const { methods, parsers } = require('_http_common');
|
||||
const { IncomingMessage } = require('_http_incoming');
|
||||
const { ERR_PROXY_INVALID_CONFIG } = require('internal/errors').codes;
|
||||
const {
|
||||
validateHeaderName,
|
||||
validateHeaderValue,
|
||||
@@ -41,6 +42,11 @@ const {
|
||||
Server,
|
||||
ServerResponse,
|
||||
} = require('_http_server');
|
||||
const {
|
||||
parseProxyUrl,
|
||||
getGlobalAgent,
|
||||
} = require('internal/http');
|
||||
const { URL } = require('internal/url');
|
||||
let maxHeaderSize;
|
||||
let undici;
|
||||
|
||||
@@ -123,6 +129,58 @@ function lazyUndici() {
|
||||
return undici ??= require('internal/deps/undici/undici');
|
||||
}
|
||||
|
||||
function setGlobalProxyFromEnv(env = process.env) {
|
||||
validateObject(env, 'proxyEnv');
|
||||
const httpProxy = parseProxyUrl(env, 'http:');
|
||||
const httpsProxy = parseProxyUrl(env, 'https:');
|
||||
const noProxy = env.no_proxy || env.NO_PROXY;
|
||||
|
||||
if (!httpProxy && !httpsProxy) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (httpProxy && !URL.canParse(httpProxy)) {
|
||||
throw new ERR_PROXY_INVALID_CONFIG(httpProxy);
|
||||
}
|
||||
if (httpsProxy && !URL.canParse(httpsProxy)) {
|
||||
throw new ERR_PROXY_INVALID_CONFIG(httpsProxy);
|
||||
}
|
||||
|
||||
let originalDispatcher, originalHttpsAgent, originalHttpAgent;
|
||||
if (httpProxy || httpsProxy) {
|
||||
// Set it for fetch.
|
||||
const { setGlobalDispatcher, getGlobalDispatcher, EnvHttpProxyAgent } = lazyUndici();
|
||||
const envHttpProxyAgent = new EnvHttpProxyAgent({
|
||||
__proto__: null, httpProxy, httpsProxy, noProxy,
|
||||
});
|
||||
originalDispatcher = getGlobalDispatcher();
|
||||
setGlobalDispatcher(envHttpProxyAgent);
|
||||
}
|
||||
|
||||
if (httpProxy) {
|
||||
originalHttpAgent = module.exports.globalAgent;
|
||||
module.exports.globalAgent = getGlobalAgent(env, httpAgent.Agent);
|
||||
}
|
||||
if (httpsProxy && !!process.versions.openssl) {
|
||||
const https = require('https');
|
||||
originalHttpsAgent = https.globalAgent;
|
||||
https.globalAgent = getGlobalAgent(env, https.Agent);
|
||||
}
|
||||
|
||||
return function restore() {
|
||||
if (originalDispatcher) {
|
||||
const { setGlobalDispatcher } = lazyUndici();
|
||||
setGlobalDispatcher(originalDispatcher);
|
||||
}
|
||||
if (originalHttpAgent) {
|
||||
module.exports.globalAgent = originalHttpAgent;
|
||||
}
|
||||
if (originalHttpsAgent) {
|
||||
require('https').globalAgent = originalHttpsAgent;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_connectionListener,
|
||||
METHODS: methods.toSorted(),
|
||||
@@ -142,6 +200,7 @@ module.exports = {
|
||||
validateInteger(max, 'max', 1);
|
||||
parsers.max = max;
|
||||
},
|
||||
setGlobalProxyFromEnv,
|
||||
};
|
||||
|
||||
ObjectDefineProperty(module.exports, 'maxHeaderSize', {
|
||||
|
||||
+2
-6
@@ -50,8 +50,8 @@ const tls = require('tls');
|
||||
const {
|
||||
kProxyConfig,
|
||||
checkShouldUseProxy,
|
||||
filterEnvForProxies,
|
||||
kWaitForProxyTunnel,
|
||||
getGlobalAgent,
|
||||
} = require('internal/http');
|
||||
const { Agent: HttpAgent } = require('_http_agent');
|
||||
const {
|
||||
@@ -602,11 +602,7 @@ Agent.prototype._evictSession = function _evictSession(key) {
|
||||
delete this._sessionCache.map[key];
|
||||
};
|
||||
|
||||
const globalAgent = new Agent({
|
||||
keepAlive: true, scheduling: 'lifo', timeout: 5000,
|
||||
// This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings.
|
||||
proxyEnv: getOptionValue('--use-env-proxy') ? filterEnvForProxies(process.env) : undefined,
|
||||
});
|
||||
const globalAgent = getGlobalAgent(getOptionValue('--use-env-proxy') ? process.env : undefined, Agent);
|
||||
|
||||
/**
|
||||
* Makes a request to a secure web server.
|
||||
|
||||
+24
-5
@@ -186,11 +186,7 @@ class ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
function parseProxyConfigFromEnv(env, protocol, keepAlive) {
|
||||
// We only support proxying for HTTP and HTTPS requests.
|
||||
if (protocol !== 'http:' && protocol !== 'https:') {
|
||||
return null;
|
||||
}
|
||||
function parseProxyUrl(env, protocol) {
|
||||
// Get the proxy url - following the most popular convention, lower case takes precedence.
|
||||
// See https://about.gitlab.com/blog/we-need-to-talk-no-proxy/#http_proxy-and-https_proxy
|
||||
const proxyUrl = (protocol === 'https:') ?
|
||||
@@ -204,6 +200,20 @@ function parseProxyConfigFromEnv(env, protocol, keepAlive) {
|
||||
throw new ERR_PROXY_INVALID_CONFIG(`Invalid proxy URL: ${proxyUrl}`);
|
||||
}
|
||||
|
||||
return proxyUrl;
|
||||
}
|
||||
|
||||
function parseProxyConfigFromEnv(env, protocol, keepAlive) {
|
||||
// We only support proxying for HTTP and HTTPS requests.
|
||||
if (protocol !== 'http:' && protocol !== 'https:') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const proxyUrl = parseProxyUrl(env, protocol);
|
||||
if (proxyUrl === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only http:// and https:// proxies are supported.
|
||||
// Ignore instead of throw, in case other protocols are supposed to be
|
||||
// handled by the user land.
|
||||
@@ -244,6 +254,13 @@ function filterEnvForProxies(env) {
|
||||
};
|
||||
}
|
||||
|
||||
function getGlobalAgent(proxyEnv, Agent) {
|
||||
return new Agent({
|
||||
keepAlive: true, scheduling: 'lifo', timeout: 5000,
|
||||
proxyEnv,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
kOutHeaders: Symbol('kOutHeaders'),
|
||||
kNeedDrain: Symbol('kNeedDrain'),
|
||||
@@ -257,4 +274,6 @@ module.exports = {
|
||||
getNextTraceEventId,
|
||||
isTraceHTTPEnabled,
|
||||
filterEnvForProxies,
|
||||
getGlobalAgent,
|
||||
parseProxyUrl,
|
||||
};
|
||||
@@ -20,9 +20,7 @@ await once(proxy, 'listening');
|
||||
const serverHost = `localhost:${server.address().port}`;
|
||||
|
||||
// FIXME(undici:4083): undici currently always tunnels the request over
|
||||
// CONNECT if proxyTunnel is not explicitly set to false, but what we
|
||||
// need is for it to be automatically false for HTTP requests to be
|
||||
// consistent with curl.
|
||||
// CONNECT if proxyTunnel is not explicitly set to false.
|
||||
const expectedLogs = [{
|
||||
method: 'CONNECT',
|
||||
url: serverHost,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() without arguments uses process.env.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js';
|
||||
|
||||
const { proxyLogs, proxyUrl, shutdown, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
// Test that calling setGlobalProxyFromEnv() without arguments uses process.env
|
||||
await checkProxiedFetch({
|
||||
FETCH_URL: requestUrl,
|
||||
// Set the proxy in the environment instead of passing it to SET_GLOBAL_PROXY
|
||||
http_proxy: proxyUrl,
|
||||
SET_GLOBAL_PROXY_DEFAULT: '1', // Signal to call without arguments
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
// FIXME(undici:4083): undici currently always tunnels the request over
|
||||
// CONNECT if proxyTunnel is not explicitly set to false.
|
||||
const expectedLogs = [{
|
||||
method: 'CONNECT',
|
||||
url: serverHost,
|
||||
headers: {
|
||||
'connection': 'close',
|
||||
'host': serverHost,
|
||||
'proxy-connection': 'keep-alive',
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,21 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() is a no-op when no proxy is configured.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js';
|
||||
|
||||
const { proxyLogs, shutdown, httpEndpoint: { requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedFetch({
|
||||
FETCH_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({}),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
// Verify request did NOT go through proxy.
|
||||
assert.deepStrictEqual(proxyLogs, []);
|
||||
@@ -0,0 +1,38 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with fetch().
|
||||
|
||||
import * as common from '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import * as fixtures from '../common/fixtures.mjs';
|
||||
import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js';
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('Needs crypto');
|
||||
}
|
||||
|
||||
const { proxyLogs, proxyUrl, shutdown, httpsEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpsEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedFetch({
|
||||
FETCH_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
https_proxy: proxyUrl,
|
||||
}),
|
||||
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
const expectedLogs = [{
|
||||
method: 'CONNECT',
|
||||
url: serverHost,
|
||||
headers: {
|
||||
'connection': 'close',
|
||||
'host': serverHost,
|
||||
'proxy-connection': 'keep-alive',
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,23 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with no_proxy configuration.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js';
|
||||
const { proxyLogs, shutdown, proxyUrl, httpEndpoint: { requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedFetch({
|
||||
FETCH_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
http_proxy: proxyUrl,
|
||||
no_proxy: 'localhost',
|
||||
}),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
// Verify request did NOT go through proxy.
|
||||
assert.deepStrictEqual(proxyLogs, []);
|
||||
@@ -0,0 +1,35 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with fetch() and https.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js';
|
||||
|
||||
const { proxyLogs, proxyUrl, shutdown, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedFetch({
|
||||
FETCH_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
http_proxy: proxyUrl,
|
||||
}),
|
||||
CLEAR_GLOBAL_PROXY_AND_RETRY: '1',
|
||||
}, {
|
||||
stdout: 'Hello world\nHello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
// FIXME(undici:4083): undici currently always tunnels the request over
|
||||
// CONNECT if proxyTunnel is not explicitly set to false.
|
||||
const expectedLogs = [{
|
||||
method: 'CONNECT',
|
||||
url: serverHost,
|
||||
headers: {
|
||||
'connection': 'close',
|
||||
'host': serverHost,
|
||||
'proxy-connection': 'keep-alive',
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,34 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with fetch() and https.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js';
|
||||
|
||||
const { proxyLogs, proxyUrl, shutdown, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedFetch({
|
||||
FETCH_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
http_proxy: proxyUrl,
|
||||
}),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
// FIXME(undici:4083): undici currently always tunnels the request over
|
||||
// CONNECT if proxyTunnel is not explicitly set to false.
|
||||
const expectedLogs = [{
|
||||
method: 'CONNECT',
|
||||
url: serverHost,
|
||||
headers: {
|
||||
'connection': 'close',
|
||||
'host': serverHost,
|
||||
'proxy-connection': 'keep-alive',
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,33 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() without arguments uses process.env.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedRequest } from '../common/proxy-server.js';
|
||||
|
||||
const { proxyLogs, proxyUrl, shutdown, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
// Test that calling setGlobalProxyFromEnv() without arguments uses process.env
|
||||
await checkProxiedRequest({
|
||||
REQUEST_URL: requestUrl,
|
||||
// Set the proxy in the environment instead of passing it to SET_GLOBAL_PROXY
|
||||
http_proxy: proxyUrl,
|
||||
SET_GLOBAL_PROXY_DEFAULT: '1', // Signal to call without arguments
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
const expectedLogs = [{
|
||||
method: 'GET',
|
||||
url: requestUrl,
|
||||
headers: {
|
||||
'connection': 'keep-alive',
|
||||
'proxy-connection': 'keep-alive',
|
||||
'host': serverHost,
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,20 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() is a no-op when no proxy is configured.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedRequest } from '../common/proxy-server.js';
|
||||
const { proxyLogs, shutdown, httpEndpoint: { requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedRequest({
|
||||
REQUEST_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({}),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
// Verify request did NOT go through proxy.
|
||||
assert.deepStrictEqual(proxyLogs, []);
|
||||
@@ -0,0 +1,33 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with fetch() and https.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedRequest } from '../common/proxy-server.js';
|
||||
|
||||
const { proxyLogs, proxyUrl, shutdown, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedRequest({
|
||||
REQUEST_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
http_proxy: proxyUrl,
|
||||
}),
|
||||
CLEAR_GLOBAL_PROXY_AND_RETRY: '1',
|
||||
}, {
|
||||
stdout: 'Hello worldHello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
const expectedLogs = [{
|
||||
method: 'GET',
|
||||
url: requestUrl,
|
||||
headers: {
|
||||
'connection': 'keep-alive',
|
||||
'proxy-connection': 'keep-alive',
|
||||
'host': serverHost,
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,29 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with http.request().
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedRequest } from '../common/proxy-server.js';
|
||||
const { proxyLogs, shutdown, proxyUrl, httpEndpoint: { requestUrl, serverHost } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedRequest({
|
||||
REQUEST_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({ http_proxy: proxyUrl }),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
const expectedLogs = [{
|
||||
method: 'GET',
|
||||
url: requestUrl,
|
||||
headers: {
|
||||
'connection': 'keep-alive',
|
||||
'proxy-connection': 'keep-alive',
|
||||
'host': serverHost,
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,37 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with https.request().
|
||||
|
||||
import * as common from '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import * as fixtures from '../common/fixtures.mjs';
|
||||
import { startTestServers, checkProxiedRequest } from '../common/proxy-server.js';
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('Needs crypto');
|
||||
}
|
||||
|
||||
const { proxyLogs, httpsEndpoint: { requestUrl, serverHost }, proxyUrl, shutdown } = await startTestServers({
|
||||
httpsEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedRequest({
|
||||
REQUEST_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
https_proxy: proxyUrl,
|
||||
}),
|
||||
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
const expectedLogs = [{
|
||||
method: 'CONNECT',
|
||||
url: serverHost,
|
||||
headers: {
|
||||
'proxy-connection': 'keep-alive',
|
||||
'host': serverHost,
|
||||
},
|
||||
}];
|
||||
|
||||
assert.deepStrictEqual(proxyLogs, expectedLogs);
|
||||
@@ -0,0 +1,15 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() validates that proxyEnv is an object.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import http from 'node:http';
|
||||
|
||||
for (const invalidInput of [42, 'string', null, [], true]) {
|
||||
assert.throws(
|
||||
() => http.setGlobalProxyFromEnv(invalidInput),
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() throws on invalid HTTP proxy URL.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import http from 'node:http';
|
||||
|
||||
assert.throws(() => {
|
||||
http.setGlobalProxyFromEnv({
|
||||
http_proxy: 'not a url',
|
||||
});
|
||||
}, {
|
||||
code: 'ERR_PROXY_INVALID_CONFIG',
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
http.setGlobalProxyFromEnv({
|
||||
https_proxy: 'not a url',
|
||||
});
|
||||
}, {
|
||||
code: 'ERR_PROXY_INVALID_CONFIG',
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() works with no_proxy configuration.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { startTestServers, checkProxiedRequest } from '../common/proxy-server.js';
|
||||
const { proxyLogs, shutdown, proxyUrl, httpEndpoint: { requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
|
||||
await checkProxiedRequest({
|
||||
REQUEST_URL: requestUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
http_proxy: proxyUrl,
|
||||
no_proxy: 'localhost',
|
||||
}),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
|
||||
// Verify request did NOT go through proxy.
|
||||
assert.deepStrictEqual(proxyLogs, []);
|
||||
@@ -0,0 +1,44 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() overrides configuration from NODE_USE_ENV_PROXY.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { once } from 'node:events';
|
||||
import { startTestServers, checkProxiedFetch, createProxyServer } from '../common/proxy-server.js';
|
||||
const { proxyLogs, shutdown, proxyUrl, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
const { proxy: proxy2, logs: proxyLogs2 } = createProxyServer();
|
||||
proxy2.listen(0);
|
||||
await once(proxy2, 'listening');
|
||||
|
||||
await checkProxiedFetch({
|
||||
FETCH_URL: requestUrl,
|
||||
NODE_USE_ENV_PROXY: '1',
|
||||
http_proxy: proxyUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
http_proxy: `http://localhost:${proxy2.address().port}`,
|
||||
}),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
proxy2.close();
|
||||
|
||||
// Verify request did NOT go through original proxy, but the overriden one.
|
||||
assert.deepStrictEqual(proxyLogs, []);
|
||||
|
||||
// FIXME(undici:4083): undici currently always tunnels the request over
|
||||
// CONNECT if proxyTunnel is not explicitly set to false, but what we
|
||||
// need is for it to be automatically false for HTTP requests to be
|
||||
// consistent with curl.
|
||||
const expectedLogs = [{
|
||||
method: 'CONNECT',
|
||||
url: serverHost,
|
||||
headers: {
|
||||
'connection': 'close',
|
||||
'host': serverHost,
|
||||
'proxy-connection': 'keep-alive',
|
||||
},
|
||||
}];
|
||||
assert.deepStrictEqual(proxyLogs2, expectedLogs);
|
||||
@@ -0,0 +1,40 @@
|
||||
// Tests that http.setGlobalProxyFromEnv() overrides configuration from NODE_USE_ENV_PROXY.
|
||||
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { once } from 'node:events';
|
||||
import { startTestServers, checkProxiedRequest, createProxyServer } from '../common/proxy-server.js';
|
||||
const { proxyLogs, shutdown, proxyUrl, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({
|
||||
httpEndpoint: true,
|
||||
});
|
||||
const { proxy: proxy2, logs: proxyLogs2 } = createProxyServer();
|
||||
proxy2.listen(0);
|
||||
await once(proxy2, 'listening');
|
||||
|
||||
await checkProxiedRequest({
|
||||
REQUEST_URL: requestUrl,
|
||||
NODE_USE_ENV_PROXY: '1',
|
||||
http_proxy: proxyUrl,
|
||||
SET_GLOBAL_PROXY: JSON.stringify({
|
||||
http_proxy: `http://localhost:${proxy2.address().port}`,
|
||||
}),
|
||||
}, {
|
||||
stdout: 'Hello world',
|
||||
});
|
||||
|
||||
shutdown();
|
||||
proxy2.close();
|
||||
|
||||
// Verify request did NOT go through original proxy, but the overriden one.
|
||||
assert.deepStrictEqual(proxyLogs, []);
|
||||
|
||||
const expectedLogs = [{
|
||||
method: 'GET',
|
||||
url: requestUrl,
|
||||
headers: {
|
||||
'connection': 'keep-alive',
|
||||
'host': serverHost,
|
||||
'proxy-connection': 'keep-alive',
|
||||
},
|
||||
}];
|
||||
assert.deepStrictEqual(proxyLogs2, expectedLogs);
|
||||
@@ -3,6 +3,8 @@
|
||||
const net = require('net');
|
||||
const http = require('http');
|
||||
const assert = require('assert');
|
||||
const { once } = require('events');
|
||||
const fixtures = require('./fixtures');
|
||||
|
||||
function logRequest(logs, req) {
|
||||
logs.push({
|
||||
@@ -14,7 +16,7 @@ function logRequest(logs, req) {
|
||||
|
||||
// This creates a minimal proxy server that logs the requests it gets
|
||||
// to an array before performing proxying.
|
||||
exports.createProxyServer = function(options = {}) {
|
||||
function createProxyServer(options = {}) {
|
||||
const logs = [];
|
||||
|
||||
let proxy;
|
||||
@@ -107,7 +109,8 @@ exports.createProxyServer = function(options = {}) {
|
||||
});
|
||||
|
||||
return { proxy, logs };
|
||||
};
|
||||
}
|
||||
exports.createProxyServer = createProxyServer;
|
||||
|
||||
function spawnPromisified(...args) {
|
||||
const { spawn } = require('child_process');
|
||||
@@ -148,12 +151,13 @@ function spawnPromisified(...args) {
|
||||
});
|
||||
}
|
||||
|
||||
exports.checkProxiedFetch = async function(envExtension, expectation, cliArgsExtension = []) {
|
||||
const fixtures = require('./fixtures');
|
||||
async function checkProxied(type, envExtension, expectation, cliArgsExtension = []) {
|
||||
const script = type === 'fetch' ? fixtures.path('fetch-and-log.mjs') : fixtures.path('request-and-log.js');
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(
|
||||
process.execPath,
|
||||
[...cliArgsExtension, fixtures.path('fetch-and-log.mjs')], {
|
||||
[...cliArgsExtension, script], {
|
||||
env: {
|
||||
NO_LOG_REQUEST: '1',
|
||||
...process.env,
|
||||
...envExtension,
|
||||
},
|
||||
@@ -172,6 +176,14 @@ exports.checkProxiedFetch = async function(envExtension, expectation, cliArgsExt
|
||||
});
|
||||
};
|
||||
|
||||
exports.checkProxiedFetch = async function(...args) {
|
||||
return checkProxied('fetch', ...args);
|
||||
};
|
||||
|
||||
exports.checkProxiedRequest = async function(...args) {
|
||||
return checkProxied('request', ...args);
|
||||
};
|
||||
|
||||
exports.runProxiedRequest = async function(envExtension, cliArgsExtension = []) {
|
||||
const fixtures = require('./fixtures');
|
||||
return spawnPromisified(
|
||||
@@ -195,3 +207,55 @@ exports.runProxiedPOST = async function(envExtension) {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
exports.startTestServers = async function(options = {}) {
|
||||
const { proxy, logs } = createProxyServer();
|
||||
proxy.listen(0);
|
||||
await once(proxy, 'listening');
|
||||
|
||||
let httpServer, httpsServer, httpEndpoint, httpsEndpoint;
|
||||
if (options.httpsEndpoint) {
|
||||
httpsServer = require('https').createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, (req, res) => {
|
||||
res.end('Hello world');
|
||||
});
|
||||
httpsServer.listen(0);
|
||||
await once(httpsServer, 'listening');
|
||||
const { port } = httpsServer.address();
|
||||
httpsEndpoint = {
|
||||
serverHost: `localhost:${port}`,
|
||||
requestUrl: `https://localhost:${port}/test`,
|
||||
};
|
||||
}
|
||||
|
||||
if (options.httpEndpoint) {
|
||||
httpServer = http.createServer((req, res) => {
|
||||
res.end('Hello world');
|
||||
});
|
||||
httpServer.listen(0);
|
||||
await once(httpServer, 'listening');
|
||||
const { port } = httpServer.address();
|
||||
httpEndpoint = {
|
||||
serverHost: `localhost:${port}`,
|
||||
requestUrl: `http://localhost:${port}/test`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
proxyLogs: logs,
|
||||
shutdown() {
|
||||
if (httpServer) {
|
||||
httpServer.close();
|
||||
}
|
||||
if (httpsServer) {
|
||||
httpsServer.close();
|
||||
}
|
||||
proxy.close();
|
||||
},
|
||||
proxyUrl: `http://localhost:${proxy.address().port}`,
|
||||
httpEndpoint,
|
||||
httpsEndpoint,
|
||||
};
|
||||
};
|
||||
Vendored
+17
@@ -1,3 +1,20 @@
|
||||
import http from 'node:http';
|
||||
|
||||
let restore;
|
||||
if (process.env.SET_GLOBAL_PROXY) {
|
||||
const config = JSON.parse(process.env.SET_GLOBAL_PROXY);
|
||||
restore = http.setGlobalProxyFromEnv(config);
|
||||
} else if (process.env.SET_GLOBAL_PROXY_DEFAULT) {
|
||||
restore = http.setGlobalProxyFromEnv();
|
||||
}
|
||||
|
||||
const response = await fetch(process.env.FETCH_URL);
|
||||
const body = await response.text();
|
||||
console.log(body);
|
||||
|
||||
if (process.env.CLEAR_GLOBAL_PROXY_AND_RETRY) {
|
||||
restore();
|
||||
const response = await fetch(process.env.FETCH_URL);
|
||||
const body = await response.text();
|
||||
console.log(body);
|
||||
}
|
||||
Vendored
+41
-18
@@ -1,3 +1,11 @@
|
||||
let restore;
|
||||
if (process.env.SET_GLOBAL_PROXY) {
|
||||
const config = JSON.parse(process.env.SET_GLOBAL_PROXY);
|
||||
restore = require('http').setGlobalProxyFromEnv(config);
|
||||
} else if (process.env.SET_GLOBAL_PROXY_DEFAULT) {
|
||||
restore = require('http').setGlobalProxyFromEnv();
|
||||
}
|
||||
|
||||
const url = process.env.REQUEST_URL;
|
||||
|
||||
let lib;
|
||||
@@ -32,24 +40,39 @@ if (process.env.RESOLVE_TO_LOCALHOST) {
|
||||
};
|
||||
}
|
||||
|
||||
const req = request(url, {
|
||||
timeout,
|
||||
agent,
|
||||
lookup,
|
||||
}, (res) => {
|
||||
// Log the status code
|
||||
console.log(`Status Code: ${res.statusCode}`);
|
||||
console.log('Headers:', res.headers);
|
||||
res.pipe(process.stdout);
|
||||
});
|
||||
function startRequest(callback) {
|
||||
const req = request(url, {
|
||||
timeout,
|
||||
agent,
|
||||
lookup,
|
||||
}, (res) => {
|
||||
// Log the status code
|
||||
if (!process.env.NO_LOG_REQUEST) {
|
||||
console.log(`Status Code: ${res.statusCode}`);
|
||||
console.log('Headers:', res.headers);
|
||||
}
|
||||
res.pipe(process.stdout);
|
||||
|
||||
req.on('error', (e) => {
|
||||
console.error('Request Error', e);
|
||||
});
|
||||
if (callback) {
|
||||
res.on('end', callback);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
console.error('Request timed out');
|
||||
req.destroy();
|
||||
});
|
||||
req.on('error', (e) => {
|
||||
console.error('Request Error', e);
|
||||
});
|
||||
|
||||
req.end();
|
||||
req.on('timeout', () => {
|
||||
console.error('Request timed out');
|
||||
req.destroy();
|
||||
});
|
||||
|
||||
req.end();
|
||||
}
|
||||
|
||||
startRequest(() => {
|
||||
if (process.env.CLEAR_GLOBAL_PROXY_AND_RETRY) {
|
||||
restore();
|
||||
startRequest();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user