undici 5.27.2 → 5.28.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/api/Client.md +1 -1
- package/docs/api/MockPool.md +38 -4
- package/docs/api/RetryHandler.md +108 -0
- package/index.js +2 -0
- package/lib/api/readable.js +40 -25
- package/lib/client.js +10 -7
- package/lib/core/errors.js +15 -1
- package/lib/core/request.js +2 -10
- package/lib/core/symbols.js +2 -1
- package/lib/core/util.js +21 -13
- package/lib/fetch/headers.js +93 -59
- package/lib/fetch/index.js +4 -4
- package/lib/fetch/request.js +2 -2
- package/lib/fetch/util.js +38 -33
- package/lib/fetch/webidl.js +2 -4
- package/lib/handler/RetryHandler.js +336 -0
- package/package.json +2 -1
- package/types/client.d.ts +1 -1
- package/types/dispatcher.d.ts +1 -1
- package/types/index.d.ts +3 -1
- package/types/retry-handler.d.ts +116 -0
package/lib/fetch/headers.js
CHANGED
|
@@ -16,6 +16,13 @@ const assert = require('assert')
|
|
|
16
16
|
const kHeadersMap = Symbol('headers map')
|
|
17
17
|
const kHeadersSortedMap = Symbol('headers map sorted')
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @param {number} code
|
|
21
|
+
*/
|
|
22
|
+
function isHTTPWhiteSpaceCharCode (code) {
|
|
23
|
+
return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
27
|
* @see https://fetch.spec.whatwg.org/#concept-header-value-normalize
|
|
21
28
|
* @param {string} potentialValue
|
|
@@ -24,12 +31,12 @@ function headerValueNormalize (potentialValue) {
|
|
|
24
31
|
// To normalize a byte sequence potentialValue, remove
|
|
25
32
|
// any leading and trailing HTTP whitespace bytes from
|
|
26
33
|
// potentialValue.
|
|
34
|
+
let i = 0; let j = potentialValue.length
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '')
|
|
36
|
+
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j
|
|
37
|
+
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i
|
|
38
|
+
|
|
39
|
+
return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j)
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
function fill (headers, object) {
|
|
@@ -38,7 +45,8 @@ function fill (headers, object) {
|
|
|
38
45
|
// 1. If object is a sequence, then for each header in object:
|
|
39
46
|
// Note: webidl conversion to array has already been done.
|
|
40
47
|
if (Array.isArray(object)) {
|
|
41
|
-
for (
|
|
48
|
+
for (let i = 0; i < object.length; ++i) {
|
|
49
|
+
const header = object[i]
|
|
42
50
|
// 1. If header does not contain exactly two items, then throw a TypeError.
|
|
43
51
|
if (header.length !== 2) {
|
|
44
52
|
throw webidl.errors.exception({
|
|
@@ -48,15 +56,16 @@ function fill (headers, object) {
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
// 2. Append (header’s first item, header’s second item) to headers.
|
|
51
|
-
headers
|
|
59
|
+
appendHeader(headers, header[0], header[1])
|
|
52
60
|
}
|
|
53
61
|
} else if (typeof object === 'object' && object !== null) {
|
|
54
62
|
// Note: null should throw
|
|
55
63
|
|
|
56
64
|
// 2. Otherwise, object is a record, then for each key → value in object,
|
|
57
65
|
// append (key, value) to headers
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
const keys = Object.keys(object)
|
|
67
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
68
|
+
appendHeader(headers, keys[i], object[keys[i]])
|
|
60
69
|
}
|
|
61
70
|
} else {
|
|
62
71
|
throw webidl.errors.conversionFailed({
|
|
@@ -67,6 +76,50 @@ function fill (headers, object) {
|
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
|
|
79
|
+
/**
|
|
80
|
+
* @see https://fetch.spec.whatwg.org/#concept-headers-append
|
|
81
|
+
*/
|
|
82
|
+
function appendHeader (headers, name, value) {
|
|
83
|
+
// 1. Normalize value.
|
|
84
|
+
value = headerValueNormalize(value)
|
|
85
|
+
|
|
86
|
+
// 2. If name is not a header name or value is not a
|
|
87
|
+
// header value, then throw a TypeError.
|
|
88
|
+
if (!isValidHeaderName(name)) {
|
|
89
|
+
throw webidl.errors.invalidArgument({
|
|
90
|
+
prefix: 'Headers.append',
|
|
91
|
+
value: name,
|
|
92
|
+
type: 'header name'
|
|
93
|
+
})
|
|
94
|
+
} else if (!isValidHeaderValue(value)) {
|
|
95
|
+
throw webidl.errors.invalidArgument({
|
|
96
|
+
prefix: 'Headers.append',
|
|
97
|
+
value,
|
|
98
|
+
type: 'header value'
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 3. If headers’s guard is "immutable", then throw a TypeError.
|
|
103
|
+
// 4. Otherwise, if headers’s guard is "request" and name is a
|
|
104
|
+
// forbidden header name, return.
|
|
105
|
+
// Note: undici does not implement forbidden header names
|
|
106
|
+
if (headers[kGuard] === 'immutable') {
|
|
107
|
+
throw new TypeError('immutable')
|
|
108
|
+
} else if (headers[kGuard] === 'request-no-cors') {
|
|
109
|
+
// 5. Otherwise, if headers’s guard is "request-no-cors":
|
|
110
|
+
// TODO
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 6. Otherwise, if headers’s guard is "response" and name is a
|
|
114
|
+
// forbidden response-header name, return.
|
|
115
|
+
|
|
116
|
+
// 7. Append (name, value) to headers’s header list.
|
|
117
|
+
return headers[kHeadersList].append(name, value)
|
|
118
|
+
|
|
119
|
+
// 8. If headers’s guard is "request-no-cors", then remove
|
|
120
|
+
// privileged no-CORS request headers from headers
|
|
121
|
+
}
|
|
122
|
+
|
|
70
123
|
class HeadersList {
|
|
71
124
|
/** @type {[string, string][]|null} */
|
|
72
125
|
cookies = null
|
|
@@ -75,7 +128,7 @@ class HeadersList {
|
|
|
75
128
|
if (init instanceof HeadersList) {
|
|
76
129
|
this[kHeadersMap] = new Map(init[kHeadersMap])
|
|
77
130
|
this[kHeadersSortedMap] = init[kHeadersSortedMap]
|
|
78
|
-
this.cookies = init.cookies
|
|
131
|
+
this.cookies = init.cookies === null ? null : [...init.cookies]
|
|
79
132
|
} else {
|
|
80
133
|
this[kHeadersMap] = new Map(init)
|
|
81
134
|
this[kHeadersSortedMap] = null
|
|
@@ -137,7 +190,7 @@ class HeadersList {
|
|
|
137
190
|
// the first such header to value and remove the
|
|
138
191
|
// others.
|
|
139
192
|
// 2. Otherwise, append header (name, value) to list.
|
|
140
|
-
|
|
193
|
+
this[kHeadersMap].set(lowercaseName, { name, value })
|
|
141
194
|
}
|
|
142
195
|
|
|
143
196
|
// https://fetch.spec.whatwg.org/#concept-header-list-delete
|
|
@@ -150,20 +203,18 @@ class HeadersList {
|
|
|
150
203
|
this.cookies = null
|
|
151
204
|
}
|
|
152
205
|
|
|
153
|
-
|
|
206
|
+
this[kHeadersMap].delete(name)
|
|
154
207
|
}
|
|
155
208
|
|
|
156
209
|
// https://fetch.spec.whatwg.org/#concept-header-list-get
|
|
157
210
|
get (name) {
|
|
158
|
-
|
|
159
|
-
if (!this.contains(name)) {
|
|
160
|
-
return null
|
|
161
|
-
}
|
|
211
|
+
const value = this[kHeadersMap].get(name.toLowerCase())
|
|
162
212
|
|
|
213
|
+
// 1. If list does not contain name, then return null.
|
|
163
214
|
// 2. Return the values of all headers in list whose name
|
|
164
215
|
// is a byte-case-insensitive match for name,
|
|
165
216
|
// separated from each other by 0x2C 0x20, in order.
|
|
166
|
-
return
|
|
217
|
+
return value === undefined ? null : value.value
|
|
167
218
|
}
|
|
168
219
|
|
|
169
220
|
* [Symbol.iterator] () {
|
|
@@ -212,43 +263,7 @@ class Headers {
|
|
|
212
263
|
name = webidl.converters.ByteString(name)
|
|
213
264
|
value = webidl.converters.ByteString(value)
|
|
214
265
|
|
|
215
|
-
|
|
216
|
-
value = headerValueNormalize(value)
|
|
217
|
-
|
|
218
|
-
// 2. If name is not a header name or value is not a
|
|
219
|
-
// header value, then throw a TypeError.
|
|
220
|
-
if (!isValidHeaderName(name)) {
|
|
221
|
-
throw webidl.errors.invalidArgument({
|
|
222
|
-
prefix: 'Headers.append',
|
|
223
|
-
value: name,
|
|
224
|
-
type: 'header name'
|
|
225
|
-
})
|
|
226
|
-
} else if (!isValidHeaderValue(value)) {
|
|
227
|
-
throw webidl.errors.invalidArgument({
|
|
228
|
-
prefix: 'Headers.append',
|
|
229
|
-
value,
|
|
230
|
-
type: 'header value'
|
|
231
|
-
})
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// 3. If headers’s guard is "immutable", then throw a TypeError.
|
|
235
|
-
// 4. Otherwise, if headers’s guard is "request" and name is a
|
|
236
|
-
// forbidden header name, return.
|
|
237
|
-
// Note: undici does not implement forbidden header names
|
|
238
|
-
if (this[kGuard] === 'immutable') {
|
|
239
|
-
throw new TypeError('immutable')
|
|
240
|
-
} else if (this[kGuard] === 'request-no-cors') {
|
|
241
|
-
// 5. Otherwise, if headers’s guard is "request-no-cors":
|
|
242
|
-
// TODO
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 6. Otherwise, if headers’s guard is "response" and name is a
|
|
246
|
-
// forbidden response-header name, return.
|
|
247
|
-
|
|
248
|
-
// 7. Append (name, value) to headers’s header list.
|
|
249
|
-
// 8. If headers’s guard is "request-no-cors", then remove
|
|
250
|
-
// privileged no-CORS request headers from headers
|
|
251
|
-
return this[kHeadersList].append(name, value)
|
|
266
|
+
return appendHeader(this, name, value)
|
|
252
267
|
}
|
|
253
268
|
|
|
254
269
|
// https://fetch.spec.whatwg.org/#dom-headers-delete
|
|
@@ -293,7 +308,7 @@ class Headers {
|
|
|
293
308
|
// 7. Delete name from this’s header list.
|
|
294
309
|
// 8. If this’s guard is "request-no-cors", then remove
|
|
295
310
|
// privileged no-CORS request headers from this.
|
|
296
|
-
|
|
311
|
+
this[kHeadersList].delete(name)
|
|
297
312
|
}
|
|
298
313
|
|
|
299
314
|
// https://fetch.spec.whatwg.org/#dom-headers-get
|
|
@@ -386,7 +401,7 @@ class Headers {
|
|
|
386
401
|
// 7. Set (name, value) in this’s header list.
|
|
387
402
|
// 8. If this’s guard is "request-no-cors", then remove
|
|
388
403
|
// privileged no-CORS request headers from this
|
|
389
|
-
|
|
404
|
+
this[kHeadersList].set(name, value)
|
|
390
405
|
}
|
|
391
406
|
|
|
392
407
|
// https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
|
|
@@ -422,7 +437,8 @@ class Headers {
|
|
|
422
437
|
const cookies = this[kHeadersList].cookies
|
|
423
438
|
|
|
424
439
|
// 3. For each name of names:
|
|
425
|
-
for (
|
|
440
|
+
for (let i = 0; i < names.length; ++i) {
|
|
441
|
+
const [name, value] = names[i]
|
|
426
442
|
// 1. If name is `set-cookie`, then:
|
|
427
443
|
if (name === 'set-cookie') {
|
|
428
444
|
// 1. Let values be a list of all values of headers in list whose name
|
|
@@ -430,8 +446,8 @@ class Headers {
|
|
|
430
446
|
|
|
431
447
|
// 2. For each value of values:
|
|
432
448
|
// 1. Append (name, value) to headers.
|
|
433
|
-
for (
|
|
434
|
-
headers.push([name,
|
|
449
|
+
for (let j = 0; j < cookies.length; ++j) {
|
|
450
|
+
headers.push([name, cookies[j]])
|
|
435
451
|
}
|
|
436
452
|
} else {
|
|
437
453
|
// 2. Otherwise:
|
|
@@ -455,6 +471,12 @@ class Headers {
|
|
|
455
471
|
keys () {
|
|
456
472
|
webidl.brandCheck(this, Headers)
|
|
457
473
|
|
|
474
|
+
if (this[kGuard] === 'immutable') {
|
|
475
|
+
const value = this[kHeadersSortedMap]
|
|
476
|
+
return makeIterator(() => value, 'Headers',
|
|
477
|
+
'key')
|
|
478
|
+
}
|
|
479
|
+
|
|
458
480
|
return makeIterator(
|
|
459
481
|
() => [...this[kHeadersSortedMap].values()],
|
|
460
482
|
'Headers',
|
|
@@ -465,6 +487,12 @@ class Headers {
|
|
|
465
487
|
values () {
|
|
466
488
|
webidl.brandCheck(this, Headers)
|
|
467
489
|
|
|
490
|
+
if (this[kGuard] === 'immutable') {
|
|
491
|
+
const value = this[kHeadersSortedMap]
|
|
492
|
+
return makeIterator(() => value, 'Headers',
|
|
493
|
+
'value')
|
|
494
|
+
}
|
|
495
|
+
|
|
468
496
|
return makeIterator(
|
|
469
497
|
() => [...this[kHeadersSortedMap].values()],
|
|
470
498
|
'Headers',
|
|
@@ -475,6 +503,12 @@ class Headers {
|
|
|
475
503
|
entries () {
|
|
476
504
|
webidl.brandCheck(this, Headers)
|
|
477
505
|
|
|
506
|
+
if (this[kGuard] === 'immutable') {
|
|
507
|
+
const value = this[kHeadersSortedMap]
|
|
508
|
+
return makeIterator(() => value, 'Headers',
|
|
509
|
+
'key+value')
|
|
510
|
+
}
|
|
511
|
+
|
|
478
512
|
return makeIterator(
|
|
479
513
|
() => [...this[kHeadersSortedMap].values()],
|
|
480
514
|
'Headers',
|
package/lib/fetch/index.js
CHANGED
|
@@ -1957,7 +1957,7 @@ async function httpNetworkFetch (
|
|
|
1957
1957
|
path: url.pathname + url.search,
|
|
1958
1958
|
origin: url.origin,
|
|
1959
1959
|
method: request.method,
|
|
1960
|
-
body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body,
|
|
1960
|
+
body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
|
|
1961
1961
|
headers: request.headersList.entries,
|
|
1962
1962
|
maxRedirections: 0,
|
|
1963
1963
|
upgrade: request.mode === 'websocket' ? 'websocket' : undefined
|
|
@@ -2002,7 +2002,7 @@ async function httpNetworkFetch (
|
|
|
2002
2002
|
location = val
|
|
2003
2003
|
}
|
|
2004
2004
|
|
|
2005
|
-
headers.append(key, val)
|
|
2005
|
+
headers[kHeadersList].append(key, val)
|
|
2006
2006
|
}
|
|
2007
2007
|
} else {
|
|
2008
2008
|
const keys = Object.keys(headersList)
|
|
@@ -2016,7 +2016,7 @@ async function httpNetworkFetch (
|
|
|
2016
2016
|
location = val
|
|
2017
2017
|
}
|
|
2018
2018
|
|
|
2019
|
-
headers.append(key, val)
|
|
2019
|
+
headers[kHeadersList].append(key, val)
|
|
2020
2020
|
}
|
|
2021
2021
|
}
|
|
2022
2022
|
|
|
@@ -2120,7 +2120,7 @@ async function httpNetworkFetch (
|
|
|
2120
2120
|
const key = headersList[n + 0].toString('latin1')
|
|
2121
2121
|
const val = headersList[n + 1].toString('latin1')
|
|
2122
2122
|
|
|
2123
|
-
headers.append(key, val)
|
|
2123
|
+
headers[kHeadersList].append(key, val)
|
|
2124
2124
|
}
|
|
2125
2125
|
|
|
2126
2126
|
resolve({
|
package/lib/fetch/request.js
CHANGED
|
@@ -316,11 +316,11 @@ class Request {
|
|
|
316
316
|
// 2. If method is not a method or method is a forbidden method, then
|
|
317
317
|
// throw a TypeError.
|
|
318
318
|
if (!isValidHTTPToken(init.method)) {
|
|
319
|
-
throw TypeError(`'${init.method}' is not a valid HTTP method.`)
|
|
319
|
+
throw new TypeError(`'${init.method}' is not a valid HTTP method.`)
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
if (forbiddenMethodsSet.has(method.toUpperCase())) {
|
|
323
|
-
throw TypeError(`'${init.method}' HTTP method is unsupported.`)
|
|
323
|
+
throw new TypeError(`'${init.method}' HTTP method is unsupported.`)
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
// 3. Normalize method.
|
package/lib/fetch/util.js
CHANGED
|
@@ -103,52 +103,57 @@ function isValidReasonPhrase (statusText) {
|
|
|
103
103
|
return true
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
106
|
+
/**
|
|
107
|
+
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
108
|
+
* @param {number} c
|
|
109
|
+
*/
|
|
110
|
+
function isTokenCharCode (c) {
|
|
111
|
+
switch (c) {
|
|
112
|
+
case 0x22:
|
|
113
|
+
case 0x28:
|
|
114
|
+
case 0x29:
|
|
115
|
+
case 0x2c:
|
|
116
|
+
case 0x2f:
|
|
117
|
+
case 0x3a:
|
|
118
|
+
case 0x3b:
|
|
119
|
+
case 0x3c:
|
|
120
|
+
case 0x3d:
|
|
121
|
+
case 0x3e:
|
|
122
|
+
case 0x3f:
|
|
123
|
+
case 0x40:
|
|
124
|
+
case 0x5b:
|
|
125
|
+
case 0x5c:
|
|
126
|
+
case 0x5d:
|
|
127
|
+
case 0x7b:
|
|
128
|
+
case 0x7d:
|
|
129
|
+
// DQUOTE and "(),/:;<=>?@[\]{}"
|
|
130
|
+
return false
|
|
131
|
+
default:
|
|
132
|
+
// VCHAR %x21-7E
|
|
133
|
+
return c >= 0x21 && c <= 0x7e
|
|
134
|
+
}
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
/**
|
|
138
|
+
* @param {string} characters
|
|
139
|
+
*/
|
|
132
140
|
function isValidHTTPToken (characters) {
|
|
133
|
-
if (
|
|
141
|
+
if (characters.length === 0) {
|
|
134
142
|
return false
|
|
135
143
|
}
|
|
136
144
|
for (let i = 0; i < characters.length; ++i) {
|
|
137
|
-
|
|
138
|
-
if (c > 0x7f || !isTokenChar(c)) {
|
|
145
|
+
if (!isTokenCharCode(characters.charCodeAt(i))) {
|
|
139
146
|
return false
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
return true
|
|
143
150
|
}
|
|
144
151
|
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
/**
|
|
153
|
+
* @see https://fetch.spec.whatwg.org/#header-name
|
|
154
|
+
* @param {string} potentialValue
|
|
155
|
+
*/
|
|
147
156
|
function isValidHeaderName (potentialValue) {
|
|
148
|
-
if (potentialValue.length === 0) {
|
|
149
|
-
return false
|
|
150
|
-
}
|
|
151
|
-
|
|
152
157
|
return isValidHTTPToken(potentialValue)
|
|
153
158
|
}
|
|
154
159
|
|
package/lib/fetch/webidl.js
CHANGED
|
@@ -427,12 +427,10 @@ webidl.converters.ByteString = function (V) {
|
|
|
427
427
|
// 2. If the value of any element of x is greater than
|
|
428
428
|
// 255, then throw a TypeError.
|
|
429
429
|
for (let index = 0; index < x.length; index++) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (charCode > 255) {
|
|
430
|
+
if (x.charCodeAt(index) > 255) {
|
|
433
431
|
throw new TypeError(
|
|
434
432
|
'Cannot convert argument to a ByteString because the character at ' +
|
|
435
|
-
`index ${index} has a value of ${
|
|
433
|
+
`index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
|
|
436
434
|
)
|
|
437
435
|
}
|
|
438
436
|
}
|