undici 7.2.0 → 7.2.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.
@@ -40,7 +40,7 @@ diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) =>
40
40
 
41
41
  ## `undici:request:headers`
42
42
 
43
- This message is published after the response headers have been received, i.e. the response has been completed.
43
+ This message is published after the response headers have been received.
44
44
 
45
45
  ```js
46
46
  import diagnosticsChannel from 'diagnostics_channel'
@@ -652,7 +652,7 @@ return null
652
652
 
653
653
  A faster version of `Dispatcher.request`. This method expects the second argument `factory` to return a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream which the response will be written to. This improves performance by avoiding creating an intermediate [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) stream when the user expects to directly pipe the response body to a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream.
654
654
 
655
- As demonstrated in [Example 1 - Basic GET stream request](/docs/docs/api/Dispatcher.md#example-1---basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](/docs/docs/api/Dispatch.md#example-2---stream-to-fastify-response) for more details.
655
+ As demonstrated in [Example 1 - Basic GET stream request](/docs/docs/api/Dispatcher.md#example-1-basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](/docs/docs/api/Dispatch.md#example-2-stream-to-fastify-response) for more details.
656
656
 
657
657
  Arguments:
658
658
 
@@ -133,7 +133,7 @@ class RetryHandler {
133
133
  ? Math.min(retryAfterHeader, maxTimeout)
134
134
  : Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
135
135
 
136
- setTimeout(() => cb(null), retryTimeout).unref()
136
+ setTimeout(() => cb(null), retryTimeout)
137
137
  }
138
138
 
139
139
  onResponseStart (controller, statusCode, headers, statusMessage) {
@@ -277,7 +277,7 @@ class RetryHandler {
277
277
  }
278
278
 
279
279
  onResponseError (controller, err) {
280
- if (!controller || controller.aborted || isDisturbed(this.opts.body)) {
280
+ if (controller?.aborted || isDisturbed(this.opts.body)) {
281
281
  this.handler.onResponseError?.(controller, err)
282
282
  return
283
283
  }
@@ -353,7 +353,7 @@ module.exports = interceptorOpts => {
353
353
 
354
354
  instance.runLookup(origin, origDispatchOpts, (err, newOrigin) => {
355
355
  if (err) {
356
- return handler.onError(err)
356
+ return handler.onResponseError(null, err)
357
357
  }
358
358
 
359
359
  let dispatchOpts = null
@@ -24,6 +24,7 @@ const { PerMessageDeflate } = require('./permessage-deflate')
24
24
 
25
25
  class ByteParser extends Writable {
26
26
  #buffers = []
27
+ #fragmentsBytes = 0
27
28
  #byteOffset = 0
28
29
  #loop = false
29
30
 
@@ -208,16 +209,14 @@ class ByteParser extends Writable {
208
209
  this.#state = parserStates.INFO
209
210
  } else {
210
211
  if (!this.#info.compressed) {
211
- this.#fragments.push(body)
212
+ this.writeFragments(body)
212
213
 
213
214
  // If the frame is not fragmented, a message has been received.
214
215
  // If the frame is fragmented, it will terminate with a fin bit set
215
216
  // and an opcode of 0 (continuation), therefore we handle that when
216
217
  // parsing continuation frames, not here.
217
218
  if (!this.#info.fragmented && this.#info.fin) {
218
- const fullMessage = Buffer.concat(this.#fragments)
219
- websocketMessageReceived(this.#handler, this.#info.binaryType, fullMessage)
220
- this.#fragments.length = 0
219
+ websocketMessageReceived(this.#handler, this.#info.binaryType, this.consumeFragments())
221
220
  }
222
221
 
223
222
  this.#state = parserStates.INFO
@@ -228,7 +227,7 @@ class ByteParser extends Writable {
228
227
  return
229
228
  }
230
229
 
231
- this.#fragments.push(data)
230
+ this.writeFragments(data)
232
231
 
233
232
  if (!this.#info.fin) {
234
233
  this.#state = parserStates.INFO
@@ -237,11 +236,10 @@ class ByteParser extends Writable {
237
236
  return
238
237
  }
239
238
 
240
- websocketMessageReceived(this.#handler, this.#info.binaryType, Buffer.concat(this.#fragments))
239
+ websocketMessageReceived(this.#handler, this.#info.binaryType, this.consumeFragments())
241
240
 
242
241
  this.#loop = true
243
242
  this.#state = parserStates.INFO
244
- this.#fragments.length = 0
245
243
  this.run(callback)
246
244
  })
247
245
 
@@ -265,34 +263,70 @@ class ByteParser extends Writable {
265
263
  return emptyBuffer
266
264
  }
267
265
 
268
- if (this.#buffers[0].length === n) {
269
- this.#byteOffset -= this.#buffers[0].length
266
+ this.#byteOffset -= n
267
+
268
+ const first = this.#buffers[0]
269
+
270
+ if (first.length > n) {
271
+ // replace with remaining buffer
272
+ this.#buffers[0] = first.subarray(n, first.length)
273
+ return first.subarray(0, n)
274
+ } else if (first.length === n) {
275
+ // prefect match
270
276
  return this.#buffers.shift()
277
+ } else {
278
+ let offset = 0
279
+ // If Buffer.allocUnsafe is used, extra copies will be made because the offset is non-zero.
280
+ const buffer = Buffer.allocUnsafeSlow(n)
281
+ while (offset !== n) {
282
+ const next = this.#buffers[0]
283
+ const length = next.length
284
+
285
+ if (length + offset === n) {
286
+ buffer.set(this.#buffers.shift(), offset)
287
+ break
288
+ } else if (length + offset > n) {
289
+ buffer.set(next.subarray(0, n - offset), offset)
290
+ this.#buffers[0] = next.subarray(n - offset)
291
+ break
292
+ } else {
293
+ buffer.set(this.#buffers.shift(), offset)
294
+ offset += length
295
+ }
296
+ }
297
+
298
+ return buffer
299
+ }
300
+ }
301
+
302
+ writeFragments (fragment) {
303
+ this.#fragmentsBytes += fragment.length
304
+ this.#fragments.push(fragment)
305
+ }
306
+
307
+ consumeFragments () {
308
+ const fragments = this.#fragments
309
+
310
+ if (fragments.length === 1) {
311
+ // single fragment
312
+ this.#fragmentsBytes = 0
313
+ return fragments.shift()
271
314
  }
272
315
 
273
- const buffer = Buffer.allocUnsafe(n)
274
316
  let offset = 0
317
+ // If Buffer.allocUnsafe is used, extra copies will be made because the offset is non-zero.
318
+ const output = Buffer.allocUnsafeSlow(this.#fragmentsBytes)
275
319
 
276
- while (offset !== n) {
277
- const next = this.#buffers[0]
278
- const { length } = next
279
-
280
- if (length + offset === n) {
281
- buffer.set(this.#buffers.shift(), offset)
282
- break
283
- } else if (length + offset > n) {
284
- buffer.set(next.subarray(0, n - offset), offset)
285
- this.#buffers[0] = next.subarray(n - offset)
286
- break
287
- } else {
288
- buffer.set(this.#buffers.shift(), offset)
289
- offset += next.length
290
- }
320
+ for (let i = 0; i < fragments.length; ++i) {
321
+ const buffer = fragments[i]
322
+ output.set(buffer, offset)
323
+ offset += buffer.length
291
324
  }
292
325
 
293
- this.#byteOffset -= n
326
+ this.#fragments = []
327
+ this.#fragmentsBytes = 0
294
328
 
295
- return buffer
329
+ return output
296
330
  }
297
331
 
298
332
  parseCloseBody (data) {
@@ -87,7 +87,7 @@ function toArrayBuffer (buffer) {
87
87
  if (buffer.byteLength === buffer.buffer.byteLength) {
88
88
  return buffer.buffer
89
89
  }
90
- return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
90
+ return new Uint8Array(buffer).buffer
91
91
  }
92
92
 
93
93
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.2.0",
3
+ "version": "7.2.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -107,7 +107,7 @@
107
107
  "prepare": "husky && node ./scripts/platform-shell.js"
108
108
  },
109
109
  "devDependencies": {
110
- "@fastify/busboy": "3.1.0",
110
+ "@fastify/busboy": "3.1.1",
111
111
  "@matteo.collina/tspl": "^0.1.1",
112
112
  "@sinonjs/fake-timers": "^12.0.0",
113
113
  "@types/node": "^18.19.50",
package/types/errors.d.ts CHANGED
@@ -33,6 +33,22 @@ declare namespace Errors {
33
33
  code: 'UND_ERR_BODY_TIMEOUT'
34
34
  }
35
35
 
36
+ export class ResponseError extends UndiciError {
37
+ constructor (
38
+ message: string,
39
+ code: number,
40
+ options: {
41
+ headers?: IncomingHttpHeaders | string[] | null,
42
+ body?: null | Record<string, any> | string
43
+ }
44
+ )
45
+ name: 'ResponseError'
46
+ code: 'UND_ERR_RESPONSE'
47
+ statusCode: number
48
+ body: null | Record<string, any> | string
49
+ headers: IncomingHttpHeaders | string[] | null
50
+ }
51
+
36
52
  export class ResponseStatusCodeError extends UndiciError {
37
53
  constructor (
38
54
  message?: string,