undici 6.13.0 → 6.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/docs/docs/api/EnvHttpProxyAgent.md +162 -0
  2. package/docs/docs/api/EventSource.md +27 -3
  3. package/docs/docs/api/Fetch.md +0 -6
  4. package/index.js +3 -1
  5. package/lib/api/util.js +14 -7
  6. package/lib/core/request.js +3 -5
  7. package/lib/core/symbols.js +4 -1
  8. package/lib/core/util.js +45 -20
  9. package/lib/dispatcher/client-h2.js +2 -4
  10. package/lib/dispatcher/client.js +2 -1
  11. package/lib/dispatcher/dispatcher-base.js +1 -3
  12. package/lib/dispatcher/env-http-proxy-agent.js +160 -0
  13. package/lib/dispatcher/pool.js +1 -1
  14. package/lib/llhttp/.gitkeep +0 -0
  15. package/lib/llhttp/llhttp-wasm.js +3 -1
  16. package/lib/llhttp/llhttp_simd-wasm.js +3 -1
  17. package/lib/web/cache/cache.js +2 -3
  18. package/lib/web/eventsource/eventsource.js +35 -38
  19. package/lib/web/fetch/body.js +12 -10
  20. package/lib/web/fetch/file.js +5 -216
  21. package/lib/web/fetch/formdata-parser.js +2 -2
  22. package/lib/web/fetch/formdata.js +3 -3
  23. package/lib/web/fetch/index.js +3 -6
  24. package/lib/web/fetch/request.js +8 -26
  25. package/lib/web/fetch/response.js +9 -22
  26. package/lib/web/fetch/symbols.js +0 -1
  27. package/lib/web/fetch/util.js +34 -8
  28. package/lib/web/websocket/util.js +6 -13
  29. package/package.json +4 -3
  30. package/types/dispatcher.d.ts +1 -1
  31. package/types/env-http-proxy-agent.d.ts +21 -0
  32. package/types/eventsource.d.ts +4 -2
  33. package/types/index.d.ts +2 -1
  34. package/lib/llhttp/constants.d.ts +0 -199
  35. package/lib/llhttp/constants.js.map +0 -1
  36. package/lib/llhttp/utils.d.ts +0 -4
  37. package/lib/llhttp/utils.js.map +0 -1
  38. package/lib/llhttp/wasm_build_env.txt +0 -32
@@ -0,0 +1,162 @@
1
+ # Class: EnvHttpProxyAgent
2
+
3
+ Stability: Experimental.
4
+
5
+ Extends: `undici.Dispatcher`
6
+
7
+ EnvHttpProxyAgent automatically reads the proxy configuration from the environment variables `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` and sets up the proxy agents accordingly. When `HTTP_PROXY` and `HTTPS_PROXY` are set, `HTTP_PROXY` is used for HTTP requests and `HTTPS_PROXY` is used for HTTPS requests. If only `HTTP_PROXY` is set, `HTTP_PROXY` is used for both HTTP and HTTPS requests. If only `HTTPS_PROXY` is set, it is only used for HTTPS requests.
8
+
9
+ `NO_PROXY` is a comma or space-separated list of hostnames that should not be proxied. The list may contain leading wildcard characters (`*`). If `NO_PROXY` is set, the EnvHttpProxyAgent will bypass the proxy for requests to hosts that match the list. If `NO_PROXY` is set to `"*"`, the EnvHttpProxyAgent will bypass the proxy for all requests.
10
+
11
+ Lower case environment variables are also supported: `http_proxy`, `https_proxy`, and `no_proxy`. However, if both the lower case and upper case environment variables are set, the lower case environment variables will be ignored.
12
+
13
+ ## `new EnvHttpProxyAgent([options])`
14
+
15
+ Arguments:
16
+
17
+ * **options** `EnvHttpProxyAgentOptions` (optional) - extends the `Agent` options.
18
+
19
+ Returns: `EnvHttpProxyAgent`
20
+
21
+ ### Parameter: `EnvHttpProxyAgentOptions`
22
+
23
+ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
24
+
25
+ * **httpProxy** `string` (optional) - When set, it will override the `HTTP_PROXY` environment variable.
26
+ * **httpsProxy** `string` (optional) - When set, it will override the `HTTPS_PROXY` environment variable.
27
+ * **noProxy** `string` (optional) - When set, it will override the `NO_PROXY` environment variable.
28
+
29
+ Examples:
30
+
31
+ ```js
32
+ import { EnvHttpProxyAgent } from 'undici'
33
+
34
+ const envHttpProxyAgent = new EnvHttpProxyAgent()
35
+ // or
36
+ const envHttpProxyAgent = new EnvHttpProxyAgent({ httpProxy: 'my.proxy.server:8080', httpsProxy: 'my.proxy.server:8443', noProxy: 'localhost' })
37
+ ```
38
+
39
+ #### Example - EnvHttpProxyAgent instantiation
40
+
41
+ This will instantiate the EnvHttpProxyAgent. It will not do anything until registered as the agent to use with requests.
42
+
43
+ ```js
44
+ import { EnvHttpProxyAgent } from 'undici'
45
+
46
+ const envHttpProxyAgent = new EnvHttpProxyAgent()
47
+ ```
48
+
49
+ #### Example - Basic Proxy Fetch with global agent dispatcher
50
+
51
+ ```js
52
+ import { setGlobalDispatcher, fetch, EnvHttpProxyAgent } from 'undici'
53
+
54
+ const envHttpProxyAgent = new EnvHttpProxyAgent()
55
+ setGlobalDispatcher(envHttpProxyAgent)
56
+
57
+ const { status, json } = await fetch('http://localhost:3000/foo')
58
+
59
+ console.log('response received', status) // response received 200
60
+
61
+ const data = await json() // data { foo: "bar" }
62
+ ```
63
+
64
+ #### Example - Basic Proxy Request with global agent dispatcher
65
+
66
+ ```js
67
+ import { setGlobalDispatcher, request, EnvHttpProxyAgent } from 'undici'
68
+
69
+ const envHttpProxyAgent = new EnvHttpProxyAgent()
70
+ setGlobalDispatcher(envHttpProxyAgent)
71
+
72
+ const { statusCode, body } = await request('http://localhost:3000/foo')
73
+
74
+ console.log('response received', statusCode) // response received 200
75
+
76
+ for await (const data of body) {
77
+ console.log('data', data.toString('utf8')) // data foo
78
+ }
79
+ ```
80
+
81
+ #### Example - Basic Proxy Request with local agent dispatcher
82
+
83
+ ```js
84
+ import { EnvHttpProxyAgent, request } from 'undici'
85
+
86
+ const envHttpProxyAgent = new EnvHttpProxyAgent()
87
+
88
+ const {
89
+ statusCode,
90
+ body
91
+ } = await request('http://localhost:3000/foo', { dispatcher: envHttpProxyAgent })
92
+
93
+ console.log('response received', statusCode) // response received 200
94
+
95
+ for await (const data of body) {
96
+ console.log('data', data.toString('utf8')) // data foo
97
+ }
98
+ ```
99
+
100
+ #### Example - Basic Proxy Fetch with local agent dispatcher
101
+
102
+ ```js
103
+ import { EnvHttpProxyAgent, fetch } from 'undici'
104
+
105
+ const envHttpProxyAgent = new EnvHttpProxyAgent()
106
+
107
+ const {
108
+ status,
109
+ json
110
+ } = await fetch('http://localhost:3000/foo', { dispatcher: envHttpProxyAgent })
111
+
112
+ console.log('response received', status) // response received 200
113
+
114
+ const data = await json() // data { foo: "bar" }
115
+ ```
116
+
117
+ ## Instance Methods
118
+
119
+ ### `EnvHttpProxyAgent.close([callback])`
120
+
121
+ Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
122
+
123
+ ### `EnvHttpProxyAgent.destroy([error, callback])`
124
+
125
+ Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
126
+
127
+ ### `EnvHttpProxyAgent.dispatch(options, handler: AgentDispatchOptions)`
128
+
129
+ Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
130
+
131
+ #### Parameter: `AgentDispatchOptions`
132
+
133
+ Extends: [`DispatchOptions`](Dispatcher.md#parameter-dispatchoptions)
134
+
135
+ * **origin** `string | URL`
136
+ * **maxRedirections** `Integer`.
137
+
138
+ Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
139
+
140
+ ### `EnvHttpProxyAgent.connect(options[, callback])`
141
+
142
+ See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
143
+
144
+ ### `EnvHttpProxyAgent.dispatch(options, handler)`
145
+
146
+ Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
147
+
148
+ ### `EnvHttpProxyAgent.pipeline(options, handler)`
149
+
150
+ See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
151
+
152
+ ### `EnvHttpProxyAgent.request(options[, callback])`
153
+
154
+ See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
155
+
156
+ ### `EnvHttpProxyAgent.stream(options, factory[, callback])`
157
+
158
+ See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
159
+
160
+ ### `EnvHttpProxyAgent.upgrade(options[, callback])`
161
+
162
+ See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
@@ -1,5 +1,7 @@
1
1
  # EventSource
2
2
 
3
+ > ⚠️ Warning: the EventSource API is experimental.
4
+
3
5
  Undici exposes a WHATWG spec-compliant implementation of [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
4
6
  for [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events).
5
7
 
@@ -11,11 +13,33 @@ follows:
11
13
  ```mjs
12
14
  import { EventSource } from 'undici'
13
15
 
14
- const evenSource = new EventSource('http://localhost:3000')
15
- evenSource.onmessage = (event) => {
16
+ const eventSource = new EventSource('http://localhost:3000')
17
+ eventSource.onmessage = (event) => {
16
18
  console.log(event.data)
17
19
  }
18
20
  ```
19
21
 
22
+ ## Using a custom Dispatcher
23
+
24
+ undici allows you to set your own Dispatcher in the EventSource constructor.
25
+
26
+ An example which allows you to modify the request headers is:
27
+
28
+ ```mjs
29
+ import { EventSource, Agent } from 'undici'
30
+
31
+ class CustomHeaderAgent extends Agent {
32
+ dispatch (opts) {
33
+ opts.headers['x-custom-header'] = 'hello world'
34
+ return super.dispatch(...arguments)
35
+ }
36
+ }
37
+
38
+ const eventSource = new EventSource('http://localhost:3000', {
39
+ dispatcher: new CustomHeaderAgent()
40
+ })
41
+
42
+ ```
43
+
20
44
  More information about the EventSource API can be found on
21
- [MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).
45
+ [MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).
@@ -4,12 +4,6 @@ Undici exposes a fetch() method starts the process of fetching a resource from t
4
4
 
5
5
  Documentation and examples can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/fetch).
6
6
 
7
- ## File
8
-
9
- This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/File)
10
-
11
- In Node versions v18.13.0 and above and v19.2.0 and above, undici will default to using Node's [File](https://nodejs.org/api/buffer.html#class-file) class. In versions where it's not available, it will default to the undici one.
12
-
13
7
  ## FormData
14
8
 
15
9
  This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/FormData).
package/index.js CHANGED
@@ -6,6 +6,7 @@ const Pool = require('./lib/dispatcher/pool')
6
6
  const BalancedPool = require('./lib/dispatcher/balanced-pool')
7
7
  const Agent = require('./lib/dispatcher/agent')
8
8
  const ProxyAgent = require('./lib/dispatcher/proxy-agent')
9
+ const EnvHttpProxyAgent = require('./lib/dispatcher/env-http-proxy-agent')
9
10
  const RetryAgent = require('./lib/dispatcher/retry-agent')
10
11
  const errors = require('./lib/core/errors')
11
12
  const util = require('./lib/core/util')
@@ -30,6 +31,7 @@ module.exports.Pool = Pool
30
31
  module.exports.BalancedPool = BalancedPool
31
32
  module.exports.Agent = Agent
32
33
  module.exports.ProxyAgent = ProxyAgent
34
+ module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent
33
35
  module.exports.RetryAgent = RetryAgent
34
36
  module.exports.RetryHandler = RetryHandler
35
37
 
@@ -116,7 +118,7 @@ module.exports.Headers = require('./lib/web/fetch/headers').Headers
116
118
  module.exports.Response = require('./lib/web/fetch/response').Response
117
119
  module.exports.Request = require('./lib/web/fetch/request').Request
118
120
  module.exports.FormData = require('./lib/web/fetch/formdata').FormData
119
- module.exports.File = require('./lib/web/fetch/file').File
121
+ module.exports.File = globalThis.File ?? require('node:buffer').File
120
122
  module.exports.FileReader = require('./lib/web/fileapi/filereader').FileReader
121
123
 
122
124
  const { setGlobalOrigin, getGlobalOrigin } = require('./lib/web/fetch/global')
package/lib/api/util.js CHANGED
@@ -12,18 +12,25 @@ async function getResolveErrorBodyCallback ({ callback, body, contentType, statu
12
12
  let chunks = []
13
13
  let length = 0
14
14
 
15
- for await (const chunk of body) {
16
- chunks.push(chunk)
17
- length += chunk.length
18
- if (length > CHUNK_LIMIT) {
19
- chunks = null
20
- break
15
+ try {
16
+ for await (const chunk of body) {
17
+ chunks.push(chunk)
18
+ length += chunk.length
19
+ if (length > CHUNK_LIMIT) {
20
+ chunks = []
21
+ length = 0
22
+ break
23
+ }
21
24
  }
25
+ } catch {
26
+ chunks = []
27
+ length = 0
28
+ // Do nothing....
22
29
  }
23
30
 
24
31
  const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`
25
32
 
26
- if (statusCode === 204 || !contentType || !chunks) {
33
+ if (statusCode === 204 || !contentType || !length) {
27
34
  queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers)))
28
35
  return
29
36
  }
@@ -7,7 +7,7 @@ const {
7
7
  const assert = require('node:assert')
8
8
  const {
9
9
  isValidHTTPToken,
10
- isValidHeaderChar,
10
+ isValidHeaderValue,
11
11
  isStream,
12
12
  destroy,
13
13
  isBuffer,
@@ -336,7 +336,7 @@ function processHeader (request, key, val) {
336
336
  const arr = []
337
337
  for (let i = 0; i < val.length; i++) {
338
338
  if (typeof val[i] === 'string') {
339
- if (!isValidHeaderChar(val[i])) {
339
+ if (!isValidHeaderValue(val[i])) {
340
340
  throw new InvalidArgumentError(`invalid ${key} header`)
341
341
  }
342
342
  arr.push(val[i])
@@ -350,13 +350,11 @@ function processHeader (request, key, val) {
350
350
  }
351
351
  val = arr
352
352
  } else if (typeof val === 'string') {
353
- if (!isValidHeaderChar(val)) {
353
+ if (!isValidHeaderValue(val)) {
354
354
  throw new InvalidArgumentError(`invalid ${key} header`)
355
355
  }
356
356
  } else if (val === null) {
357
357
  val = ''
358
- } else if (typeof val === 'object') {
359
- throw new InvalidArgumentError(`invalid ${key} header`)
360
358
  } else {
361
359
  val = `${val}`
362
360
  }
@@ -60,5 +60,8 @@ module.exports = {
60
60
  kConstruct: Symbol('constructable'),
61
61
  kListeners: Symbol('listeners'),
62
62
  kHTTPContext: Symbol('http context'),
63
- kMaxConcurrentStreams: Symbol('max concurrent streams')
63
+ kMaxConcurrentStreams: Symbol('max concurrent streams'),
64
+ kNoProxyAgent: Symbol('no proxy agent'),
65
+ kHttpProxyAgent: Symbol('http proxy agent'),
66
+ kHttpsProxyAgent: Symbol('https proxy agent')
64
67
  }
package/lib/core/util.js CHANGED
@@ -52,11 +52,37 @@ function buildURL (url, queryParams) {
52
52
  return url
53
53
  }
54
54
 
55
+ function isValidPort (port) {
56
+ const value = parseInt(port, 10)
57
+ return (
58
+ value === Number(port) &&
59
+ value >= 0 &&
60
+ value <= 65535
61
+ )
62
+ }
63
+
64
+ function isHttpOrHttpsPrefixed (value) {
65
+ return (
66
+ value != null &&
67
+ value[0] === 'h' &&
68
+ value[1] === 't' &&
69
+ value[2] === 't' &&
70
+ value[3] === 'p' &&
71
+ (
72
+ value[4] === ':' ||
73
+ (
74
+ value[4] === 's' &&
75
+ value[5] === ':'
76
+ )
77
+ )
78
+ )
79
+ }
80
+
55
81
  function parseURL (url) {
56
82
  if (typeof url === 'string') {
57
83
  url = new URL(url)
58
84
 
59
- if (!/^https?:/.test(url.origin || url.protocol)) {
85
+ if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
60
86
  throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
61
87
  }
62
88
 
@@ -67,12 +93,8 @@ function parseURL (url) {
67
93
  throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.')
68
94
  }
69
95
 
70
- if (!/^https?:/.test(url.origin || url.protocol)) {
71
- throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
72
- }
73
-
74
96
  if (!(url instanceof URL)) {
75
- if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) {
97
+ if (url.port != null && url.port !== '' && isValidPort(url.port) === false) {
76
98
  throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.')
77
99
  }
78
100
 
@@ -92,28 +114,36 @@ function parseURL (url) {
92
114
  throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.')
93
115
  }
94
116
 
117
+ if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
118
+ throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
119
+ }
120
+
95
121
  const port = url.port != null
96
122
  ? url.port
97
123
  : (url.protocol === 'https:' ? 443 : 80)
98
124
  let origin = url.origin != null
99
125
  ? url.origin
100
- : `${url.protocol}//${url.hostname}:${port}`
126
+ : `${url.protocol || ''}//${url.hostname || ''}:${port}`
101
127
  let path = url.path != null
102
128
  ? url.path
103
129
  : `${url.pathname || ''}${url.search || ''}`
104
130
 
105
- if (origin.endsWith('/')) {
106
- origin = origin.substring(0, origin.length - 1)
131
+ if (origin[origin.length - 1] === '/') {
132
+ origin = origin.slice(0, origin.length - 1)
107
133
  }
108
134
 
109
- if (path && !path.startsWith('/')) {
135
+ if (path && path[0] !== '/') {
110
136
  path = `/${path}`
111
137
  }
112
138
  // new URL(path, origin) is unsafe when `path` contains an absolute URL
113
139
  // From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL:
114
140
  // If first parameter is a relative URL, second param is required, and will be used as the base URL.
115
141
  // If first parameter is an absolute URL, a given second param will be ignored.
116
- url = new URL(origin + path)
142
+ return new URL(`${origin}${path}`)
143
+ }
144
+
145
+ if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
146
+ throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
117
147
  }
118
148
 
119
149
  return url
@@ -193,11 +223,6 @@ function isDestroyed (body) {
193
223
  return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
194
224
  }
195
225
 
196
- function isReadableAborted (stream) {
197
- const state = stream?._readableState
198
- return isDestroyed(stream) && state && !state.endEmitted
199
- }
200
-
201
226
  function destroy (stream, err) {
202
227
  if (stream == null || !isStream(stream) || isDestroyed(stream)) {
203
228
  return
@@ -522,7 +547,7 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
522
547
  /**
523
548
  * @param {string} characters
524
549
  */
525
- function isValidHeaderChar (characters) {
550
+ function isValidHeaderValue (characters) {
526
551
  return !headerCharRegex.test(characters)
527
552
  }
528
553
 
@@ -575,7 +600,6 @@ module.exports = {
575
600
  isReadable,
576
601
  toUSVString,
577
602
  isUSVString,
578
- isReadableAborted,
579
603
  isBlobLike,
580
604
  parseOrigin,
581
605
  parseURL,
@@ -603,11 +627,12 @@ module.exports = {
603
627
  buildURL,
604
628
  addAbortListener,
605
629
  isValidHTTPToken,
606
- isValidHeaderChar,
630
+ isValidHeaderValue,
607
631
  isTokenCharCode,
608
632
  parseRangeHeader,
633
+ isValidPort,
634
+ isHttpOrHttpsPrefixed,
609
635
  nodeMajor,
610
636
  nodeMinor,
611
- nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13),
612
637
  safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
613
638
  }
@@ -208,10 +208,9 @@ function onHttp2SessionEnd () {
208
208
  * This is the root cause of #3011
209
209
  * We need to handle GOAWAY frames properly, and trigger the session close
210
210
  * along with the socket right away
211
- * Find a way to trigger the close cycle from here on.
212
211
  */
213
212
  function onHTTP2GoAway (code) {
214
- const err = new InformationalError(`HTTP/2: "GOAWAY" frame received with code ${code}`)
213
+ const err = new RequestAbortedError(`HTTP/2: "GOAWAY" frame received with code ${code}`)
215
214
 
216
215
  // We need to trigger the close cycle right away
217
216
  // We need to destroy the session and the socket
@@ -220,8 +219,7 @@ function onHTTP2GoAway (code) {
220
219
  this[kClient][kOnError](err)
221
220
 
222
221
  this.unref()
223
- // We send the GOAWAY frame response as no error
224
- this.destroy()
222
+
225
223
  util.destroy(this[kSocket], err)
226
224
  }
227
225
 
@@ -203,7 +203,7 @@ class Client extends DispatcherBase {
203
203
  allowH2,
204
204
  socketPath,
205
205
  timeout: connectTimeout,
206
- ...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
206
+ ...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
207
207
  ...connect
208
208
  })
209
209
  }
@@ -376,6 +376,7 @@ function onError (client, err) {
376
376
  assert(client[kPendingIdx] === client[kRunningIdx])
377
377
 
378
378
  const requests = client[kQueue].splice(client[kRunningIdx])
379
+
379
380
  for (let i = 0; i < requests.length; i++) {
380
381
  const request = requests[i]
381
382
  util.errorRequest(client, request, err)
@@ -6,10 +6,8 @@ const {
6
6
  ClientClosedError,
7
7
  InvalidArgumentError
8
8
  } = require('../core/errors')
9
- const { kDestroy, kClose, kDispatch, kInterceptors } = require('../core/symbols')
9
+ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = require('../core/symbols')
10
10
 
11
- const kDestroyed = Symbol('destroyed')
12
- const kClosed = Symbol('closed')
13
11
  const kOnDestroyed = Symbol('onDestroyed')
14
12
  const kOnClosed = Symbol('onClosed')
15
13
  const kInterceptedDispatch = Symbol('Intercepted Dispatch')
@@ -0,0 +1,160 @@
1
+ 'use strict'
2
+
3
+ const DispatcherBase = require('./dispatcher-base')
4
+ const { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require('../core/symbols')
5
+ const ProxyAgent = require('./proxy-agent')
6
+ const Agent = require('./agent')
7
+
8
+ const DEFAULT_PORTS = {
9
+ 'http:': 80,
10
+ 'https:': 443
11
+ }
12
+
13
+ let experimentalWarned = false
14
+
15
+ class EnvHttpProxyAgent extends DispatcherBase {
16
+ #noProxyValue = null
17
+ #noProxyEntries = null
18
+ #opts = null
19
+
20
+ constructor (opts = {}) {
21
+ super()
22
+ this.#opts = opts
23
+
24
+ if (!experimentalWarned) {
25
+ experimentalWarned = true
26
+ process.emitWarning('EnvHttpProxyAgent is experimental, expect them to change at any time.', {
27
+ code: 'UNDICI-EHPA'
28
+ })
29
+ }
30
+
31
+ const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts
32
+
33
+ this[kNoProxyAgent] = new Agent(agentOpts)
34
+
35
+ const HTTP_PROXY = httpProxy ?? process.env.HTTP_PROXY ?? process.env.http_proxy
36
+ if (HTTP_PROXY) {
37
+ this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY })
38
+ } else {
39
+ this[kHttpProxyAgent] = this[kNoProxyAgent]
40
+ }
41
+
42
+ const HTTPS_PROXY = httpsProxy ?? process.env.HTTPS_PROXY ?? process.env.https_proxy
43
+ if (HTTPS_PROXY) {
44
+ this[kHttpsProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTPS_PROXY })
45
+ } else {
46
+ this[kHttpsProxyAgent] = this[kHttpProxyAgent]
47
+ }
48
+
49
+ this.#parseNoProxy()
50
+ }
51
+
52
+ [kDispatch] (opts, handler) {
53
+ const url = new URL(opts.origin)
54
+ const agent = this.#getProxyAgentForUrl(url)
55
+ return agent.dispatch(opts, handler)
56
+ }
57
+
58
+ async [kClose] () {
59
+ await this[kNoProxyAgent].close()
60
+ if (!this[kHttpProxyAgent][kClosed]) {
61
+ await this[kHttpProxyAgent].close()
62
+ }
63
+ if (!this[kHttpsProxyAgent][kClosed]) {
64
+ await this[kHttpsProxyAgent].close()
65
+ }
66
+ }
67
+
68
+ async [kDestroy] (err) {
69
+ await this[kNoProxyAgent].destroy(err)
70
+ if (!this[kHttpProxyAgent][kDestroyed]) {
71
+ await this[kHttpProxyAgent].destroy(err)
72
+ }
73
+ if (!this[kHttpsProxyAgent][kDestroyed]) {
74
+ await this[kHttpsProxyAgent].destroy(err)
75
+ }
76
+ }
77
+
78
+ #getProxyAgentForUrl (url) {
79
+ let { protocol, host: hostname, port } = url
80
+
81
+ // Stripping ports in this way instead of using parsedUrl.hostname to make
82
+ // sure that the brackets around IPv6 addresses are kept.
83
+ hostname = hostname.replace(/:\d*$/, '').toLowerCase()
84
+ port = Number.parseInt(port, 10) || DEFAULT_PORTS[protocol] || 0
85
+ if (!this.#shouldProxy(hostname, port)) {
86
+ return this[kNoProxyAgent]
87
+ }
88
+ if (protocol === 'https:') {
89
+ return this[kHttpsProxyAgent]
90
+ }
91
+ return this[kHttpProxyAgent]
92
+ }
93
+
94
+ #shouldProxy (hostname, port) {
95
+ if (this.#noProxyChanged) {
96
+ this.#parseNoProxy()
97
+ }
98
+
99
+ if (this.#noProxyEntries.length === 0) {
100
+ return true // Always proxy if NO_PROXY is not set or empty.
101
+ }
102
+ if (this.#noProxyValue === '*') {
103
+ return false // Never proxy if wildcard is set.
104
+ }
105
+
106
+ for (let i = 0; i < this.#noProxyEntries.length; i++) {
107
+ const entry = this.#noProxyEntries[i]
108
+ if (entry.port && entry.port !== port) {
109
+ continue // Skip if ports don't match.
110
+ }
111
+ if (!/^[.*]/.test(entry.hostname)) {
112
+ // No wildcards, so don't proxy only if there is not an exact match.
113
+ if (hostname === entry.hostname) {
114
+ return false
115
+ }
116
+ } else {
117
+ // Don't proxy if the hostname ends with the no_proxy host.
118
+ if (hostname.endsWith(entry.hostname.replace(/^\*/, ''))) {
119
+ return false
120
+ }
121
+ }
122
+ }
123
+
124
+ return true
125
+ }
126
+
127
+ #parseNoProxy () {
128
+ const noProxyValue = this.#opts.noProxy ?? this.#noProxyEnv
129
+ const noProxySplit = noProxyValue.split(/[,\s]/)
130
+ const noProxyEntries = []
131
+
132
+ for (let i = 0; i < noProxySplit.length; i++) {
133
+ const entry = noProxySplit[i]
134
+ if (!entry) {
135
+ continue
136
+ }
137
+ const parsed = entry.match(/^(.+):(\d+)$/)
138
+ noProxyEntries.push({
139
+ hostname: (parsed ? parsed[1] : entry).toLowerCase(),
140
+ port: parsed ? Number.parseInt(parsed[2], 10) : 0
141
+ })
142
+ }
143
+
144
+ this.#noProxyValue = noProxyValue
145
+ this.#noProxyEntries = noProxyEntries
146
+ }
147
+
148
+ get #noProxyChanged () {
149
+ if (this.#opts.noProxy !== undefined) {
150
+ return false
151
+ }
152
+ return this.#noProxyValue !== this.#noProxyEnv
153
+ }
154
+
155
+ get #noProxyEnv () {
156
+ return process.env.NO_PROXY ?? process.env.no_proxy ?? ''
157
+ }
158
+ }
159
+
160
+ module.exports = EnvHttpProxyAgent
@@ -58,7 +58,7 @@ class Pool extends PoolBase {
58
58
  allowH2,
59
59
  socketPath,
60
60
  timeout: connectTimeout,
61
- ...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
61
+ ...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
62
62
  ...connect
63
63
  })
64
64
  }
File without changes