| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 42666c2 commit 8470e29
25 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4341,6 +4341,32 @@ added: | |||
| 4341 | 4341 | ||
| 4342 | 4342 | Set the maximum number of idle HTTP parsers. | |
| 4343 | 4343 | ||
| 4344 | + ## `http.setGlobalProxyFromEnv([proxyEnv])` | ||
| 4345 | + | ||
| 4346 | + <!-- YAML | ||
| 4347 | + added: | ||
| 4348 | + - REPLACEME | ||
| 4349 | + --> | ||
| 4350 | + | ||
| 4351 | + * `proxyEnv` {Object} An object containing proxy configuration. This accepts the | ||
| 4352 | + same options as the `proxyEnv` option accepted by [`Agent`][]. **Default:** | ||
| 4353 | + `process.env`. | ||
| 4354 | + * Returns: {Function} A function that restores the original agent and dispatcher | ||
| 4355 | + settings to the state before this `http.setGlobalProxyFromEnv()` is invoked. | ||
| 4356 | + | ||
| 4357 | + Dynamically resets the global configurations to enable built-in proxy support for | ||
| 4358 | + `fetch()` and `http.request()`/`https.request()` at runtime, as an alternative | ||
| 4359 | + to using the `--use-env-proxy` flag or `NODE_USE_ENV_PROXY` environment variable. | ||
| 4360 | + It can also be used to override settings configured from the environment variables. | ||
| 4361 | + | ||
| 4362 | + As this function resets the global configurations, any previously configured | ||
| 4363 | + `http.globalAgent`, `https.globalAgent` or undici global dispatcher would be | ||
| 4364 | + overridden after this function is invoked. It's recommended to invoke it before any | ||
| 4365 | + requests are made and avoid invoking it in the middle of any requests. | ||
| 4366 | + | ||
| 4367 | + See [Built-in Proxy Support][] for details on proxy URL formats and `NO_PROXY` | ||
| 4368 | + syntax. | ||
| 4369 | + | ||
| 4344 | 4370 | ## Class: `WebSocket` | |
| 4345 | 4371 | ||
| 4346 | 4372 | <!-- YAML | |
@@ -4361,6 +4387,9 @@ added: v24.5.0 | |||
| 4361 | 4387 | When Node.js creates the global agent, if the `NODE_USE_ENV_PROXY` environment variable is | |
| 4362 | 4388 | set to `1` or `--use-env-proxy` is enabled, the global agent will be constructed | |
| 4363 | 4389 | with `proxyEnv: process.env`, enabling proxy support based on the environment variables. | |
| 4390 | + | ||
| 4391 | + To enable proxy support dynamically and globally, use [`http.setGlobalProxyFromEnv()`][]. | ||
| 4392 | + | ||
| 4364 | 4393 | Custom agents can also be created with proxy support by passing a | |
| 4365 | 4394 | `proxyEnv` option when constructing the agent. The value can be `process.env` | |
| 4366 | 4395 | if they just want to inherit the configuration from the environment variables, | |
@@ -4416,6 +4445,86 @@ Or the `--use-env-proxy` flag. | |||
| 4416 | 4445 | HTTP_PROXY=http://proxy.example.com:8080 NO_PROXY=localhost,127.0.0.1 node --use-env-proxy client.js | |
| 4417 | 4446 | ``` | |
| 4418 | 4447 | ||
| 4448 | + To enable proxy support dynamically and globally with `process.env` (the default option of `http.setGlobalProxyFromEnv()`): | ||
| 4449 | + | ||
| 4450 | + ```cjs | ||
| 4451 | + const http = require('node:http'); | ||
| 4452 | + | ||
| 4453 | + // Reads proxy-related environment variables from process.env | ||
| 4454 | + const restore = http.setGlobalProxyFromEnv(); | ||
| 4455 | + | ||
| 4456 | + // Subsequent requests will use the configured proxies from environment variables | ||
| 4457 | + http.get('http://www.example.com', (res) => { | ||
| 4458 | + // This request will be proxied if HTTP_PROXY or http_proxy is set | ||
| 4459 | + }); | ||
| 4460 | + | ||
| 4461 | + fetch('https://www.example.com', (res) => { | ||
| 4462 | + // This request will be proxied if HTTPS_PROXY or https_proxy is set | ||
| 4463 | + }); | ||
| 4464 | + | ||
| 4465 | + // To restore the original global agent and dispatcher settings, call the returned function. | ||
| 4466 | + // restore(); | ||
| 4467 | + ``` | ||
| 4468 | + | ||
| 4469 | + ```mjs | ||
| 4470 | + import http from 'node:http'; | ||
| 4471 | + | ||
| 4472 | + // Reads proxy-related environment variables from process.env | ||
| 4473 | + http.setGlobalProxyFromEnv(); | ||
| 4474 | + | ||
| 4475 | + // Subsequent requests will use the configured proxies from environment variables | ||
| 4476 | + http.get('http://www.example.com', (res) => { | ||
| 4477 | + // This request will be proxied if HTTP_PROXY or http_proxy is set | ||
| 4478 | + }); | ||
| 4479 | + | ||
| 4480 | + fetch('https://www.example.com', (res) => { | ||
| 4481 | + // This request will be proxied if HTTPS_PROXY or https_proxy is set | ||
| 4482 | + }); | ||
| 4483 | + | ||
| 4484 | + // To restore the original global agent and dispatcher settings, call the returned function. | ||
| 4485 | + // restore(); | ||
| 4486 | + ``` | ||
| 4487 | + | ||
| 4488 | + To enable proxy support dynamically and globally with custom settings: | ||
| 4489 | + | ||
| 4490 | + ```cjs | ||
| 4491 | + const http = require('node:http'); | ||
| 4492 | + | ||
| 4493 | + const restore = http.setGlobalProxyFromEnv({ | ||
| 4494 | + http_proxy: 'http://proxy.example.com:8080', | ||
| 4495 | + https_proxy: 'https://proxy.example.com:8443', | ||
| 4496 | + no_proxy: 'localhost,127.0.0.1,.internal.example.com', | ||
| 4497 | + }); | ||
| 4498 | + | ||
| 4499 | + // Subsequent requests will use the configured proxies | ||
| 4500 | + http.get('http://www.example.com', (res) => { | ||
| 4501 | + // This request will be proxied through proxy.example.com:8080 | ||
| 4502 | + }); | ||
| 4503 | + | ||
| 4504 | + fetch('https://www.example.com', (res) => { | ||
| 4505 | + // This request will be proxied through proxy.example.com:8443 | ||
| 4506 | + }); | ||
| 4507 | + ``` | ||
| 4508 | + | ||
| 4509 | + ```mjs | ||
| 4510 | + import http from 'node:http'; | ||
| 4511 | + | ||
| 4512 | + http.setGlobalProxyFromEnv({ | ||
| 4513 | + http_proxy: 'http://proxy.example.com:8080', | ||
| 4514 | + https_proxy: 'https://proxy.example.com:8443', | ||
| 4515 | + no_proxy: 'localhost,127.0.0.1,.internal.example.com', | ||
| 4516 | + }); | ||
| 4517 | + | ||
| 4518 | + // Subsequent requests will use the configured proxies | ||
| 4519 | + http.get('http://www.example.com', (res) => { | ||
| 4520 | + // This request will be proxied through proxy.example.com:8080 | ||
| 4521 | + }); | ||
| 4522 | + | ||
| 4523 | + fetch('https://www.example.com', (res) => { | ||
| 4524 | + // This request will be proxied through proxy.example.com:8443 | ||
| 4525 | + }); | ||
| 4526 | + ``` | ||
| 4527 | + | ||
| 4419 | 4528 | To create a custom agent with built-in proxy support: | |
| 4420 | 4529 | ||
| 4421 | 4530 | ```cjs | |
@@ -4479,6 +4588,7 @@ const agent2 = new http.Agent({ proxyEnv: process.env }); | |||
| 4479 | 4588 | [`http.get()`]: #httpgetoptions-callback | |
| 4480 | 4589 | [`http.globalAgent`]: #httpglobalagent | |
| 4481 | 4590 | [`http.request()`]: #httprequestoptions-callback | |
| 4591 | + [`http.setGlobalProxyFromEnv()`]: #httpsetglobalproxyfromenvproxyenv | ||
| 4482 | 4592 | [`message.headers`]: #messageheaders | |
| 4483 | 4593 | [`message.rawHeaders`]: #messagerawheaders | |
| 4484 | 4594 | [`message.socket`]: #messagesocket | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -40,7 +40,7 @@ const { | |||
| 40 | 40 | kProxyConfig, | |
| 41 | 41 | checkShouldUseProxy, | |
| 42 | 42 | kWaitForProxyTunnel, | |
| 43 | - filterEnvForProxies, | ||
| 43 | + getGlobalAgent, | ||
| 44 | 44 | } = require('internal/http'); | |
| 45 | 45 | const { AsyncResource } = require('async_hooks'); | |
| 46 | 46 | const { async_id_symbol } = require('internal/async_hooks').symbols; | |
@@ -627,9 +627,5 @@ function asyncResetHandle(socket) { | |||
| 627 | 627 | ||
| 628 | 628 | module.exports = { | |
| 629 | 629 | Agent, | |
| 630 | - globalAgent: new Agent({ | ||
| 631 | - keepAlive: true, scheduling: 'lifo', timeout: 5000, | ||
| 632 | - // This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings. | ||
| 633 | - proxyEnv: getOptionValue('--use-env-proxy') ? filterEnvForProxies(process.env) : undefined, | ||
| 634 | - }), | ||
| 630 | + globalAgent: getGlobalAgent(getOptionValue('--use-env-proxy') ? process.env : undefined, Agent), | ||
| 635 | 631 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,11 +25,12 @@ const { | |||
| 25 | 25 | ObjectDefineProperty, | |
| 26 | 26 | } = primordials; | |
| 27 | 27 | ||
| 28 | - const { validateInteger } = require('internal/validators'); | ||
| 28 | + const { validateInteger, validateObject } = require('internal/validators'); | ||
| 29 | 29 | const httpAgent = require('_http_agent'); | |
| 30 | 30 | const { ClientRequest } = require('_http_client'); | |
| 31 | 31 | const { methods, parsers } = require('_http_common'); | |
| 32 | 32 | const { IncomingMessage } = require('_http_incoming'); | |
| 33 | + const { ERR_PROXY_INVALID_CONFIG } = require('internal/errors').codes; | ||
| 33 | 34 | const { | |
| 34 | 35 | validateHeaderName, | |
| 35 | 36 | validateHeaderValue, | |
@@ -41,6 +42,11 @@ const { | |||
| 41 | 42 | Server, | |
| 42 | 43 | ServerResponse, | |
| 43 | 44 | } = require('_http_server'); | |
| 45 | + const { | ||
| 46 | + parseProxyUrl, | ||
| 47 | + getGlobalAgent, | ||
| 48 | + } = require('internal/http'); | ||
| 49 | + const { URL } = require('internal/url'); | ||
| 44 | 50 | let maxHeaderSize; | |
| 45 | 51 | let undici; | |
| 46 | 52 | ||
@@ -123,6 +129,58 @@ function lazyUndici() { | |||
| 123 | 129 | return undici ??= require('internal/deps/undici/undici'); | |
| 124 | 130 | } | |
| 125 | 131 | ||
| 132 | + function setGlobalProxyFromEnv(env = process.env) { | ||
| 133 | + validateObject(env, 'proxyEnv'); | ||
| 134 | + const httpProxy = parseProxyUrl(env, 'http:'); | ||
| 135 | + const httpsProxy = parseProxyUrl(env, 'https:'); | ||
| 136 | + const noProxy = env.no_proxy || env.NO_PROXY; | ||
| 137 | + | ||
| 138 | + if (!httpProxy && !httpsProxy) { | ||
| 139 | + return () => {}; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + if (httpProxy && !URL.canParse(httpProxy)) { | ||
| 143 | + throw new ERR_PROXY_INVALID_CONFIG(httpProxy); | ||
| 144 | + } | ||
| 145 | + if (httpsProxy && !URL.canParse(httpsProxy)) { | ||
| 146 | + throw new ERR_PROXY_INVALID_CONFIG(httpsProxy); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + let originalDispatcher, originalHttpsAgent, originalHttpAgent; | ||
| 150 | + if (httpProxy || httpsProxy) { | ||
| 151 | + // Set it for fetch. | ||
| 152 | + const { setGlobalDispatcher, getGlobalDispatcher, EnvHttpProxyAgent } = lazyUndici(); | ||
| 153 | + const envHttpProxyAgent = new EnvHttpProxyAgent({ | ||
| 154 | + __proto__: null, httpProxy, httpsProxy, noProxy, | ||
| 155 | + }); | ||
| 156 | + originalDispatcher = getGlobalDispatcher(); | ||
| 157 | + setGlobalDispatcher(envHttpProxyAgent); | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + if (httpProxy) { | ||
| 161 | + originalHttpAgent = module.exports.globalAgent; | ||
| 162 | + module.exports.globalAgent = getGlobalAgent(env, httpAgent.Agent); | ||
| 163 | + } | ||
| 164 | + if (httpsProxy && !!process.versions.openssl) { | ||
| 165 | + const https = require('https'); | ||
| 166 | + originalHttpsAgent = https.globalAgent; | ||
| 167 | + https.globalAgent = getGlobalAgent(env, https.Agent); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + return function restore() { | ||
| 171 | + if (originalDispatcher) { | ||
| 172 | + const { setGlobalDispatcher } = lazyUndici(); | ||
| 173 | + setGlobalDispatcher(originalDispatcher); | ||
| 174 | + } | ||
| 175 | + if (originalHttpAgent) { | ||
| 176 | + module.exports.globalAgent = originalHttpAgent; | ||
| 177 | + } | ||
| 178 | + if (originalHttpsAgent) { | ||
| 179 | + require('https').globalAgent = originalHttpsAgent; | ||
| 180 | + } | ||
| 181 | + }; | ||
| 182 | + } | ||
| 183 | + | ||
| 126 | 184 | module.exports = { | |
| 127 | 185 | _connectionListener, | |
| 128 | 186 | METHODS: methods.toSorted(), | |
@@ -142,6 +200,7 @@ module.exports = { | |||
| 142 | 200 | validateInteger(max, 'max', 1); | |
| 143 | 201 | parsers.max = max; | |
| 144 | 202 | }, | |
| 203 | + setGlobalProxyFromEnv, | ||
| 145 | 204 | }; | |
| 146 | 205 | ||
| 147 | 206 | ObjectDefineProperty(module.exports, 'maxHeaderSize', { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -50,8 +50,8 @@ const tls = require('tls'); | |||
| 50 | 50 | const { | |
| 51 | 51 | kProxyConfig, | |
| 52 | 52 | checkShouldUseProxy, | |
| 53 | - filterEnvForProxies, | ||
| 54 | 53 | kWaitForProxyTunnel, | |
| 54 | + getGlobalAgent, | ||
| 55 | 55 | } = require('internal/http'); | |
| 56 | 56 | const { Agent: HttpAgent } = require('_http_agent'); | |
| 57 | 57 | const { | |
@@ -602,11 +602,7 @@ Agent.prototype._evictSession = function _evictSession(key) { | |||
| 602 | 602 | delete this._sessionCache.map[key]; | |
| 603 | 603 | }; | |
| 604 | 604 | ||
| 605 | - const globalAgent = new Agent({ | ||
| 606 | - keepAlive: true, scheduling: 'lifo', timeout: 5000, | ||
| 607 | - // This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings. | ||
| 608 | - proxyEnv: getOptionValue('--use-env-proxy') ? filterEnvForProxies(process.env) : undefined, | ||
| 609 | - }); | ||
| 605 | + const globalAgent = getGlobalAgent(getOptionValue('--use-env-proxy') ? process.env : undefined, Agent); | ||
| 610 | 606 | ||
| 611 | 607 | /** | |
| 612 | 608 | * Makes a request to a secure web server. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -186,11 +186,7 @@ class ProxyConfig { | |||
| 186 | 186 | } | |
| 187 | 187 | } | |
| 188 | 188 | ||
| 189 | - function parseProxyConfigFromEnv(env, protocol, keepAlive) { | ||
| 190 | - // We only support proxying for HTTP and HTTPS requests. | ||
| 191 | - if (protocol !== 'http:' && protocol !== 'https:') { | ||
| 192 | - return null; | ||
| 193 | - } | ||
| 189 | + function parseProxyUrl(env, protocol) { | ||
| 194 | 190 | // Get the proxy url - following the most popular convention, lower case takes precedence. | |
| 195 | 191 | // See https://about.gitlab.com/blog/we-need-to-talk-no-proxy/#http_proxy-and-https_proxy | |
| 196 | 192 | const proxyUrl = (protocol === 'https:') ? | |
@@ -204,6 +200,20 @@ function parseProxyConfigFromEnv(env, protocol, keepAlive) { | |||
| 204 | 200 | throw new ERR_PROXY_INVALID_CONFIG(`Invalid proxy URL: ${proxyUrl}`); | |
| 205 | 201 | } | |
| 206 | 202 | ||
| 203 | + return proxyUrl; | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + function parseProxyConfigFromEnv(env, protocol, keepAlive) { | ||
| 207 | + // We only support proxying for HTTP and HTTPS requests. | ||
| 208 | + if (protocol !== 'http:' && protocol !== 'https:') { | ||
| 209 | + return null; | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + const proxyUrl = parseProxyUrl(env, protocol); | ||
| 213 | + if (proxyUrl === null) { | ||
| 214 | + return null; | ||
| 215 | + } | ||
| 216 | + | ||
| 207 | 217 | // Only http:// and https:// proxies are supported. | |
| 208 | 218 | // Ignore instead of throw, in case other protocols are supposed to be | |
| 209 | 219 | // handled by the user land. | |
@@ -244,6 +254,13 @@ function filterEnvForProxies(env) { | |||
| 244 | 254 | }; | |
| 245 | 255 | } | |
| 246 | 256 | ||
| 257 | + function getGlobalAgent(proxyEnv, Agent) { | ||
| 258 | + return new Agent({ | ||
| 259 | + keepAlive: true, scheduling: 'lifo', timeout: 5000, | ||
| 260 | + proxyEnv, | ||
| 261 | + }); | ||
| 262 | + } | ||
| 263 | + | ||
| 247 | 264 | module.exports = { | |
| 248 | 265 | kOutHeaders: Symbol('kOutHeaders'), | |
| 249 | 266 | kNeedDrain: Symbol('kNeedDrain'), | |
@@ -257,4 +274,6 @@ module.exports = { | |||
| 257 | 274 | getNextTraceEventId, | |
| 258 | 275 | isTraceHTTPEnabled, | |
| 259 | 276 | filterEnvForProxies, | |
| 277 | + getGlobalAgent, | ||
| 278 | + parseProxyUrl, | ||
| 260 | 279 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,9 +20,7 @@ await once(proxy, 'listening'); | |||
| 20 | 20 | const serverHost = `localhost:${server.address().port}`; | |
| 21 | 21 | ||
| 22 | 22 | // FIXME(undici:4083): undici currently always tunnels the request over | |
| 23 | - // CONNECT if proxyTunnel is not explicitly set to false, but what we | ||
| 24 | - // need is for it to be automatically false for HTTP requests to be | ||
| 25 | - // consistent with curl. | ||
| 23 | + // CONNECT if proxyTunnel is not explicitly set to false. | ||
| 26 | 24 | const expectedLogs = [{ | |
| 27 | 25 | method: 'CONNECT', | |
| 28 | 26 | url: serverHost, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,35 @@ | |||
| 1 | + // Tests that http.setGlobalProxyFromEnv() without arguments uses process.env. | ||
| 2 | + | ||
| 3 | + import '../common/index.mjs'; | ||
| 4 | + import assert from 'node:assert'; | ||
| 5 | + import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js'; | ||
| 6 | + | ||
| 7 | + const { proxyLogs, proxyUrl, shutdown, httpEndpoint: { serverHost, requestUrl } } = await startTestServers({ | ||
| 8 | + httpEndpoint: true, | ||
| 9 | + }); | ||
| 10 | + | ||
| 11 | + // Test that calling setGlobalProxyFromEnv() without arguments uses process.env | ||
| 12 | + await checkProxiedFetch({ | ||
| 13 | + FETCH_URL: requestUrl, | ||
| 14 | + // Set the proxy in the environment instead of passing it to SET_GLOBAL_PROXY | ||
| 15 | + http_proxy: proxyUrl, | ||
| 16 | + SET_GLOBAL_PROXY_DEFAULT: '1', // Signal to call without arguments | ||
| 17 | + }, { | ||
| 18 | + stdout: 'Hello world', | ||
| 19 | + }); | ||
| 20 | + | ||
| 21 | + shutdown(); | ||
| 22 | + | ||
| 23 | + // FIXME(undici:4083): undici currently always tunnels the request over | ||
| 24 | + // CONNECT if proxyTunnel is not explicitly set to false. | ||
| 25 | + const expectedLogs = [{ | ||
| 26 | + method: 'CONNECT', | ||
| 27 | + url: serverHost, | ||
| 28 | + headers: { | ||
| 29 | + 'connection': 'close', | ||
| 30 | + 'host': serverHost, | ||
| 31 | + 'proxy-connection': 'keep-alive', | ||
| 32 | + }, | ||
| 33 | + }]; | ||
| 34 | + | ||
| 35 | + assert.deepStrictEqual(proxyLogs, expectedLogs); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,21 @@ | |||
| 1 | + // Tests that http.setGlobalProxyFromEnv() is a no-op when no proxy is configured. | ||
| 2 | + | ||
| 3 | + import '../common/index.mjs'; | ||
| 4 | + import assert from 'node:assert'; | ||
| 5 | + import { startTestServers, checkProxiedFetch } from '../common/proxy-server.js'; | ||
| 6 | + | ||
| 7 | + const { proxyLogs, shutdown, httpEndpoint: { requestUrl } } = await startTestServers({ | ||
| 8 | + httpEndpoint: true, | ||
| 9 | + }); | ||
| 10 | + | ||
| 11 | + await checkProxiedFetch({ | ||
| 12 | + FETCH_URL: requestUrl, | ||
| 13 | + SET_GLOBAL_PROXY: JSON.stringify({}), | ||
| 14 | + }, { | ||
| 15 | + stdout: 'Hello world', | ||
| 16 | + }); | ||
| 17 | + | ||
| 18 | + shutdown(); | ||
| 19 | + | ||
| 20 | + // Verify request did NOT go through proxy. | ||
| 21 | + assert.deepStrictEqual(proxyLogs, []); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments