undici 6.18.1 → 6.19.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.
- package/docs/docs/api/Client.md +1 -1
- package/docs/docs/api/RetryHandler.md +3 -0
- package/lib/core/connect.js +2 -2
- package/lib/core/request.js +4 -3
- package/lib/core/symbols.js +1 -0
- package/lib/core/util.js +84 -3
- package/lib/dispatcher/client-h1.js +10 -10
- package/lib/dispatcher/client-h2.js +38 -40
- package/lib/handler/retry-handler.js +13 -6
- package/lib/web/cookies/index.js +3 -4
- package/lib/web/cookies/util.js +1 -28
- package/lib/web/fetch/headers.js +0 -8
- package/lib/web/fetch/request.js +9 -7
- package/lib/web/fetch/util.js +2 -28
- package/lib/web/websocket/websocket.js +0 -9
- package/package.json +3 -3
- package/types/errors.d.ts +21 -0
- package/types/index.d.ts +2 -1
package/docs/docs/api/Client.md
CHANGED
|
@@ -19,7 +19,7 @@ Returns: `Client`
|
|
|
19
19
|
|
|
20
20
|
> ⚠️ Warning: The `H2` support is experimental.
|
|
21
21
|
|
|
22
|
-
* **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
|
|
22
|
+
* **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds. Please note the `timeout` will be reset if you keep writing data to the scoket everytime.
|
|
23
23
|
* **headersTimeout** `number | null` (optional) - Default: `300e3` - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
|
|
24
24
|
* **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout`, in milliseconds, when overridden by *keep-alive* hints from the server. Defaults to 10 minutes.
|
|
25
25
|
* **keepAliveTimeout** `number | null` (optional) - Default: `4e3` - The timeout, in milliseconds, after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overridden by *keep-alive* hints from the server. See [MDN: HTTP - Headers - Keep-Alive directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive#directives) for more details. Defaults to 4 seconds.
|
|
@@ -46,6 +46,9 @@ It represents the retry state for a given request.
|
|
|
46
46
|
- **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every retry.
|
|
47
47
|
- **handler** Extends [`Dispatch.DispatchHandlers`](Dispatcher.md#dispatcherdispatchoptions-handler) (required) - Handler function to be called after the request is successful or the retries are exhausted.
|
|
48
48
|
|
|
49
|
+
>__Note__: The `RetryHandler` does not retry over stateful bodies (e.g. streams, AsyncIterable) as those, once consumed, are left in an state that cannot be reutilized. For these situations the `RetryHandler` will identify
|
|
50
|
+
>the body as stateful and will not retry the request rejecting with the error `UND_ERR_REQ_RETRY`.
|
|
51
|
+
|
|
49
52
|
Examples:
|
|
50
53
|
|
|
51
54
|
```js
|
package/lib/core/connect.js
CHANGED
|
@@ -73,7 +73,7 @@ if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) {
|
|
76
|
+
function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) {
|
|
77
77
|
if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
|
|
78
78
|
throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
|
|
79
79
|
}
|
|
@@ -91,7 +91,7 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
|
|
|
91
91
|
servername = servername || options.servername || util.getServerName(host) || null
|
|
92
92
|
|
|
93
93
|
const sessionKey = servername || hostname
|
|
94
|
-
const session = sessionCache.get(sessionKey) || null
|
|
94
|
+
const session = customSession || sessionCache.get(sessionKey) || null
|
|
95
95
|
|
|
96
96
|
assert(sessionKey)
|
|
97
97
|
|
package/lib/core/request.js
CHANGED
|
@@ -16,7 +16,8 @@ const {
|
|
|
16
16
|
isBlobLike,
|
|
17
17
|
buildURL,
|
|
18
18
|
validateHandler,
|
|
19
|
-
getServerName
|
|
19
|
+
getServerName,
|
|
20
|
+
normalizedMethodRecords
|
|
20
21
|
} = require('./util')
|
|
21
22
|
const { channels } = require('./diagnostics.js')
|
|
22
23
|
const { headerNameLowerCasedRecord } = require('./constants')
|
|
@@ -51,13 +52,13 @@ class Request {
|
|
|
51
52
|
method !== 'CONNECT'
|
|
52
53
|
) {
|
|
53
54
|
throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
|
|
54
|
-
} else if (invalidPathRegex.
|
|
55
|
+
} else if (invalidPathRegex.test(path)) {
|
|
55
56
|
throw new InvalidArgumentError('invalid request path')
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
if (typeof method !== 'string') {
|
|
59
60
|
throw new InvalidArgumentError('method must be a string')
|
|
60
|
-
} else if (!isValidHTTPToken(method)) {
|
|
61
|
+
} else if (normalizedMethodRecords[method] === undefined && !isValidHTTPToken(method)) {
|
|
61
62
|
throw new InvalidArgumentError('invalid request method')
|
|
62
63
|
}
|
|
63
64
|
|
package/lib/core/symbols.js
CHANGED
package/lib/core/util.js
CHANGED
|
@@ -1,19 +1,72 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
-
const { kDestroyed, kBodyUsed, kListeners } = require('./symbols')
|
|
4
|
+
const { kDestroyed, kBodyUsed, kListeners, kBody } = require('./symbols')
|
|
5
5
|
const { IncomingMessage } = require('node:http')
|
|
6
6
|
const stream = require('node:stream')
|
|
7
7
|
const net = require('node:net')
|
|
8
|
-
const { InvalidArgumentError } = require('./errors')
|
|
9
8
|
const { Blob } = require('node:buffer')
|
|
10
9
|
const nodeUtil = require('node:util')
|
|
11
10
|
const { stringify } = require('node:querystring')
|
|
11
|
+
const { EventEmitter: EE } = require('node:events')
|
|
12
|
+
const { InvalidArgumentError } = require('./errors')
|
|
12
13
|
const { headerNameLowerCasedRecord } = require('./constants')
|
|
13
14
|
const { tree } = require('./tree')
|
|
14
15
|
|
|
15
16
|
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
|
|
16
17
|
|
|
18
|
+
class BodyAsyncIterable {
|
|
19
|
+
constructor (body) {
|
|
20
|
+
this[kBody] = body
|
|
21
|
+
this[kBodyUsed] = false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async * [Symbol.asyncIterator] () {
|
|
25
|
+
assert(!this[kBodyUsed], 'disturbed')
|
|
26
|
+
this[kBodyUsed] = true
|
|
27
|
+
yield * this[kBody]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function wrapRequestBody (body) {
|
|
32
|
+
if (isStream(body)) {
|
|
33
|
+
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
|
|
34
|
+
// so that it can be dispatched again?
|
|
35
|
+
// TODO (fix): Do we need 100-expect support to provide a way to do this properly?
|
|
36
|
+
if (bodyLength(body) === 0) {
|
|
37
|
+
body
|
|
38
|
+
.on('data', function () {
|
|
39
|
+
assert(false)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof body.readableDidRead !== 'boolean') {
|
|
44
|
+
body[kBodyUsed] = false
|
|
45
|
+
EE.prototype.on.call(body, 'data', function () {
|
|
46
|
+
this[kBodyUsed] = true
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return body
|
|
51
|
+
} else if (body && typeof body.pipeTo === 'function') {
|
|
52
|
+
// TODO (fix): We can't access ReadableStream internal state
|
|
53
|
+
// to determine whether or not it has been disturbed. This is just
|
|
54
|
+
// a workaround.
|
|
55
|
+
return new BodyAsyncIterable(body)
|
|
56
|
+
} else if (
|
|
57
|
+
body &&
|
|
58
|
+
typeof body !== 'string' &&
|
|
59
|
+
!ArrayBuffer.isView(body) &&
|
|
60
|
+
isIterable(body)
|
|
61
|
+
) {
|
|
62
|
+
// TODO: Should we allow re-using iterable if !this.opts.idempotent
|
|
63
|
+
// or through some other flag?
|
|
64
|
+
return new BodyAsyncIterable(body)
|
|
65
|
+
} else {
|
|
66
|
+
return body
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
17
70
|
function nop () {}
|
|
18
71
|
|
|
19
72
|
function isStream (obj) {
|
|
@@ -592,6 +645,31 @@ function errorRequest (client, request, err) {
|
|
|
592
645
|
const kEnumerableProperty = Object.create(null)
|
|
593
646
|
kEnumerableProperty.enumerable = true
|
|
594
647
|
|
|
648
|
+
const normalizedMethodRecordsBase = {
|
|
649
|
+
delete: 'DELETE',
|
|
650
|
+
DELETE: 'DELETE',
|
|
651
|
+
get: 'GET',
|
|
652
|
+
GET: 'GET',
|
|
653
|
+
head: 'HEAD',
|
|
654
|
+
HEAD: 'HEAD',
|
|
655
|
+
options: 'OPTIONS',
|
|
656
|
+
OPTIONS: 'OPTIONS',
|
|
657
|
+
post: 'POST',
|
|
658
|
+
POST: 'POST',
|
|
659
|
+
put: 'PUT',
|
|
660
|
+
PUT: 'PUT'
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const normalizedMethodRecords = {
|
|
664
|
+
...normalizedMethodRecordsBase,
|
|
665
|
+
patch: 'patch',
|
|
666
|
+
PATCH: 'PATCH'
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
|
|
670
|
+
Object.setPrototypeOf(normalizedMethodRecordsBase, null)
|
|
671
|
+
Object.setPrototypeOf(normalizedMethodRecords, null)
|
|
672
|
+
|
|
595
673
|
module.exports = {
|
|
596
674
|
kEnumerableProperty,
|
|
597
675
|
nop,
|
|
@@ -630,9 +708,12 @@ module.exports = {
|
|
|
630
708
|
isValidHeaderValue,
|
|
631
709
|
isTokenCharCode,
|
|
632
710
|
parseRangeHeader,
|
|
711
|
+
normalizedMethodRecordsBase,
|
|
712
|
+
normalizedMethodRecords,
|
|
633
713
|
isValidPort,
|
|
634
714
|
isHttpOrHttpsPrefixed,
|
|
635
715
|
nodeMajor,
|
|
636
716
|
nodeMinor,
|
|
637
|
-
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
|
717
|
+
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'],
|
|
718
|
+
wrapRequestBody
|
|
638
719
|
}
|
|
@@ -978,19 +978,19 @@ function writeH1 (client, request) {
|
|
|
978
978
|
|
|
979
979
|
/* istanbul ignore else: assertion */
|
|
980
980
|
if (!body || bodyLength === 0) {
|
|
981
|
-
writeBuffer(
|
|
981
|
+
writeBuffer(abort, null, client, request, socket, contentLength, header, expectsPayload)
|
|
982
982
|
} else if (util.isBuffer(body)) {
|
|
983
|
-
writeBuffer(
|
|
983
|
+
writeBuffer(abort, body, client, request, socket, contentLength, header, expectsPayload)
|
|
984
984
|
} else if (util.isBlobLike(body)) {
|
|
985
985
|
if (typeof body.stream === 'function') {
|
|
986
|
-
writeIterable(
|
|
986
|
+
writeIterable(abort, body.stream(), client, request, socket, contentLength, header, expectsPayload)
|
|
987
987
|
} else {
|
|
988
|
-
writeBlob(
|
|
988
|
+
writeBlob(abort, body, client, request, socket, contentLength, header, expectsPayload)
|
|
989
989
|
}
|
|
990
990
|
} else if (util.isStream(body)) {
|
|
991
|
-
writeStream(
|
|
991
|
+
writeStream(abort, body, client, request, socket, contentLength, header, expectsPayload)
|
|
992
992
|
} else if (util.isIterable(body)) {
|
|
993
|
-
writeIterable(
|
|
993
|
+
writeIterable(abort, body, client, request, socket, contentLength, header, expectsPayload)
|
|
994
994
|
} else {
|
|
995
995
|
assert(false)
|
|
996
996
|
}
|
|
@@ -998,7 +998,7 @@ function writeH1 (client, request) {
|
|
|
998
998
|
return true
|
|
999
999
|
}
|
|
1000
1000
|
|
|
1001
|
-
function writeStream (
|
|
1001
|
+
function writeStream (abort, body, client, request, socket, contentLength, header, expectsPayload) {
|
|
1002
1002
|
assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
|
|
1003
1003
|
|
|
1004
1004
|
let finished = false
|
|
@@ -1101,7 +1101,7 @@ function writeStream ({ abort, body, client, request, socket, contentLength, hea
|
|
|
1101
1101
|
}
|
|
1102
1102
|
}
|
|
1103
1103
|
|
|
1104
|
-
function writeBuffer (
|
|
1104
|
+
function writeBuffer (abort, body, client, request, socket, contentLength, header, expectsPayload) {
|
|
1105
1105
|
try {
|
|
1106
1106
|
if (!body) {
|
|
1107
1107
|
if (contentLength === 0) {
|
|
@@ -1131,7 +1131,7 @@ function writeBuffer ({ abort, body, client, request, socket, contentLength, hea
|
|
|
1131
1131
|
}
|
|
1132
1132
|
}
|
|
1133
1133
|
|
|
1134
|
-
async function writeBlob (
|
|
1134
|
+
async function writeBlob (abort, body, client, request, socket, contentLength, header, expectsPayload) {
|
|
1135
1135
|
assert(contentLength === body.size, 'blob body must have content length')
|
|
1136
1136
|
|
|
1137
1137
|
try {
|
|
@@ -1159,7 +1159,7 @@ async function writeBlob ({ abort, body, client, request, socket, contentLength,
|
|
|
1159
1159
|
}
|
|
1160
1160
|
}
|
|
1161
1161
|
|
|
1162
|
-
async function writeIterable (
|
|
1162
|
+
async function writeIterable (abort, body, client, request, socket, contentLength, header, expectsPayload) {
|
|
1163
1163
|
assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
|
|
1164
1164
|
|
|
1165
1165
|
let callback = null
|
|
@@ -477,82 +477,80 @@ function writeH2 (client, request) {
|
|
|
477
477
|
function writeBodyH2 () {
|
|
478
478
|
/* istanbul ignore else: assertion */
|
|
479
479
|
if (!body || contentLength === 0) {
|
|
480
|
-
writeBuffer(
|
|
480
|
+
writeBuffer(
|
|
481
481
|
abort,
|
|
482
|
+
stream,
|
|
483
|
+
null,
|
|
482
484
|
client,
|
|
483
485
|
request,
|
|
486
|
+
client[kSocket],
|
|
484
487
|
contentLength,
|
|
485
|
-
expectsPayload
|
|
486
|
-
|
|
487
|
-
body: null,
|
|
488
|
-
socket: client[kSocket]
|
|
489
|
-
})
|
|
488
|
+
expectsPayload
|
|
489
|
+
)
|
|
490
490
|
} else if (util.isBuffer(body)) {
|
|
491
|
-
writeBuffer(
|
|
491
|
+
writeBuffer(
|
|
492
492
|
abort,
|
|
493
|
+
stream,
|
|
494
|
+
body,
|
|
493
495
|
client,
|
|
494
496
|
request,
|
|
497
|
+
client[kSocket],
|
|
495
498
|
contentLength,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
h2stream: stream,
|
|
499
|
-
socket: client[kSocket]
|
|
500
|
-
})
|
|
499
|
+
expectsPayload
|
|
500
|
+
)
|
|
501
501
|
} else if (util.isBlobLike(body)) {
|
|
502
502
|
if (typeof body.stream === 'function') {
|
|
503
|
-
writeIterable(
|
|
503
|
+
writeIterable(
|
|
504
504
|
abort,
|
|
505
|
+
stream,
|
|
506
|
+
body.stream(),
|
|
505
507
|
client,
|
|
506
508
|
request,
|
|
509
|
+
client[kSocket],
|
|
507
510
|
contentLength,
|
|
508
|
-
expectsPayload
|
|
509
|
-
|
|
510
|
-
body: body.stream(),
|
|
511
|
-
socket: client[kSocket]
|
|
512
|
-
})
|
|
511
|
+
expectsPayload
|
|
512
|
+
)
|
|
513
513
|
} else {
|
|
514
|
-
writeBlob(
|
|
514
|
+
writeBlob(
|
|
515
515
|
abort,
|
|
516
|
+
stream,
|
|
516
517
|
body,
|
|
517
518
|
client,
|
|
518
519
|
request,
|
|
520
|
+
client[kSocket],
|
|
519
521
|
contentLength,
|
|
520
|
-
expectsPayload
|
|
521
|
-
|
|
522
|
-
socket: client[kSocket]
|
|
523
|
-
})
|
|
522
|
+
expectsPayload
|
|
523
|
+
)
|
|
524
524
|
}
|
|
525
525
|
} else if (util.isStream(body)) {
|
|
526
|
-
writeStream(
|
|
526
|
+
writeStream(
|
|
527
527
|
abort,
|
|
528
|
+
client[kSocket],
|
|
529
|
+
expectsPayload,
|
|
530
|
+
stream,
|
|
528
531
|
body,
|
|
529
532
|
client,
|
|
530
533
|
request,
|
|
531
|
-
contentLength
|
|
532
|
-
|
|
533
|
-
socket: client[kSocket],
|
|
534
|
-
h2stream: stream,
|
|
535
|
-
header: ''
|
|
536
|
-
})
|
|
534
|
+
contentLength
|
|
535
|
+
)
|
|
537
536
|
} else if (util.isIterable(body)) {
|
|
538
|
-
writeIterable(
|
|
537
|
+
writeIterable(
|
|
539
538
|
abort,
|
|
539
|
+
stream,
|
|
540
540
|
body,
|
|
541
541
|
client,
|
|
542
542
|
request,
|
|
543
|
+
client[kSocket],
|
|
543
544
|
contentLength,
|
|
544
|
-
expectsPayload
|
|
545
|
-
|
|
546
|
-
h2stream: stream,
|
|
547
|
-
socket: client[kSocket]
|
|
548
|
-
})
|
|
545
|
+
expectsPayload
|
|
546
|
+
)
|
|
549
547
|
} else {
|
|
550
548
|
assert(false)
|
|
551
549
|
}
|
|
552
550
|
}
|
|
553
551
|
}
|
|
554
552
|
|
|
555
|
-
function writeBuffer (
|
|
553
|
+
function writeBuffer (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
556
554
|
try {
|
|
557
555
|
if (body != null && util.isBuffer(body)) {
|
|
558
556
|
assert(contentLength === body.byteLength, 'buffer body must have content length')
|
|
@@ -575,7 +573,7 @@ function writeBuffer ({ abort, h2stream, body, client, request, socket, contentL
|
|
|
575
573
|
}
|
|
576
574
|
}
|
|
577
575
|
|
|
578
|
-
function writeStream (
|
|
576
|
+
function writeStream (abort, socket, expectsPayload, h2stream, body, client, request, contentLength) {
|
|
579
577
|
assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
|
|
580
578
|
|
|
581
579
|
// For HTTP/2, is enough to pipe the stream
|
|
@@ -606,7 +604,7 @@ function writeStream ({ abort, socket, expectsPayload, h2stream, body, client, r
|
|
|
606
604
|
}
|
|
607
605
|
}
|
|
608
606
|
|
|
609
|
-
async function writeBlob (
|
|
607
|
+
async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
610
608
|
assert(contentLength === body.size, 'blob body must have content length')
|
|
611
609
|
|
|
612
610
|
try {
|
|
@@ -634,7 +632,7 @@ async function writeBlob ({ abort, h2stream, body, client, request, socket, cont
|
|
|
634
632
|
}
|
|
635
633
|
}
|
|
636
634
|
|
|
637
|
-
async function writeIterable (
|
|
635
|
+
async function writeIterable (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
638
636
|
assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
|
|
639
637
|
|
|
640
638
|
let callback = null
|
|
@@ -3,7 +3,12 @@ const assert = require('node:assert')
|
|
|
3
3
|
|
|
4
4
|
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
|
|
5
5
|
const { RequestRetryError } = require('../core/errors')
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
isDisturbed,
|
|
8
|
+
parseHeaders,
|
|
9
|
+
parseRangeHeader,
|
|
10
|
+
wrapRequestBody
|
|
11
|
+
} = require('../core/util')
|
|
7
12
|
|
|
8
13
|
function calculateRetryAfterHeader (retryAfter) {
|
|
9
14
|
const current = Date.now()
|
|
@@ -29,7 +34,7 @@ class RetryHandler {
|
|
|
29
34
|
|
|
30
35
|
this.dispatch = handlers.dispatch
|
|
31
36
|
this.handler = handlers.handler
|
|
32
|
-
this.opts = dispatchOpts
|
|
37
|
+
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
|
|
33
38
|
this.abort = null
|
|
34
39
|
this.aborted = false
|
|
35
40
|
this.retryOpts = {
|
|
@@ -174,7 +179,9 @@ class RetryHandler {
|
|
|
174
179
|
this.abort(
|
|
175
180
|
new RequestRetryError('Request failed', statusCode, {
|
|
176
181
|
headers,
|
|
177
|
-
|
|
182
|
+
data: {
|
|
183
|
+
count: this.retryCount
|
|
184
|
+
}
|
|
178
185
|
})
|
|
179
186
|
)
|
|
180
187
|
return false
|
|
@@ -195,7 +202,7 @@ class RetryHandler {
|
|
|
195
202
|
this.abort(
|
|
196
203
|
new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
197
204
|
headers,
|
|
198
|
-
count: this.retryCount
|
|
205
|
+
data: { count: this.retryCount }
|
|
199
206
|
})
|
|
200
207
|
)
|
|
201
208
|
return false
|
|
@@ -206,7 +213,7 @@ class RetryHandler {
|
|
|
206
213
|
this.abort(
|
|
207
214
|
new RequestRetryError('ETag mismatch', statusCode, {
|
|
208
215
|
headers,
|
|
209
|
-
count: this.retryCount
|
|
216
|
+
data: { count: this.retryCount }
|
|
210
217
|
})
|
|
211
218
|
)
|
|
212
219
|
return false
|
|
@@ -278,7 +285,7 @@ class RetryHandler {
|
|
|
278
285
|
|
|
279
286
|
const err = new RequestRetryError('Request failed', statusCode, {
|
|
280
287
|
headers,
|
|
281
|
-
count: this.retryCount
|
|
288
|
+
data: { count: this.retryCount }
|
|
282
289
|
})
|
|
283
290
|
|
|
284
291
|
this.abort(err)
|
package/lib/web/cookies/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { parseSetCookie } = require('./parse')
|
|
4
|
-
const { stringify
|
|
4
|
+
const { stringify } = require('./util')
|
|
5
5
|
const { webidl } = require('../fetch/webidl')
|
|
6
6
|
const { Headers } = require('../fetch/headers')
|
|
7
7
|
|
|
@@ -78,14 +78,13 @@ function getSetCookies (headers) {
|
|
|
78
78
|
|
|
79
79
|
webidl.brandCheck(headers, Headers, { strict: false })
|
|
80
80
|
|
|
81
|
-
const cookies =
|
|
81
|
+
const cookies = headers.getSetCookie()
|
|
82
82
|
|
|
83
83
|
if (!cookies) {
|
|
84
84
|
return []
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair))
|
|
87
|
+
return cookies.map((pair) => parseSetCookie(pair))
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
/**
|
package/lib/web/cookies/util.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const assert = require('node:assert')
|
|
4
|
-
const { getHeadersList: internalGetHeadersList } = require('../fetch/headers')
|
|
5
|
-
|
|
6
3
|
/**
|
|
7
4
|
* @param {string} value
|
|
8
5
|
* @returns {boolean}
|
|
@@ -275,35 +272,11 @@ function stringify (cookie) {
|
|
|
275
272
|
return out.join('; ')
|
|
276
273
|
}
|
|
277
274
|
|
|
278
|
-
let kHeadersListNode
|
|
279
|
-
|
|
280
|
-
function getHeadersList (headers) {
|
|
281
|
-
try {
|
|
282
|
-
return internalGetHeadersList(headers)
|
|
283
|
-
} catch {
|
|
284
|
-
// fall-through
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (!kHeadersListNode) {
|
|
288
|
-
kHeadersListNode = Object.getOwnPropertySymbols(headers).find(
|
|
289
|
-
(symbol) => symbol.description === 'headers list'
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
assert(kHeadersListNode, 'Headers cannot be parsed')
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const headersList = headers[kHeadersListNode]
|
|
296
|
-
assert(headersList)
|
|
297
|
-
|
|
298
|
-
return headersList
|
|
299
|
-
}
|
|
300
|
-
|
|
301
275
|
module.exports = {
|
|
302
276
|
isCTLExcludingHtab,
|
|
303
277
|
validateCookieName,
|
|
304
278
|
validateCookiePath,
|
|
305
279
|
validateCookieValue,
|
|
306
280
|
toIMFDate,
|
|
307
|
-
stringify
|
|
308
|
-
getHeadersList
|
|
281
|
+
stringify
|
|
309
282
|
}
|
package/lib/web/fetch/headers.js
CHANGED
|
@@ -641,14 +641,6 @@ Object.defineProperties(Headers.prototype, {
|
|
|
641
641
|
},
|
|
642
642
|
[util.inspect.custom]: {
|
|
643
643
|
enumerable: false
|
|
644
|
-
},
|
|
645
|
-
// Compatibility for global headers
|
|
646
|
-
[Symbol('headers list')]: {
|
|
647
|
-
configurable: false,
|
|
648
|
-
enumerable: false,
|
|
649
|
-
get: function () {
|
|
650
|
-
return getHeadersList(this)
|
|
651
|
-
}
|
|
652
644
|
}
|
|
653
645
|
})
|
|
654
646
|
|
package/lib/web/fetch/request.js
CHANGED
|
@@ -10,9 +10,7 @@ const nodeUtil = require('node:util')
|
|
|
10
10
|
const {
|
|
11
11
|
isValidHTTPToken,
|
|
12
12
|
sameOrigin,
|
|
13
|
-
|
|
14
|
-
environmentSettingsObject,
|
|
15
|
-
normalizeMethodRecord
|
|
13
|
+
environmentSettingsObject
|
|
16
14
|
} = require('./util')
|
|
17
15
|
const {
|
|
18
16
|
forbiddenMethodsSet,
|
|
@@ -24,7 +22,7 @@ const {
|
|
|
24
22
|
requestCache,
|
|
25
23
|
requestDuplex
|
|
26
24
|
} = require('./constants')
|
|
27
|
-
const { kEnumerableProperty } = util
|
|
25
|
+
const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util
|
|
28
26
|
const { kHeaders, kSignal, kState, kDispatcher } = require('./symbols')
|
|
29
27
|
const { webidl } = require('./webidl')
|
|
30
28
|
const { URLSerializer } = require('./data-url')
|
|
@@ -349,7 +347,7 @@ class Request {
|
|
|
349
347
|
// 1. Let method be init["method"].
|
|
350
348
|
let method = init.method
|
|
351
349
|
|
|
352
|
-
const mayBeNormalized =
|
|
350
|
+
const mayBeNormalized = normalizedMethodRecords[method]
|
|
353
351
|
|
|
354
352
|
if (mayBeNormalized !== undefined) {
|
|
355
353
|
// Note: Bypass validation DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH and these lowercase ones
|
|
@@ -361,12 +359,16 @@ class Request {
|
|
|
361
359
|
throw new TypeError(`'${method}' is not a valid HTTP method.`)
|
|
362
360
|
}
|
|
363
361
|
|
|
364
|
-
|
|
362
|
+
const upperCase = method.toUpperCase()
|
|
363
|
+
|
|
364
|
+
if (forbiddenMethodsSet.has(upperCase)) {
|
|
365
365
|
throw new TypeError(`'${method}' HTTP method is unsupported.`)
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
// 3. Normalize method.
|
|
369
|
-
|
|
369
|
+
// https://fetch.spec.whatwg.org/#concept-method-normalize
|
|
370
|
+
// Note: must be in uppercase
|
|
371
|
+
method = normalizedMethodRecordsBase[upperCase] ?? method
|
|
370
372
|
|
|
371
373
|
// 4. Set request’s method to method.
|
|
372
374
|
request.method = method
|
package/lib/web/fetch/util.js
CHANGED
|
@@ -6,7 +6,7 @@ const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet
|
|
|
6
6
|
const { getGlobalOrigin } = require('./global')
|
|
7
7
|
const { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require('./data-url')
|
|
8
8
|
const { performance } = require('node:perf_hooks')
|
|
9
|
-
const { isBlobLike, ReadableStreamFrom, isValidHTTPToken } = require('../../core/util')
|
|
9
|
+
const { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
|
|
10
10
|
const assert = require('node:assert')
|
|
11
11
|
const { isUint8Array } = require('node:util/types')
|
|
12
12
|
const { webidl } = require('./webidl')
|
|
@@ -791,37 +791,12 @@ function isCancelled (fetchParams) {
|
|
|
791
791
|
fetchParams.controller.state === 'terminated'
|
|
792
792
|
}
|
|
793
793
|
|
|
794
|
-
const normalizeMethodRecordBase = {
|
|
795
|
-
delete: 'DELETE',
|
|
796
|
-
DELETE: 'DELETE',
|
|
797
|
-
get: 'GET',
|
|
798
|
-
GET: 'GET',
|
|
799
|
-
head: 'HEAD',
|
|
800
|
-
HEAD: 'HEAD',
|
|
801
|
-
options: 'OPTIONS',
|
|
802
|
-
OPTIONS: 'OPTIONS',
|
|
803
|
-
post: 'POST',
|
|
804
|
-
POST: 'POST',
|
|
805
|
-
put: 'PUT',
|
|
806
|
-
PUT: 'PUT'
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const normalizeMethodRecord = {
|
|
810
|
-
...normalizeMethodRecordBase,
|
|
811
|
-
patch: 'patch',
|
|
812
|
-
PATCH: 'PATCH'
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
|
|
816
|
-
Object.setPrototypeOf(normalizeMethodRecordBase, null)
|
|
817
|
-
Object.setPrototypeOf(normalizeMethodRecord, null)
|
|
818
|
-
|
|
819
794
|
/**
|
|
820
795
|
* @see https://fetch.spec.whatwg.org/#concept-method-normalize
|
|
821
796
|
* @param {string} method
|
|
822
797
|
*/
|
|
823
798
|
function normalizeMethod (method) {
|
|
824
|
-
return
|
|
799
|
+
return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
|
|
825
800
|
}
|
|
826
801
|
|
|
827
802
|
// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
|
|
@@ -1639,7 +1614,6 @@ module.exports = {
|
|
|
1639
1614
|
urlHasHttpsScheme,
|
|
1640
1615
|
urlIsHttpHttpsScheme,
|
|
1641
1616
|
readAllBytes,
|
|
1642
|
-
normalizeMethodRecord,
|
|
1643
1617
|
simpleRangeHeaderValue,
|
|
1644
1618
|
buildContentRange,
|
|
1645
1619
|
parseMetadata,
|
|
@@ -28,8 +28,6 @@ const { types } = require('node:util')
|
|
|
28
28
|
const { ErrorEvent, CloseEvent } = require('./events')
|
|
29
29
|
const { SendQueue } = require('./sender')
|
|
30
30
|
|
|
31
|
-
let experimentalWarned = false
|
|
32
|
-
|
|
33
31
|
// https://websockets.spec.whatwg.org/#interface-definition
|
|
34
32
|
class WebSocket extends EventTarget {
|
|
35
33
|
#events = {
|
|
@@ -56,13 +54,6 @@ class WebSocket extends EventTarget {
|
|
|
56
54
|
const prefix = 'WebSocket constructor'
|
|
57
55
|
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
58
56
|
|
|
59
|
-
if (!experimentalWarned) {
|
|
60
|
-
experimentalWarned = true
|
|
61
|
-
process.emitWarning('WebSockets are experimental, expect them to change at any time.', {
|
|
62
|
-
code: 'UNDICI-WS'
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
57
|
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
|
|
67
58
|
|
|
68
59
|
url = webidl.converters.USVString(url, prefix, 'url')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.19.0",
|
|
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,8 +107,8 @@
|
|
|
107
107
|
"@sinonjs/fake-timers": "^11.1.0",
|
|
108
108
|
"@types/node": "^18.0.3",
|
|
109
109
|
"abort-controller": "^3.0.0",
|
|
110
|
-
"borp": "^0.
|
|
111
|
-
"c8": "^
|
|
110
|
+
"borp": "^0.15.0",
|
|
111
|
+
"c8": "^10.0.0",
|
|
112
112
|
"cross-env": "^7.0.3",
|
|
113
113
|
"dns-packet": "^5.4.0",
|
|
114
114
|
"fast-check": "^3.17.1",
|
package/types/errors.d.ts
CHANGED
|
@@ -125,4 +125,25 @@ declare namespace Errors {
|
|
|
125
125
|
name: 'ResponseExceededMaxSizeError';
|
|
126
126
|
code: 'UND_ERR_RES_EXCEEDED_MAX_SIZE';
|
|
127
127
|
}
|
|
128
|
+
|
|
129
|
+
export class RequestRetryError extends UndiciError {
|
|
130
|
+
constructor (
|
|
131
|
+
message: string,
|
|
132
|
+
statusCode: number,
|
|
133
|
+
headers?: IncomingHttpHeaders | string[] | null,
|
|
134
|
+
body?: null | Record<string, any> | string
|
|
135
|
+
);
|
|
136
|
+
name: 'RequestRetryError';
|
|
137
|
+
code: 'UND_ERR_REQ_RETRY';
|
|
138
|
+
statusCode: number;
|
|
139
|
+
data: {
|
|
140
|
+
count: number;
|
|
141
|
+
};
|
|
142
|
+
headers: Record<string, string | string[]>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class SecureProxyConnectionError extends UndiciError {
|
|
146
|
+
name: 'SecureProxyConnectionError';
|
|
147
|
+
code: 'UND_ERR_PRX_TLS';
|
|
148
|
+
}
|
|
128
149
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import EnvHttpProxyAgent from './env-http-proxy-agent'
|
|
|
18
18
|
import RetryHandler from'./retry-handler'
|
|
19
19
|
import RetryAgent from'./retry-agent'
|
|
20
20
|
import { request, pipeline, stream, connect, upgrade } from './api'
|
|
21
|
+
import interceptors from './interceptors'
|
|
21
22
|
|
|
22
23
|
export * from './util'
|
|
23
24
|
export * from './cookies'
|
|
@@ -32,7 +33,7 @@ export * from './content-type'
|
|
|
32
33
|
export * from './cache'
|
|
33
34
|
export { Interceptable } from './mock-interceptor'
|
|
34
35
|
|
|
35
|
-
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent }
|
|
36
|
+
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, interceptors, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent }
|
|
36
37
|
export default Undici
|
|
37
38
|
|
|
38
39
|
declare namespace Undici {
|