undici 8.11.0 → 8.11.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.
- package/README.md +10 -1
- package/docs/docs/api/Dispatcher.md +9 -0
- package/docs/docs/api/Fetch.md +10 -0
- package/docs/docs/index.md +10 -1
- package/lib/dispatcher/dispatcher1-wrapper.js +2 -3
- package/lib/web/fetch/index.js +8 -11
- package/lib/web/websocket/connection.js +16 -5
- package/lib/web/websocket/receiver.js +0 -3
- package/lib/web/websocket/stream/websocketstream.js +19 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -378,7 +378,16 @@ The `body` mixins are the most common way to format the request/response body. M
|
|
|
378
378
|
> The body returned from `undici.request` does not implement `.formData()`.
|
|
379
379
|
|
|
380
380
|
> [!WARNING]
|
|
381
|
-
>
|
|
381
|
+
> The body mixins `.arrayBuffer()`, `.blob()`, `.bytes()`, `.json()`, `.text()`,
|
|
382
|
+
> and `.formData()` buffer the entire body in memory before returning. Where
|
|
383
|
+
> applicable, they also decode or parse the payload and retain that
|
|
384
|
+
> representation in memory. Calling these methods therefore means trusting that
|
|
385
|
+
> the response body is small enough to fit in the available memory. Do not use
|
|
386
|
+
> them for responses from untrusted or user-controlled sources. Instead, consume
|
|
387
|
+
> the response body as a stream and enforce an application-specific size limit:
|
|
388
|
+
> use `response.body` for fetch responses or the `body` returned by
|
|
389
|
+
> `undici.request()`. For streaming decoded text, use `body.textStream()` on a
|
|
390
|
+
> fetch `Request` or `Response`.
|
|
382
391
|
|
|
383
392
|
Example usage:
|
|
384
393
|
|
|
@@ -466,6 +466,15 @@ example, calling `text()` after `json()` throws a `TypeError`. The body also
|
|
|
466
466
|
provides `dump({ limit })`, which discards up to `limit` bytes (default
|
|
467
467
|
`131072`) without destroying the socket.
|
|
468
468
|
|
|
469
|
+
> [!WARNING]
|
|
470
|
+
> The `arrayBuffer()`, `blob()`, `bytes()`, `json()`, and `text()` methods buffer
|
|
471
|
+
> the entire body in memory before returning. Where applicable, they also decode
|
|
472
|
+
> or parse the payload and retain that representation in memory. Calling these
|
|
473
|
+
> methods therefore means trusting that the body is small enough to fit in the
|
|
474
|
+
> available memory. Do not use them for bodies received from untrusted or
|
|
475
|
+
> user-controlled sources. Instead, process `body` as a `Readable` stream and
|
|
476
|
+
> enforce an application-specific size limit.
|
|
477
|
+
|
|
469
478
|
The body is always a `Readable`, even when empty. Deserializing an empty body
|
|
470
479
|
with `json()` throws. To guard against this, verify the status code is not `204`
|
|
471
480
|
and the `content-type` header starts with `application/json` before calling
|
package/docs/docs/api/Fetch.md
CHANGED
|
@@ -596,6 +596,16 @@ properties for reading a body. Each consuming method reads the body once; after
|
|
|
596
596
|
the body has been consumed, [`bodyUsed`](#bodybodyused) becomes `true` and
|
|
597
597
|
calling another consuming method throws a `TypeError`.
|
|
598
598
|
|
|
599
|
+
> [!WARNING]
|
|
600
|
+
> The `arrayBuffer()`, `blob()`, `bytes()`, `formData()`, `json()`, and `text()`
|
|
601
|
+
> methods buffer the entire body in memory before returning. Where applicable,
|
|
602
|
+
> they also decode or parse the payload and retain that representation in
|
|
603
|
+
> memory. Calling these methods therefore means trusting that the body is small
|
|
604
|
+
> enough to fit in the available memory. Do not use them for bodies received
|
|
605
|
+
> from untrusted or user-controlled sources. Instead, process `body.body` or
|
|
606
|
+
> `body.textStream()` incrementally and enforce an application-specific size
|
|
607
|
+
> limit.
|
|
608
|
+
|
|
599
609
|
### `body.arrayBuffer()`
|
|
600
610
|
|
|
601
611
|
<!-- YAML
|
package/docs/docs/index.md
CHANGED
|
@@ -380,7 +380,16 @@ The `body` mixins are the most common way to format the request/response body. M
|
|
|
380
380
|
> The body returned from `undici.request` does not implement `.formData()`.
|
|
381
381
|
|
|
382
382
|
> [!WARNING]
|
|
383
|
-
>
|
|
383
|
+
> The body mixins `.arrayBuffer()`, `.blob()`, `.bytes()`, `.json()`, `.text()`,
|
|
384
|
+
> and `.formData()` buffer the entire body in memory before returning. Where
|
|
385
|
+
> applicable, they also decode or parse the payload and retain that
|
|
386
|
+
> representation in memory. Calling these methods therefore means trusting that
|
|
387
|
+
> the response body is small enough to fit in the available memory. Do not use
|
|
388
|
+
> them for responses from untrusted or user-controlled sources. Instead, consume
|
|
389
|
+
> the response body as a stream and enforce an application-specific size limit:
|
|
390
|
+
> use `response.body` for fetch responses or the `body` returned by
|
|
391
|
+
> `undici.request()`. For streaming decoded text, use `body.textStream()` on a
|
|
392
|
+
> fetch `Request` or `Response`.
|
|
384
393
|
|
|
385
394
|
Example usage:
|
|
386
395
|
|
|
@@ -89,10 +89,9 @@ class Dispatcher1Wrapper extends Dispatcher {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
dispatch (opts, handler) {
|
|
92
|
-
// Legacy (v1)
|
|
93
|
-
// when HTTP/2 Extended CONNECT is unavailable.
|
|
92
|
+
// Legacy (v1) consumers do not support HTTP/2, so force HTTP/1.1.
|
|
94
93
|
// See https://github.com/nodejs/undici/issues/4989
|
|
95
|
-
if (opts.
|
|
94
|
+
if (opts.allowH2 !== false) {
|
|
96
95
|
opts = { ...opts, allowH2: false }
|
|
97
96
|
}
|
|
98
97
|
|
package/lib/web/fetch/index.js
CHANGED
|
@@ -812,11 +812,8 @@ async function mainFetch (fetchParams, recursive) {
|
|
|
812
812
|
// https://fetch.spec.whatwg.org/#concept-scheme-fetch
|
|
813
813
|
// given a fetch params fetchParams
|
|
814
814
|
function schemeFetch (fetchParams) {
|
|
815
|
-
// Note: since the connection is destroyed on redirect, which sets fetchParams to a
|
|
816
|
-
// cancelled state, we do not want this condition to trigger *unless* there have been
|
|
817
|
-
// no redirects. See https://github.com/nodejs/undici/issues/1776
|
|
818
815
|
// 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
|
|
819
|
-
if (isCancelled(fetchParams)
|
|
816
|
+
if (isCancelled(fetchParams)) {
|
|
820
817
|
return Promise.resolve(makeAppropriateNetworkError(fetchParams))
|
|
821
818
|
}
|
|
822
819
|
|
|
@@ -1243,7 +1240,7 @@ async function httpFetch (fetchParams) {
|
|
|
1243
1240
|
// encouraged to, transmit an RST_STREAM frame.
|
|
1244
1241
|
// See, https://github.com/whatwg/fetch/issues/1288
|
|
1245
1242
|
if (request.redirect !== 'manual') {
|
|
1246
|
-
fetchParams.controller.connection.destroy(
|
|
1243
|
+
fetchParams.controller.connection.destroy()
|
|
1247
1244
|
}
|
|
1248
1245
|
|
|
1249
1246
|
// 2. Switch on request’s redirect mode:
|
|
@@ -1852,12 +1849,10 @@ async function httpNetworkFetch (
|
|
|
1852
1849
|
fetchParams.controller.connection = {
|
|
1853
1850
|
abort: null,
|
|
1854
1851
|
destroyed: false,
|
|
1855
|
-
destroy (err
|
|
1852
|
+
destroy (err) {
|
|
1856
1853
|
if (!this.destroyed) {
|
|
1857
1854
|
this.destroyed = true
|
|
1858
|
-
|
|
1859
|
-
this.abort?.(err ?? new DOMException('The operation was aborted.', 'AbortError'))
|
|
1860
|
-
}
|
|
1855
|
+
this.abort?.(err ?? new DOMException('The operation was aborted.', 'AbortError'))
|
|
1861
1856
|
}
|
|
1862
1857
|
}
|
|
1863
1858
|
}
|
|
@@ -2393,7 +2388,7 @@ async function httpNetworkFetch (
|
|
|
2393
2388
|
this.body?.push(null)
|
|
2394
2389
|
},
|
|
2395
2390
|
|
|
2396
|
-
onResponseError (
|
|
2391
|
+
onResponseError (controller, error) {
|
|
2397
2392
|
if (this.abort) {
|
|
2398
2393
|
fetchParams.controller.off('terminated', this.abort)
|
|
2399
2394
|
}
|
|
@@ -2412,7 +2407,9 @@ async function httpNetworkFetch (
|
|
|
2412
2407
|
|
|
2413
2408
|
this.body?.destroy(error)
|
|
2414
2409
|
|
|
2415
|
-
|
|
2410
|
+
if (!controller?.aborted) {
|
|
2411
|
+
fetchParams.controller.terminate(error)
|
|
2412
|
+
}
|
|
2416
2413
|
|
|
2417
2414
|
reject(error)
|
|
2418
2415
|
},
|
|
@@ -159,11 +159,15 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
159
159
|
// E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
|
|
160
160
|
// trailing whitespace, the client MUST _Fail the WebSocket
|
|
161
161
|
// Connection_.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
// For H2, implementations "do not do the processing of the Sec-WebSocket-Key and
|
|
163
|
+
// Sec-WebSocket-Accept header fields". https://datatracker.ietf.org/doc/html/rfc8441#section-5
|
|
164
|
+
if (response.socket.session == null) {
|
|
165
|
+
const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
|
|
166
|
+
const digest = crypto.hash('sha1', keyValue + uid, 'base64')
|
|
167
|
+
if (secWSAccept !== digest) {
|
|
168
|
+
failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
|
|
169
|
+
return
|
|
170
|
+
}
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
// 5. If the response includes a |Sec-WebSocket-Extensions| header
|
|
@@ -206,6 +210,13 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
206
210
|
}
|
|
207
211
|
}
|
|
208
212
|
|
|
213
|
+
// For H2, "Orderly TCP-level closures are represented as END_STREAM flags", so
|
|
214
|
+
// end our side of the stream when the server ends its side, as a TCP socket would.
|
|
215
|
+
// https://datatracker.ietf.org/doc/html/rfc8441#section-5
|
|
216
|
+
if (response.socket.session != null) {
|
|
217
|
+
response.socket.allowHalfOpen = false
|
|
218
|
+
}
|
|
219
|
+
|
|
209
220
|
response.socket.on('data', handler.onSocketData)
|
|
210
221
|
response.socket.on('close', handler.onSocketClose)
|
|
211
222
|
response.socket.on('error', handler.onSocketError)
|
|
@@ -369,9 +369,6 @@ class ByteParser extends Writable {
|
|
|
369
369
|
consumeFragments () {
|
|
370
370
|
const fragments = this.#fragments
|
|
371
371
|
|
|
372
|
-
// The compressed flag describes the message that is being completed here.
|
|
373
|
-
// It must not survive into the next message, otherwise a stray
|
|
374
|
-
// continuation frame is accepted instead of failing the connection.
|
|
375
372
|
this.#info.compressed = false
|
|
376
373
|
|
|
377
374
|
if (fragments.length === 1) {
|
|
@@ -62,7 +62,9 @@ class WebSocketStream {
|
|
|
62
62
|
|
|
63
63
|
this.#handler.socket.destroy()
|
|
64
64
|
},
|
|
65
|
-
|
|
65
|
+
// When the WebSocket connection is closed for a WebSocketStream stream, possibly cleanly, the user agent must
|
|
66
|
+
// queue a global task on the WebSocket task source given stream ’s relevant global object to run the following substeps:
|
|
67
|
+
onSocketClose: () => queueMicrotask(() => this.#onSocketClose()),
|
|
66
68
|
onPing: () => {},
|
|
67
69
|
onPong: () => {},
|
|
68
70
|
|
|
@@ -246,11 +248,15 @@ class WebSocketStream {
|
|
|
246
248
|
const frame = new WebsocketFrameSend(data)
|
|
247
249
|
|
|
248
250
|
this.#handler.socket.write(frame.createFrame(opcode), () => {
|
|
251
|
+
// 6.3. Queue a global task on the WebSocket task source given stream ’s relevant global object to resolve promise with undefined.
|
|
249
252
|
promise.resolve(undefined)
|
|
250
253
|
})
|
|
254
|
+
} else {
|
|
255
|
+
// 6.3. Queue a global task on the WebSocket task source given stream ’s relevant global object to resolve promise with undefined.
|
|
256
|
+
promise.resolve(undefined)
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
//
|
|
259
|
+
// 7. Return promise.
|
|
254
260
|
return promise.promise
|
|
255
261
|
}
|
|
256
262
|
|
|
@@ -278,7 +284,7 @@ class WebSocketStream {
|
|
|
278
284
|
// This is done in the opening handshake.
|
|
279
285
|
|
|
280
286
|
// 3. Let extensions be the extensions in use .
|
|
281
|
-
const extensions =
|
|
287
|
+
const extensions = response.headersList.get('sec-websocket-extensions') ?? ''
|
|
282
288
|
|
|
283
289
|
// 4. Let protocol be the subprotocol in use .
|
|
284
290
|
const protocol = response.headersList.get('sec-websocket-protocol') ?? ''
|
|
@@ -291,6 +297,7 @@ class WebSocketStream {
|
|
|
291
297
|
start: (controller) => {
|
|
292
298
|
this.#readableStreamController = controller
|
|
293
299
|
},
|
|
300
|
+
pull: () => this.#pull(),
|
|
294
301
|
cancel: (reason) => this.#cancel(reason)
|
|
295
302
|
})
|
|
296
303
|
|
|
@@ -350,6 +357,9 @@ class WebSocketStream {
|
|
|
350
357
|
this.#readableStreamController.enqueue(chunk)
|
|
351
358
|
|
|
352
359
|
// 4. Apply backpressure to the WebSocket.
|
|
360
|
+
if (this.#readableStreamController.desiredSize <= 0) {
|
|
361
|
+
this.#handler.socket.pause()
|
|
362
|
+
}
|
|
353
363
|
}
|
|
354
364
|
|
|
355
365
|
/** @type {import('../websocket').Handler['onSocketClose']} */
|
|
@@ -383,7 +393,7 @@ class WebSocketStream {
|
|
|
383
393
|
// 1006.
|
|
384
394
|
let code = result?.code ?? 1005
|
|
385
395
|
|
|
386
|
-
if (!this.#handler.closeState.has(sentCloseFrameState.
|
|
396
|
+
if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
|
387
397
|
code = 1006
|
|
388
398
|
}
|
|
389
399
|
|
|
@@ -441,6 +451,11 @@ class WebSocketStream {
|
|
|
441
451
|
closeWebSocketConnection(this.#handler, code, reasonString)
|
|
442
452
|
}
|
|
443
453
|
|
|
454
|
+
// To pull bytes from a WebSocketStream stream , if stream is currently applying backpressure, release backpressure.
|
|
455
|
+
#pull () {
|
|
456
|
+
this.#handler.socket.resume()
|
|
457
|
+
}
|
|
458
|
+
|
|
444
459
|
// To cancel a WebSocketStream stream given reason , close using reason giving stream and reason .
|
|
445
460
|
#cancel (reason) {
|
|
446
461
|
this.#closeUsingReason(reason)
|