| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ebec3ef commit 4210259
55 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2527,6 +2527,18 @@ Accessing `Object.prototype.__proto__` has been forbidden using | |||
| 2527 | 2527 | [`Object.setPrototypeOf`][] should be used to get and set the prototype of an | |
| 2528 | 2528 | object. | |
| 2529 | 2529 | ||
| 2530 | + <a id="ERR_PROXY_INVALID_CONFIG"></a> | ||
| 2531 | + | ||
| 2532 | + ### `ERR_PROXY_INVALID_CONFIG` | ||
| 2533 | + | ||
| 2534 | + Failed to proxy a request because the proxy configuration is invalid. | ||
| 2535 | + | ||
| 2536 | + <a id="ERR_PROXY_TUNNEL"></a> | ||
| 2537 | + | ||
| 2538 | + ### `ERR_PROXY_TUNNEL` | ||
| 2539 | + | ||
| 2540 | + Failed to establish proxy tunnel when `NODE_USE_ENV_PROXY` is enabled. | ||
| 2541 | + | ||
| 2530 | 2542 | <a id="ERR_QUIC_APPLICATION_ERROR"></a> | |
| 2531 | 2543 | ||
| 2532 | 2544 | ### `ERR_QUIC_APPLICATION_ERROR` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -116,6 +116,14 @@ http.get({ | |||
| 116 | 116 | <!-- YAML | |
| 117 | 117 | added: v0.3.4 | |
| 118 | 118 | changes: | |
| 119 | + - version: | ||
| 120 | + - REPLACEME | ||
| 121 | + pr-url: https://github.com/nodejs/node/pull/58980 | ||
| 122 | + description: Add support for `proxyEnv`. | ||
| 123 | + - version: | ||
| 124 | + - REPLACEME | ||
| 125 | + pr-url: https://github.com/nodejs/node/pull/58980 | ||
| 126 | + description: Add support for `defaultPort` and `protocol`. | ||
| 119 | 127 | - version: | |
| 120 | 128 | - v22.20.0 | |
| 121 | 129 | pr-url: https://github.com/nodejs/node/pull/59315 | |
@@ -188,6 +196,20 @@ changes: | |||
| 188 | 196 | **Default:** `'lifo'`. | |
| 189 | 197 | * `timeout` {number} Socket timeout in milliseconds. | |
| 190 | 198 | This will set the timeout when the socket is created. | |
| 199 | + * `proxyEnv` {Object|undefined} Environment variables for proxy configuration. | ||
| 200 | + See [Built-in Proxy Support][] for details. **Default:** `undefined` | ||
| 201 | + * `HTTP_PROXY` {string|undefined} URL for the proxy server that HTTP requests should use. | ||
| 202 | + If undefined, no proxy is used for HTTP requests. | ||
| 203 | + * `HTTPS_PROXY` {string|undefined} URL for the proxy server that HTTPS requests should use. | ||
| 204 | + If undefined, no proxy is used for HTTPS requests. | ||
| 205 | + * `NO_PROXY` {string|undefined} Patterns specifying the endpoints | ||
| 206 | + that should not be routed through a proxy. | ||
| 207 | + * `http_proxy` {string|undefined} Same as `HTTP_PROXY`. If both are set, `http_proxy` takes precedence. | ||
| 208 | + * `https_proxy` {string|undefined} Same as `HTTPS_PROXY`. If both are set, `https_proxy` takes precedence. | ||
| 209 | + * `no_proxy` {string|undefined} Same as `NO_PROXY`. If both are set, `no_proxy` takes precedence. | ||
| 210 | + * `defaultPort` {number} Default port to use when the port is not specified | ||
| 211 | + in requests. **Default:** `80`. | ||
| 212 | + * `protocol` {string} The protocol to use for the agent. **Default:** `'http:'`. | ||
| 191 | 213 | ||
| 192 | 214 | `options` in [`socket.connect()`][] are also supported. | |
| 193 | 215 | ||
@@ -4299,6 +4321,98 @@ added: | |||
| 4299 | 4321 | ||
| 4300 | 4322 | A browser-compatible implementation of {WebSocket}. | |
| 4301 | 4323 | ||
| 4324 | + ## Built-in Proxy Support | ||
| 4325 | + | ||
| 4326 | + <!-- YAML | ||
| 4327 | + added: REPLACEME | ||
| 4328 | + --> | ||
| 4329 | + | ||
| 4330 | + > Stability: 1.1 - Active development | ||
| 4331 | + | ||
| 4332 | + When Node.js creates the global agent, it checks the `NODE_USE_ENV_PROXY` | ||
| 4333 | + environment variable. If it is set to `1`, the global agent will be constructed | ||
| 4334 | + with `proxyEnv: process.env`, enabling proxy support based on the environment variables. | ||
| 4335 | + | ||
| 4336 | + Custom agents can also be created with proxy support by passing a | ||
| 4337 | + `proxyEnv` option when constructing the agent. The value can be `process.env` | ||
| 4338 | + if they just want to inherit the configuration from the environment variables, | ||
| 4339 | + or an object with specific setting overriding the environment. | ||
| 4340 | + | ||
| 4341 | + The following properties of the `proxyEnv` are checked to configure proxy | ||
| 4342 | + support. | ||
| 4343 | + | ||
| 4344 | + * `HTTP_PROXY` or `http_proxy`: Proxy server URL for HTTP requests. If both are set, | ||
| 4345 | + `http_proxy` takes precedence. | ||
| 4346 | + * `HTTPS_PROXY` or `https_proxy`: Proxy server URL for HTTPS requests. If both are set, | ||
| 4347 | + `https_proxy` takes precedence. | ||
| 4348 | + * `NO_PROXY` or `no_proxy`: Comma-separated list of hosts to bypass the proxy. If both are set, | ||
| 4349 | + `no_proxy` takes precedence. | ||
| 4350 | + | ||
| 4351 | + If the request is made to a Unix domain socket, the proxy settings will be ignored. | ||
| 4352 | + | ||
| 4353 | + ### Proxy URL Format | ||
| 4354 | + | ||
| 4355 | + Proxy URLs can use either HTTP or HTTPS protocols: | ||
| 4356 | + | ||
| 4357 | + * HTTP proxy: `http://proxy.example.com:8080` | ||
| 4358 | + * HTTPS proxy: `https://proxy.example.com:8080` | ||
| 4359 | + * Proxy with authentication: `http://username:password@proxy.example.com:8080` | ||
| 4360 | + | ||
| 4361 | + ### `NO_PROXY` Format | ||
| 4362 | + | ||
| 4363 | + The `NO_PROXY` environment variable supports several formats: | ||
| 4364 | + | ||
| 4365 | + * `*` - Bypass proxy for all hosts | ||
| 4366 | + * `example.com` - Exact host name match | ||
| 4367 | + * `.example.com` - Domain suffix match (matches `sub.example.com`) | ||
| 4368 | + * `*.example.com` - Wildcard domain match | ||
| 4369 | + * `192.168.1.100` - Exact IP address match | ||
| 4370 | + * `192.168.1.1-192.168.1.100` - IP address range | ||
| 4371 | + * `example.com:8080` - Hostname with specific port | ||
| 4372 | + | ||
| 4373 | + Multiple entries should be separated by commas. | ||
| 4374 | + | ||
| 4375 | + ### Example | ||
| 4376 | + | ||
| 4377 | + Starting a Node.js process with proxy support enabled for all requests sent | ||
| 4378 | + through the default global agent: | ||
| 4379 | + | ||
| 4380 | + ```console | ||
| 4381 | + NODE_USE_ENV_PROXY=1 HTTP_PROXY=http://proxy.example.com:8080 NO_PROXY=localhost,127.0.0.1 node client.js | ||
| 4382 | + ``` | ||
| 4383 | + | ||
| 4384 | + To create a custom agent with built-in proxy support: | ||
| 4385 | + | ||
| 4386 | + ```cjs | ||
| 4387 | + const http = require('node:http'); | ||
| 4388 | + | ||
| 4389 | + // Creating a custom agent with custom proxy support. | ||
| 4390 | + const agent = new http.Agent({ proxyEnv: { HTTP_PROXY: 'http://proxy.example.com:8080' } }); | ||
| 4391 | + | ||
| 4392 | + http.request({ | ||
| 4393 | + hostname: 'www.example.com', | ||
| 4394 | + port: 80, | ||
| 4395 | + path: '/', | ||
| 4396 | + agent, | ||
| 4397 | + }, (res) => { | ||
| 4398 | + // This request will be proxied through proxy.example.com:8080 using the HTTP protocol. | ||
| 4399 | + console.log(`STATUS: ${res.statusCode}`); | ||
| 4400 | + }); | ||
| 4401 | + ``` | ||
| 4402 | + | ||
| 4403 | + Alternatively, the following also works: | ||
| 4404 | + | ||
| 4405 | + ```cjs | ||
| 4406 | + const http = require('node:http'); | ||
| 4407 | + // Use lower-cased option name. | ||
| 4408 | + const agent1 = new http.Agent({ proxyEnv: { http_proxy: 'http://proxy.example.com:8080' } }); | ||
| 4409 | + // Use values inherited from the environment variables, if the process is started with | ||
| 4410 | + // HTTP_PROXY=http://proxy.example.com:8080 this will use the proxy server specified | ||
| 4411 | + // in process.env.HTTP_PROXY. | ||
| 4412 | + const agent2 = new http.Agent({ proxyEnv: process.env }); | ||
| 4413 | + ``` | ||
| 4414 | + | ||
| 4415 | + [Built-in Proxy Support]: #built-in-proxy-support | ||
| 4302 | 4416 | [RFC 8187]: https://www.rfc-editor.org/rfc/rfc8187.txt | |
| 4303 | 4417 | [`'ERR_HTTP_CONTENT_LENGTH_MISMATCH'`]: errors.md#err_http_content_length_mismatch | |
| 4304 | 4418 | [`'checkContinue'`]: #event-checkcontinue | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -65,6 +65,14 @@ An [`Agent`][] object for HTTPS similar to [`http.Agent`][]. See | |||
| 65 | 65 | ||
| 66 | 66 | <!-- YAML | |
| 67 | 67 | changes: | |
| 68 | + - version: | ||
| 69 | + - REPLACEME | ||
| 70 | + pr-url: https://github.com/nodejs/node/pull/58980 | ||
| 71 | + description: Add support for `proxyEnv`. | ||
| 72 | + - version: | ||
| 73 | + - REPLACEME | ||
| 74 | + pr-url: https://github.com/nodejs/node/pull/58980 | ||
| 75 | + description: Add support for `defaultPort` and `protocol`. | ||
| 68 | 76 | - version: v12.5.0 | |
| 69 | 77 | pr-url: https://github.com/nodejs/node/pull/28209 | |
| 70 | 78 | description: do not automatically set servername if the target host was | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,9 +35,17 @@ const EventEmitter = require('events'); | |||
| 35 | 35 | let debug = require('internal/util/debuglog').debuglog('http', (fn) => { | |
| 36 | 36 | debug = fn; | |
| 37 | 37 | }); | |
| 38 | + const { | ||
| 39 | + parseProxyConfigFromEnv, | ||
| 40 | + kProxyConfig, | ||
| 41 | + checkShouldUseProxy, | ||
| 42 | + kWaitForProxyTunnel, | ||
| 43 | + filterEnvForProxies, | ||
| 44 | + } = require('internal/http'); | ||
| 38 | 45 | const { AsyncResource } = require('async_hooks'); | |
| 39 | 46 | const { async_id_symbol } = require('internal/async_hooks').symbols; | |
| 40 | 47 | const { | |
| 48 | + getLazy, | ||
| 41 | 49 | kEmptyObject, | |
| 42 | 50 | once, | |
| 43 | 51 | } = require('internal/util'); | |
@@ -46,6 +54,7 @@ const { | |||
| 46 | 54 | validateOneOf, | |
| 47 | 55 | validateString, | |
| 48 | 56 | } = require('internal/validators'); | |
| 57 | + const assert = require('internal/assert'); | ||
| 49 | 58 | ||
| 50 | 59 | const kOnKeylog = Symbol('onkeylog'); | |
| 51 | 60 | const kRequestOptions = Symbol('requestOptions'); | |
@@ -83,11 +92,11 @@ function Agent(options) { | |||
| 83 | 92 | ||
| 84 | 93 | EventEmitter.call(this); | |
| 85 | 94 | ||
| 86 | - this.defaultPort = 80; | ||
| 87 | - this.protocol = 'http:'; | ||
| 88 | - | ||
| 89 | 95 | this.options = { __proto__: null, ...options }; | |
| 90 | 96 | ||
| 97 | + this.defaultPort = this.options.defaultPort || 80; | ||
| 98 | + this.protocol = this.options.protocol || 'http:'; | ||
| 99 | + | ||
| 91 | 100 | if (this.options.noDelay === undefined) | |
| 92 | 101 | this.options.noDelay = true; | |
| 93 | 102 | ||
@@ -103,6 +112,11 @@ function Agent(options) { | |||
| 103 | 112 | this.scheduling = this.options.scheduling || 'lifo'; | |
| 104 | 113 | this.maxTotalSockets = this.options.maxTotalSockets; | |
| 105 | 114 | this.totalSocketCount = 0; | |
| 115 | + const proxyEnv = this.options.proxyEnv; | ||
| 116 | + if (typeof proxyEnv === 'object' && proxyEnv !== null) { | ||
| 117 | + this[kProxyConfig] = parseProxyConfigFromEnv(proxyEnv, this.protocol, this.keepAlive); | ||
| 118 | + debug(`new ${this.protocol} agent with proxy config`, this[kProxyConfig]); | ||
| 119 | + } | ||
| 106 | 120 | ||
| 107 | 121 | this.agentKeepAliveTimeoutBuffer = | |
| 108 | 122 | typeof this.options.agentKeepAliveTimeoutBuffer === 'number' && | |
@@ -206,9 +220,40 @@ function maybeEnableKeylog(eventName) { | |||
| 206 | 220 | } | |
| 207 | 221 | } | |
| 208 | 222 | ||
| 223 | + const lazyTLS = getLazy(() => require('tls')); | ||
| 224 | + | ||
| 209 | 225 | Agent.defaultMaxSockets = Infinity; | |
| 210 | 226 | ||
| 211 | - Agent.prototype.createConnection = net.createConnection; | ||
| 227 | + // See ProxyConfig in internal/http.js for how the connection should be handled | ||
| 228 | + // when the agent is configured to use a proxy server. | ||
| 229 | + Agent.prototype.createConnection = function createConnection(...args) { | ||
| 230 | + const normalized = net._normalizeArgs(args); | ||
| 231 | + const options = normalized[0]; | ||
| 232 | + const cb = normalized[1]; | ||
| 233 | + | ||
| 234 | + // Check if this specific request should bypass the proxy | ||
| 235 | + const shouldUseProxy = checkShouldUseProxy(this[kProxyConfig], options); | ||
| 236 | + debug(`http createConnection should use proxy for ${options.host}:${options.port}:`, shouldUseProxy); | ||
| 237 | + if (!shouldUseProxy) { // Forward to net.createConnection if no proxying is needed. | ||
| 238 | + return net.createConnection(...args); | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + // Create a copy of the shared proxy connection options and connect | ||
| 242 | + // to the proxy server instead of the endpoint. For Agent.prototype.createConnection | ||
| 243 | + // which is used by the http agent, this is enough | ||
| 244 | + const connectOptions = { | ||
| 245 | + ...this[kProxyConfig].proxyConnectionOptions, | ||
| 246 | + }; | ||
| 247 | + const proxyProtocol = this[kProxyConfig].protocol; | ||
| 248 | + if (proxyProtocol === 'http:') { | ||
| 249 | + return net.connect(connectOptions, cb); | ||
| 250 | + } else if (proxyProtocol === 'https:') { | ||
| 251 | + return lazyTLS().connect(connectOptions, cb); | ||
| 252 | + } | ||
| 253 | + // This should be unreachable because proxy config should be null for other protocols. | ||
| 254 | + assert.fail(`Unexpected proxy protocol ${proxyProtocol}`); | ||
| 255 | + | ||
| 256 | + }; | ||
| 212 | 257 | ||
| 213 | 258 | // Get the key for a given set of request options | |
| 214 | 259 | Agent.prototype.getName = function getName(options = kEmptyObject) { | |
@@ -233,6 +278,16 @@ Agent.prototype.getName = function getName(options = kEmptyObject) { | |||
| 233 | 278 | return name; | |
| 234 | 279 | }; | |
| 235 | 280 | ||
| 281 | + function handleSocketAfterProxy(err, req) { | ||
| 282 | + if (err.code === 'ERR_PROXY_TUNNEL') { | ||
| 283 | + if (err.proxyTunnelTimeout) { | ||
| 284 | + req.emit('timeout'); // Propagate the timeout from the tunnel to the request. | ||
| 285 | + } else { | ||
| 286 | + req.emit('error', err); | ||
| 287 | + } | ||
| 288 | + } | ||
| 289 | + } | ||
| 290 | + | ||
| 236 | 291 | Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */, | |
| 237 | 292 | localAddress/* legacy */) { | |
| 238 | 293 | // Legacy API: addRequest(req, host, port, localAddress) | |
@@ -245,6 +300,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */, | |||
| 245 | 300 | }; | |
| 246 | 301 | } | |
| 247 | 302 | ||
| 303 | + // Here the agent options will override per-request options. | ||
| 248 | 304 | options = { __proto__: null, ...options, ...this.options }; | |
| 249 | 305 | if (options.socketPath) | |
| 250 | 306 | options.path = options.socketPath; | |
@@ -270,20 +326,24 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */, | |||
| 270 | 326 | const freeLen = freeSockets ? freeSockets.length : 0; | |
| 271 | 327 | const sockLen = freeLen + this.sockets[name].length; | |
| 272 | 328 | ||
| 329 | + // Reusing a socket from the pool. | ||
| 273 | 330 | if (socket) { | |
| 274 | 331 | asyncResetHandle(socket); | |
| 275 | 332 | this.reuseSocket(socket, req); | |
| 276 | 333 | setRequestSocket(this, req, socket); | |
| 277 | 334 | this.sockets[name].push(socket); | |
| 278 | 335 | } else if (sockLen < this.maxSockets && | |
| 279 | 336 | this.totalSocketCount < this.maxTotalSockets) { | |
| 280 | - debug('call onSocket', sockLen, freeLen); | ||
| 281 | 337 | // If we are under maxSockets create a new one. | |
| 282 | 338 | this.createSocket(req, options, (err, socket) => { | |
| 283 | - if (err) | ||
| 339 | + if (err) { | ||
| 340 | + handleSocketAfterProxy(err, req); | ||
| 341 | + debug('call onSocket', sockLen, freeLen); | ||
| 284 | 342 | req.onSocket(socket, err); | |
| 285 | - else | ||
| 286 | - setRequestSocket(this, req, socket); | ||
| 343 | + return; | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + setRequestSocket(this, req, socket); | ||
| 287 | 347 | }); | |
| 288 | 348 | } else { | |
| 289 | 349 | debug('wait for socket'); | |
@@ -300,16 +360,23 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */, | |||
| 300 | 360 | }; | |
| 301 | 361 | ||
| 302 | 362 | Agent.prototype.createSocket = function createSocket(req, options, cb) { | |
| 363 | + // Here the agent options will override per-request options. | ||
| 303 | 364 | options = { __proto__: null, ...options, ...this.options }; | |
| 304 | 365 | if (options.socketPath) | |
| 305 | 366 | options.path = options.socketPath; | |
| 306 | 367 | ||
| 307 | 368 | normalizeServerName(options, req); | |
| 308 | 369 | ||
| 370 | + // Make sure per-request timeout is respected. | ||
| 371 | + const timeout = req.timeout || this.options.timeout || undefined; | ||
| 372 | + if (timeout) { | ||
| 373 | + options.timeout = timeout; | ||
| 374 | + } | ||
| 375 | + | ||
| 309 | 376 | const name = this.getName(options); | |
| 310 | 377 | options._agentKey = name; | |
| 311 | 378 | ||
| 312 | - debug('createConnection', name, options); | ||
| 379 | + debug('createConnection', name); | ||
| 313 | 380 | options.encoding = null; | |
| 314 | 381 | ||
| 315 | 382 | const oncreate = once((err, s) => { | |
@@ -327,8 +394,15 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) { | |||
| 327 | 394 | options.keepAlive = this.keepAlive; | |
| 328 | 395 | options.keepAliveInitialDelay = this.keepAliveMsecs; | |
| 329 | 396 | } | |
| 397 | + | ||
| 330 | 398 | const newSocket = this.createConnection(options, oncreate); | |
| 331 | - if (newSocket) | ||
| 399 | + // In the case where we are proxying through a tunnel for HTTPS, only add | ||
| 400 | + // the socket to the pool and install/invoke the listeners after | ||
| 401 | + // the tunnel is successfully established, so that actual operations | ||
| 402 | + // on the socket all go through the tunnel. Errors emitted during | ||
| 403 | + // tunnel establishment will be handled in the createConnection method | ||
| 404 | + // in lib/https.js. | ||
| 405 | + if (newSocket && !newSocket[kWaitForProxyTunnel]) | ||
| 332 | 406 | oncreate(null, newSocket); | |
| 333 | 407 | }; | |
| 334 | 408 | ||
@@ -462,10 +536,13 @@ Agent.prototype.removeSocket = function removeSocket(s, options) { | |||
| 462 | 536 | req[kRequestOptions] = undefined; | |
| 463 | 537 | // If we have pending requests and a socket gets closed make a new one | |
| 464 | 538 | this.createSocket(req, options, (err, socket) => { | |
| 465 | - if (err) | ||
| 466 | - req.onSocket(socket, err); | ||
| 467 | - else | ||
| 468 | - socket.emit('free'); | ||
| 539 | + if (err) { | ||
| 540 | + handleSocketAfterProxy(err, req); | ||
| 541 | + req.onSocket(null, err); | ||
| 542 | + return; | ||
| 543 | + } | ||
| 544 | + | ||
| 545 | + socket.emit('free'); | ||
| 469 | 546 | }); | |
| 470 | 547 | } | |
| 471 | 548 | ||
@@ -549,5 +626,8 @@ function asyncResetHandle(socket) { | |||
| 549 | 626 | ||
| 550 | 627 | module.exports = { | |
| 551 | 628 | Agent, | |
| 552 | - globalAgent: new Agent({ keepAlive: true, scheduling: 'lifo', timeout: 5000 }), | ||
| 629 | + globalAgent: new Agent({ | ||
| 630 | + keepAlive: true, scheduling: 'lifo', timeout: 5000, | ||
| 631 | + proxyEnv: process.env.NODE_USE_ENV_PROXY ? filterEnvForProxies(process.env) : undefined, | ||
| 632 | + }), | ||
| 553 | 633 | }; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments