undici 6.19.8 → 7.0.0-alpha.1

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 (115) hide show
  1. package/README.md +5 -9
  2. package/docs/docs/api/Agent.md +0 -3
  3. package/docs/docs/api/Client.md +0 -2
  4. package/docs/docs/api/Dispatcher.md +204 -6
  5. package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
  6. package/docs/docs/api/Fetch.md +1 -0
  7. package/docs/docs/api/Pool.md +0 -1
  8. package/docs/docs/api/RetryHandler.md +1 -1
  9. package/index.js +0 -4
  10. package/lib/api/api-connect.js +3 -1
  11. package/lib/api/api-pipeline.js +3 -4
  12. package/lib/api/api-request.js +29 -46
  13. package/lib/api/api-stream.js +36 -49
  14. package/lib/api/api-upgrade.js +5 -3
  15. package/lib/api/readable.js +71 -27
  16. package/lib/core/connect.js +39 -24
  17. package/lib/core/errors.js +17 -4
  18. package/lib/core/request.js +7 -5
  19. package/lib/core/symbols.js +0 -1
  20. package/lib/core/tree.js +6 -0
  21. package/lib/core/util.js +1 -11
  22. package/lib/dispatcher/agent.js +3 -17
  23. package/lib/dispatcher/balanced-pool.js +5 -8
  24. package/lib/dispatcher/client-h1.js +44 -39
  25. package/lib/dispatcher/client.js +3 -27
  26. package/lib/dispatcher/dispatcher-base.js +2 -34
  27. package/lib/dispatcher/dispatcher.js +3 -24
  28. package/lib/dispatcher/pool.js +3 -6
  29. package/lib/dispatcher/proxy-agent.js +3 -6
  30. package/lib/handler/decorator-handler.js +24 -0
  31. package/lib/handler/redirect-handler.js +9 -0
  32. package/lib/handler/retry-handler.js +22 -3
  33. package/lib/interceptor/dump.js +2 -2
  34. package/lib/interceptor/redirect.js +11 -14
  35. package/lib/interceptor/response-error.js +89 -0
  36. package/lib/llhttp/constants.d.ts +97 -0
  37. package/lib/llhttp/constants.js +412 -192
  38. package/lib/llhttp/constants.js.map +1 -0
  39. package/lib/llhttp/llhttp-wasm.js +11 -1
  40. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  41. package/lib/llhttp/utils.d.ts +2 -0
  42. package/lib/llhttp/utils.js +9 -9
  43. package/lib/llhttp/utils.js.map +1 -0
  44. package/lib/mock/mock-client.js +2 -2
  45. package/lib/mock/mock-pool.js +2 -2
  46. package/lib/mock/mock-symbols.js +1 -0
  47. package/lib/util/timers.js +324 -44
  48. package/lib/web/cookies/index.js +15 -13
  49. package/lib/web/cookies/parse.js +2 -2
  50. package/lib/web/eventsource/eventsource-stream.js +9 -8
  51. package/lib/web/eventsource/eventsource.js +10 -6
  52. package/lib/web/fetch/body.js +4 -6
  53. package/lib/web/fetch/data-url.js +1 -1
  54. package/lib/web/fetch/formdata-parser.js +1 -2
  55. package/lib/web/fetch/formdata.js +28 -37
  56. package/lib/web/fetch/headers.js +1 -1
  57. package/lib/web/fetch/index.js +7 -8
  58. package/lib/web/fetch/request.js +7 -24
  59. package/lib/web/fetch/response.js +9 -22
  60. package/lib/web/fetch/symbols.js +0 -1
  61. package/lib/web/fetch/util.js +3 -12
  62. package/lib/web/fetch/webidl.js +73 -62
  63. package/lib/web/websocket/connection.js +26 -174
  64. package/lib/web/websocket/constants.js +1 -1
  65. package/lib/web/websocket/frame.js +45 -3
  66. package/lib/web/websocket/receiver.js +28 -26
  67. package/lib/web/websocket/sender.js +18 -13
  68. package/lib/web/websocket/util.js +20 -74
  69. package/lib/web/websocket/websocket.js +294 -70
  70. package/package.json +16 -29
  71. package/scripts/strip-comments.js +3 -1
  72. package/types/agent.d.ts +7 -7
  73. package/types/api.d.ts +24 -24
  74. package/types/balanced-pool.d.ts +11 -11
  75. package/types/client.d.ts +11 -12
  76. package/types/diagnostics-channel.d.ts +10 -10
  77. package/types/dispatcher.d.ts +96 -97
  78. package/types/env-http-proxy-agent.d.ts +2 -2
  79. package/types/errors.d.ts +53 -47
  80. package/types/eventsource.d.ts +0 -2
  81. package/types/fetch.d.ts +8 -8
  82. package/types/formdata.d.ts +7 -7
  83. package/types/global-dispatcher.d.ts +4 -4
  84. package/types/global-origin.d.ts +5 -5
  85. package/types/handlers.d.ts +4 -4
  86. package/types/header.d.ts +157 -1
  87. package/types/index.d.ts +42 -46
  88. package/types/interceptors.d.ts +10 -8
  89. package/types/mock-agent.d.ts +18 -18
  90. package/types/mock-client.d.ts +4 -4
  91. package/types/mock-errors.d.ts +3 -3
  92. package/types/mock-interceptor.d.ts +19 -19
  93. package/types/mock-pool.d.ts +4 -4
  94. package/types/patch.d.ts +0 -42
  95. package/types/pool-stats.d.ts +8 -8
  96. package/types/pool.d.ts +12 -12
  97. package/types/proxy-agent.d.ts +4 -4
  98. package/types/readable.d.ts +14 -9
  99. package/types/retry-agent.d.ts +1 -1
  100. package/types/retry-handler.d.ts +8 -8
  101. package/types/util.d.ts +3 -3
  102. package/types/utility.d.ts +7 -0
  103. package/types/webidl.d.ts +22 -4
  104. package/types/websocket.d.ts +1 -3
  105. package/docs/docs/api/DispatchInterceptor.md +0 -60
  106. package/lib/interceptor/redirect-interceptor.js +0 -21
  107. package/lib/web/fetch/file.js +0 -126
  108. package/lib/web/fileapi/encoding.js +0 -290
  109. package/lib/web/fileapi/filereader.js +0 -344
  110. package/lib/web/fileapi/progressevent.js +0 -78
  111. package/lib/web/fileapi/symbols.js +0 -10
  112. package/lib/web/fileapi/util.js +0 -391
  113. package/lib/web/websocket/symbols.js +0 -12
  114. package/types/file.d.ts +0 -39
  115. package/types/filereader.d.ts +0 -54
@@ -13,7 +13,7 @@ const {
13
13
  kGetDispatcher
14
14
  } = require('./pool-base')
15
15
  const Pool = require('./pool')
16
- const { kUrl, kInterceptors } = require('../core/symbols')
16
+ const { kUrl } = require('../core/symbols')
17
17
  const { parseOrigin } = require('../core/util')
18
18
  const kFactory = Symbol('factory')
19
19
 
@@ -50,6 +50,10 @@ function defaultFactory (origin, opts) {
50
50
 
51
51
  class BalancedPool extends PoolBase {
52
52
  constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) {
53
+ if (typeof factory !== 'function') {
54
+ throw new InvalidArgumentError('factory must be a function.')
55
+ }
56
+
53
57
  super()
54
58
 
55
59
  this[kOptions] = opts
@@ -63,13 +67,6 @@ class BalancedPool extends PoolBase {
63
67
  upstreams = [upstreams]
64
68
  }
65
69
 
66
- if (typeof factory !== 'function') {
67
- throw new InvalidArgumentError('factory must be a function.')
68
- }
69
-
70
- this[kInterceptors] = opts.interceptors?.BalancedPool && Array.isArray(opts.interceptors.BalancedPool)
71
- ? opts.interceptors.BalancedPool
72
- : []
73
70
  this[kFactory] = factory
74
71
 
75
72
  for (const upstream of upstreams) {
@@ -78,7 +78,6 @@ async function lazyllhttp () {
78
78
 
79
79
  return await WebAssembly.instantiate(mod, {
80
80
  env: {
81
- /* eslint-disable camelcase */
82
81
 
83
82
  wasm_on_url: (p, at, len) => {
84
83
  /* istanbul ignore next */
@@ -117,7 +116,6 @@ async function lazyllhttp () {
117
116
  return currentParser.onMessageComplete() || 0
118
117
  }
119
118
 
120
- /* eslint-enable camelcase */
121
119
  }
122
120
  })
123
121
  }
@@ -164,12 +162,12 @@ class Parser {
164
162
  this.maxResponseSize = client[kMaxResponseSize]
165
163
  }
166
164
 
167
- setTimeout (value, type) {
165
+ setTimeout (delay, type) {
168
166
  this.timeoutType = type
169
- if (value !== this.timeoutValue) {
170
- timers.clearTimeout(this.timeout)
171
- if (value) {
172
- this.timeout = timers.setTimeout(onParserTimeout, value, this)
167
+ if (delay !== this.timeoutValue) {
168
+ this.timeout && timers.clearTimeout(this.timeout)
169
+ if (delay) {
170
+ this.timeout = timers.setTimeout(onParserTimeout, delay, new WeakRef(this))
173
171
  // istanbul ignore else: only for jest
174
172
  if (this.timeout.unref) {
175
173
  this.timeout.unref()
@@ -177,7 +175,7 @@ class Parser {
177
175
  } else {
178
176
  this.timeout = null
179
177
  }
180
- this.timeoutValue = value
178
+ this.timeoutValue = delay
181
179
  } else if (this.timeout) {
182
180
  // istanbul ignore else: only for jest
183
181
  if (this.timeout.refresh) {
@@ -192,7 +190,7 @@ class Parser {
192
190
  }
193
191
 
194
192
  assert(this.ptr != null)
195
- assert(currentParser == null)
193
+ assert(currentParser === null)
196
194
 
197
195
  this.llhttp.llhttp_resume(this.ptr)
198
196
 
@@ -219,22 +217,27 @@ class Parser {
219
217
  }
220
218
  }
221
219
 
222
- execute (data) {
220
+ /**
221
+ * @param {Buffer} chunk
222
+ */
223
+ execute (chunk) {
224
+ assert(currentParser === null)
223
225
  assert(this.ptr != null)
224
- assert(currentParser == null)
225
226
  assert(!this.paused)
226
227
 
227
228
  const { socket, llhttp } = this
228
229
 
229
- if (data.length > currentBufferSize) {
230
+ // Allocate a new buffer if the current buffer is too small.
231
+ if (chunk.length > currentBufferSize) {
230
232
  if (currentBufferPtr) {
231
233
  llhttp.free(currentBufferPtr)
232
234
  }
233
- currentBufferSize = Math.ceil(data.length / 4096) * 4096
235
+ // Allocate a buffer that is a multiple of 4096 bytes.
236
+ currentBufferSize = Math.ceil(chunk.length / 4096) * 4096
234
237
  currentBufferPtr = llhttp.malloc(currentBufferSize)
235
238
  }
236
239
 
237
- new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data)
240
+ new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(chunk)
238
241
 
239
242
  // Call `execute` on the wasm parser.
240
243
  // We pass the `llhttp_parser` pointer address, the pointer address of buffer view data,
@@ -244,9 +247,9 @@ class Parser {
244
247
  let ret
245
248
 
246
249
  try {
247
- currentBufferRef = data
250
+ currentBufferRef = chunk
248
251
  currentParser = this
249
- ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, data.length)
252
+ ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, chunk.length)
250
253
  /* eslint-disable-next-line no-useless-catch */
251
254
  } catch (err) {
252
255
  /* istanbul ignore next: difficult to make a test case for */
@@ -256,25 +259,27 @@ class Parser {
256
259
  currentBufferRef = null
257
260
  }
258
261
 
259
- const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
260
-
261
- if (ret === constants.ERROR.PAUSED_UPGRADE) {
262
- this.onUpgrade(data.slice(offset))
263
- } else if (ret === constants.ERROR.PAUSED) {
264
- this.paused = true
265
- socket.unshift(data.slice(offset))
266
- } else if (ret !== constants.ERROR.OK) {
267
- const ptr = llhttp.llhttp_get_error_reason(this.ptr)
268
- let message = ''
269
- /* istanbul ignore else: difficult to make a test case for */
270
- if (ptr) {
271
- const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
272
- message =
273
- 'Response does not match the HTTP/1.1 protocol (' +
274
- Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
275
- ')'
262
+ if (ret !== constants.ERROR.OK) {
263
+ const data = chunk.subarray(llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr)
264
+
265
+ if (ret === constants.ERROR.PAUSED_UPGRADE) {
266
+ this.onUpgrade(data)
267
+ } else if (ret === constants.ERROR.PAUSED) {
268
+ this.paused = true
269
+ socket.unshift(data)
270
+ } else {
271
+ const ptr = llhttp.llhttp_get_error_reason(this.ptr)
272
+ let message = ''
273
+ /* istanbul ignore else: difficult to make a test case for */
274
+ if (ptr) {
275
+ const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
276
+ message =
277
+ 'Response does not match the HTTP/1.1 protocol (' +
278
+ Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
279
+ ')'
280
+ }
281
+ throw new HTTPParserError(message, constants.ERROR[ret], data)
276
282
  }
277
- throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
278
283
  }
279
284
  } catch (err) {
280
285
  util.destroy(socket, err)
@@ -282,13 +287,13 @@ class Parser {
282
287
  }
283
288
 
284
289
  destroy () {
290
+ assert(currentParser === null)
285
291
  assert(this.ptr != null)
286
- assert(currentParser == null)
287
292
 
288
293
  this.llhttp.llhttp_free(this.ptr)
289
294
  this.ptr = null
290
295
 
291
- timers.clearTimeout(this.timeout)
296
+ this.timeout && timers.clearTimeout(this.timeout)
292
297
  this.timeout = null
293
298
  this.timeoutValue = null
294
299
  this.timeoutType = null
@@ -613,16 +618,16 @@ class Parser {
613
618
  }
614
619
 
615
620
  function onParserTimeout (parser) {
616
- const { socket, timeoutType, client } = parser
621
+ const { socket, timeoutType, client, paused } = parser.deref()
617
622
 
618
623
  /* istanbul ignore else */
619
624
  if (timeoutType === TIMEOUT_HEADERS) {
620
625
  if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) {
621
- assert(!parser.paused, 'cannot be paused while waiting for headers')
626
+ assert(!paused, 'cannot be paused while waiting for headers')
622
627
  util.destroy(socket, new HeadersTimeoutError())
623
628
  }
624
629
  } else if (timeoutType === TIMEOUT_BODY) {
625
- if (!parser.paused) {
630
+ if (!paused) {
626
631
  util.destroy(socket, new BodyTimeoutError())
627
632
  }
628
633
  } else if (timeoutType === TIMEOUT_IDLE) {
@@ -43,13 +43,11 @@ const {
43
43
  kBodyTimeout,
44
44
  kStrictContentLength,
45
45
  kConnector,
46
- kMaxRedirections,
47
46
  kMaxRequests,
48
47
  kCounter,
49
48
  kClose,
50
49
  kDestroy,
51
50
  kDispatch,
52
- kInterceptors,
53
51
  kLocalAddress,
54
52
  kMaxResponseSize,
55
53
  kOnError,
@@ -59,7 +57,6 @@ const {
59
57
  } = require('../core/symbols.js')
60
58
  const connectH1 = require('./client-h1.js')
61
59
  const connectH2 = require('./client-h2.js')
62
- let deprecatedInterceptorWarned = false
63
60
 
64
61
  const kClosedResolve = Symbol('kClosedResolve')
65
62
 
@@ -77,7 +74,6 @@ class Client extends DispatcherBase {
77
74
  * @param {import('../../types/client.js').Client.Options} options
78
75
  */
79
76
  constructor (url, {
80
- interceptors,
81
77
  maxHeaderSize,
82
78
  headersTimeout,
83
79
  socketTimeout,
@@ -95,7 +91,6 @@ class Client extends DispatcherBase {
95
91
  tls,
96
92
  strictContentLength,
97
93
  maxCachedSessions,
98
- maxRedirections,
99
94
  connect,
100
95
  maxRequestsPerClient,
101
96
  localAddress,
@@ -106,8 +101,6 @@ class Client extends DispatcherBase {
106
101
  maxConcurrentStreams,
107
102
  allowH2
108
103
  } = {}) {
109
- super()
110
-
111
104
  if (keepAlive !== undefined) {
112
105
  throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
113
106
  }
@@ -164,10 +157,6 @@ class Client extends DispatcherBase {
164
157
  throw new InvalidArgumentError('connect must be a function or an object')
165
158
  }
166
159
 
167
- if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
168
- throw new InvalidArgumentError('maxRedirections must be a positive number')
169
- }
170
-
171
160
  if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
172
161
  throw new InvalidArgumentError('maxRequestsPerClient must be a positive number')
173
162
  }
@@ -196,6 +185,8 @@ class Client extends DispatcherBase {
196
185
  throw new InvalidArgumentError('maxConcurrentStreams must be a positive integer, greater than 0')
197
186
  }
198
187
 
188
+ super()
189
+
199
190
  if (typeof connect !== 'function') {
200
191
  connect = buildConnector({
201
192
  ...tls,
@@ -208,18 +199,6 @@ class Client extends DispatcherBase {
208
199
  })
209
200
  }
210
201
 
211
- if (interceptors?.Client && Array.isArray(interceptors.Client)) {
212
- this[kInterceptors] = interceptors.Client
213
- if (!deprecatedInterceptorWarned) {
214
- deprecatedInterceptorWarned = true
215
- process.emitWarning('Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.', {
216
- code: 'UNDICI-CLIENT-INTERCEPTOR-DEPRECATED'
217
- })
218
- }
219
- } else {
220
- this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })]
221
- }
222
-
223
202
  this[kUrl] = util.parseOrigin(url)
224
203
  this[kConnector] = connect
225
204
  this[kPipelining] = pipelining != null ? pipelining : 1
@@ -236,7 +215,6 @@ class Client extends DispatcherBase {
236
215
  this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3
237
216
  this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3
238
217
  this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
239
- this[kMaxRedirections] = maxRedirections
240
218
  this[kMaxRequests] = maxRequestsPerClient
241
219
  this[kClosedResolve] = null
242
220
  this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1
@@ -362,8 +340,6 @@ class Client extends DispatcherBase {
362
340
  }
363
341
  }
364
342
 
365
- const createRedirectInterceptor = require('../interceptor/redirect-interceptor.js')
366
-
367
343
  function onError (client, err) {
368
344
  if (
369
345
  client[kRunning] === 0 &&
@@ -398,7 +374,7 @@ async function connect (client) {
398
374
  assert(idx !== -1)
399
375
  const ip = hostname.substring(1, idx)
400
376
 
401
- assert(net.isIP(ip))
377
+ assert(net.isIPv6(ip))
402
378
  hostname = ip
403
379
  }
404
380
 
@@ -6,11 +6,10 @@ const {
6
6
  ClientClosedError,
7
7
  InvalidArgumentError
8
8
  } = require('../core/errors')
9
- const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = require('../core/symbols')
9
+ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch } = require('../core/symbols')
10
10
 
11
11
  const kOnDestroyed = Symbol('onDestroyed')
12
12
  const kOnClosed = Symbol('onClosed')
13
- const kInterceptedDispatch = Symbol('Intercepted Dispatch')
14
13
 
15
14
  class DispatcherBase extends Dispatcher {
16
15
  constructor () {
@@ -30,23 +29,6 @@ class DispatcherBase extends Dispatcher {
30
29
  return this[kClosed]
31
30
  }
32
31
 
33
- get interceptors () {
34
- return this[kInterceptors]
35
- }
36
-
37
- set interceptors (newInterceptors) {
38
- if (newInterceptors) {
39
- for (let i = newInterceptors.length - 1; i >= 0; i--) {
40
- const interceptor = this[kInterceptors][i]
41
- if (typeof interceptor !== 'function') {
42
- throw new InvalidArgumentError('interceptor must be an function')
43
- }
44
- }
45
- }
46
-
47
- this[kInterceptors] = newInterceptors
48
- }
49
-
50
32
  close (callback) {
51
33
  if (callback === undefined) {
52
34
  return new Promise((resolve, reject) => {
@@ -142,20 +124,6 @@ class DispatcherBase extends Dispatcher {
142
124
  })
143
125
  }
144
126
 
145
- [kInterceptedDispatch] (opts, handler) {
146
- if (!this[kInterceptors] || this[kInterceptors].length === 0) {
147
- this[kInterceptedDispatch] = this[kDispatch]
148
- return this[kDispatch](opts, handler)
149
- }
150
-
151
- let dispatch = this[kDispatch].bind(this)
152
- for (let i = this[kInterceptors].length - 1; i >= 0; i--) {
153
- dispatch = this[kInterceptors][i](dispatch)
154
- }
155
- this[kInterceptedDispatch] = dispatch
156
- return dispatch(opts, handler)
157
- }
158
-
159
127
  dispatch (opts, handler) {
160
128
  if (!handler || typeof handler !== 'object') {
161
129
  throw new InvalidArgumentError('handler must be an object')
@@ -174,7 +142,7 @@ class DispatcherBase extends Dispatcher {
174
142
  throw new ClientClosedError()
175
143
  }
176
144
 
177
- return this[kInterceptedDispatch](opts, handler)
145
+ return this[kDispatch](opts, handler)
178
146
  } catch (err) {
179
147
  if (typeof handler.onError !== 'function') {
180
148
  throw new InvalidArgumentError('invalid onError method')
@@ -35,30 +35,9 @@ class Dispatcher extends EventEmitter {
35
35
  }
36
36
  }
37
37
 
38
- return new ComposedDispatcher(this, dispatch)
39
- }
40
- }
41
-
42
- class ComposedDispatcher extends Dispatcher {
43
- #dispatcher = null
44
- #dispatch = null
45
-
46
- constructor (dispatcher, dispatch) {
47
- super()
48
- this.#dispatcher = dispatcher
49
- this.#dispatch = dispatch
50
- }
51
-
52
- dispatch (...args) {
53
- this.#dispatch(...args)
54
- }
55
-
56
- close (...args) {
57
- return this.#dispatcher.close(...args)
58
- }
59
-
60
- destroy (...args) {
61
- return this.#dispatcher.destroy(...args)
38
+ return new Proxy(this, {
39
+ get: (target, key) => key === 'dispatch' ? dispatch : target[key]
40
+ })
62
41
  }
63
42
  }
64
43
 
@@ -12,7 +12,7 @@ const {
12
12
  InvalidArgumentError
13
13
  } = require('../core/errors')
14
14
  const util = require('../core/util')
15
- const { kUrl, kInterceptors } = require('../core/symbols')
15
+ const { kUrl } = require('../core/symbols')
16
16
  const buildConnector = require('../core/connect')
17
17
 
18
18
  const kOptions = Symbol('options')
@@ -37,8 +37,6 @@ class Pool extends PoolBase {
37
37
  allowH2,
38
38
  ...options
39
39
  } = {}) {
40
- super()
41
-
42
40
  if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
43
41
  throw new InvalidArgumentError('invalid connections')
44
42
  }
@@ -51,6 +49,8 @@ class Pool extends PoolBase {
51
49
  throw new InvalidArgumentError('connect must be a function or an object')
52
50
  }
53
51
 
52
+ super()
53
+
54
54
  if (typeof connect !== 'function') {
55
55
  connect = buildConnector({
56
56
  ...tls,
@@ -63,9 +63,6 @@ class Pool extends PoolBase {
63
63
  })
64
64
  }
65
65
 
66
- this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
67
- ? options.interceptors.Pool
68
- : []
69
66
  this[kConnections] = connections || null
70
67
  this[kUrl] = util.parseOrigin(origin)
71
68
  this[kOptions] = { ...util.deepClone(options), connect, allowH2 }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { kProxy, kClose, kDestroy, kInterceptors } = require('../core/symbols')
3
+ const { kProxy, kClose, kDestroy } = require('../core/symbols')
4
4
  const { URL } = require('node:url')
5
5
  const Agent = require('./agent')
6
6
  const Pool = require('./pool')
@@ -25,8 +25,6 @@ function defaultFactory (origin, opts) {
25
25
 
26
26
  class ProxyAgent extends DispatcherBase {
27
27
  constructor (opts) {
28
- super()
29
-
30
28
  if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
31
29
  throw new InvalidArgumentError('Proxy uri is mandatory')
32
30
  }
@@ -36,13 +34,12 @@ class ProxyAgent extends DispatcherBase {
36
34
  throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
37
35
  }
38
36
 
37
+ super()
38
+
39
39
  const url = this.#getUrl(opts)
40
40
  const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url
41
41
 
42
42
  this[kProxy] = { uri: href, protocol }
43
- this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent)
44
- ? opts.interceptors.ProxyAgent
45
- : []
46
43
  this[kRequestTls] = opts.requestTls
47
44
  this[kProxyTls] = opts.proxyTls
48
45
  this[kProxyHeaders] = opts.headers || {}
@@ -1,7 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ const assert = require('node:assert')
4
+
3
5
  module.exports = class DecoratorHandler {
4
6
  #handler
7
+ #onCompleteCalled = false
8
+ #onErrorCalled = false
5
9
 
6
10
  constructor (handler) {
7
11
  if (typeof handler !== 'object' || handler === null) {
@@ -15,30 +19,50 @@ module.exports = class DecoratorHandler {
15
19
  }
16
20
 
17
21
  onError (...args) {
22
+ this.#onErrorCalled = true
18
23
  return this.#handler.onError?.(...args)
19
24
  }
20
25
 
21
26
  onUpgrade (...args) {
27
+ assert(!this.#onCompleteCalled)
28
+ assert(!this.#onErrorCalled)
29
+
22
30
  return this.#handler.onUpgrade?.(...args)
23
31
  }
24
32
 
25
33
  onResponseStarted (...args) {
34
+ assert(!this.#onCompleteCalled)
35
+ assert(!this.#onErrorCalled)
36
+
26
37
  return this.#handler.onResponseStarted?.(...args)
27
38
  }
28
39
 
29
40
  onHeaders (...args) {
41
+ assert(!this.#onCompleteCalled)
42
+ assert(!this.#onErrorCalled)
43
+
30
44
  return this.#handler.onHeaders?.(...args)
31
45
  }
32
46
 
33
47
  onData (...args) {
48
+ assert(!this.#onCompleteCalled)
49
+ assert(!this.#onErrorCalled)
50
+
34
51
  return this.#handler.onData?.(...args)
35
52
  }
36
53
 
37
54
  onComplete (...args) {
55
+ assert(!this.#onCompleteCalled)
56
+ assert(!this.#onErrorCalled)
57
+
58
+ this.#onCompleteCalled = true
38
59
  return this.#handler.onComplete?.(...args)
39
60
  }
40
61
 
41
62
  onBodySent (...args) {
63
+ assert(!this.#onCompleteCalled)
64
+ assert(!this.#onErrorCalled)
65
+
42
66
  return this.#handler.onBodySent?.(...args)
43
67
  }
44
68
  }
@@ -24,6 +24,15 @@ class BodyAsyncIterable {
24
24
  }
25
25
 
26
26
  class RedirectHandler {
27
+ static buildDispatch (dispatcher, maxRedirections) {
28
+ if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
29
+ throw new InvalidArgumentError('maxRedirections must be a positive number')
30
+ }
31
+
32
+ const dispatch = dispatcher.dispatch.bind(dispatcher)
33
+ return (opts, originalHandler) => dispatch(opts, new RedirectHandler(dispatch, maxRedirections, opts, originalHandler))
34
+ }
35
+
27
36
  constructor (dispatch, maxRedirections, opts, handler) {
28
37
  if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
29
38
  throw new InvalidArgumentError('maxRedirections must be a positive number')
@@ -192,8 +192,18 @@ class RetryHandler {
192
192
  if (this.resume != null) {
193
193
  this.resume = null
194
194
 
195
- if (statusCode !== 206) {
196
- return true
195
+ // Only Partial Content 206 supposed to provide Content-Range,
196
+ // any other status code that partially consumed the payload
197
+ // should not be retry because it would result in downstream
198
+ // wrongly concatanete multiple responses.
199
+ if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
200
+ this.abort(
201
+ new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
202
+ headers,
203
+ data: { count: this.retryCount }
204
+ })
205
+ )
206
+ return false
197
207
  }
198
208
 
199
209
  const contentRange = parseRangeHeader(headers['content-range'])
@@ -271,7 +281,11 @@ class RetryHandler {
271
281
  // Weak etags are not useful for comparison nor cache
272
282
  // for instance not safe to assume if the response is byte-per-byte
273
283
  // equal
274
- if (this.etag != null && this.etag.startsWith('W/')) {
284
+ if (
285
+ this.etag != null &&
286
+ this.etag[0] === 'W' &&
287
+ this.etag[1] === '/'
288
+ ) {
275
289
  this.etag = null
276
290
  }
277
291
 
@@ -329,6 +343,11 @@ class RetryHandler {
329
343
  onRetry.bind(this)
330
344
  )
331
345
 
346
+ /**
347
+ * @this {RetryHandler}
348
+ * @param {Error} [err]
349
+ * @returns
350
+ */
332
351
  function onRetry (err) {
333
352
  if (err != null || this.aborted || isDisturbed(this.opts.body)) {
334
353
  return this.handler.onError(err)
@@ -14,12 +14,12 @@ class DumpHandler extends DecoratorHandler {
14
14
  #handler = null
15
15
 
16
16
  constructor ({ maxSize }, handler) {
17
- super(handler)
18
-
19
17
  if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
20
18
  throw new InvalidArgumentError('maxSize must be a number greater than 0')
21
19
  }
22
20
 
21
+ super(handler)
22
+
23
23
  this.#maxSize = maxSize ?? this.#maxSize
24
24
  this.#handler = handler
25
25
  }
@@ -1,24 +1,21 @@
1
1
  'use strict'
2
+
2
3
  const RedirectHandler = require('../handler/redirect-handler')
3
4
 
4
- module.exports = opts => {
5
- const globalMaxRedirections = opts?.maxRedirections
6
- return dispatch => {
7
- return function redirectInterceptor (opts, handler) {
8
- const { maxRedirections = globalMaxRedirections, ...baseOpts } = opts
5
+ function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections } = {}) {
6
+ return (dispatch) => {
7
+ return function Intercept (opts, handler) {
8
+ const { maxRedirections = defaultMaxRedirections, ...rest } = opts
9
9
 
10
- if (!maxRedirections) {
10
+ if (maxRedirections == null || maxRedirections === 0) {
11
11
  return dispatch(opts, handler)
12
12
  }
13
13
 
14
- const redirectHandler = new RedirectHandler(
15
- dispatch,
16
- maxRedirections,
17
- opts,
18
- handler
19
- )
20
-
21
- return dispatch(baseOpts, redirectHandler)
14
+ const dispatchOpts = { ...rest, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
15
+ const redirectHandler = new RedirectHandler(dispatch, maxRedirections, dispatchOpts, handler)
16
+ return dispatch(dispatchOpts, redirectHandler)
22
17
  }
23
18
  }
24
19
  }
20
+
21
+ module.exports = createRedirectInterceptor