undici 5.20.0 → 5.21.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 +1 -1
- package/docs/api/ProxyAgent.md +3 -1
- package/lib/api/api-stream.js +33 -5
- package/lib/api/readable.js +18 -2
- package/lib/client.js +14 -0
- package/lib/core/symbols.js +1 -1
- package/lib/core/util.js +56 -23
- package/lib/fetch/constants.js +7 -1
- package/lib/fetch/dataURL.js +11 -2
- package/lib/fetch/formdata.js +1 -8
- package/lib/fetch/index.js +23 -15
- package/lib/fetch/request.js +31 -12
- package/lib/fetch/response.js +1 -3
- package/lib/fetch/util.js +139 -96
- package/lib/fileapi/encoding.js +5 -1
- package/lib/proxy-agent.js +12 -2
- package/lib/timers.js +21 -13
- package/lib/websocket/connection.js +12 -61
- package/lib/websocket/symbols.js +0 -3
- package/lib/websocket/websocket.js +55 -9
- package/package.json +8 -8
- package/types/balanced-pool.d.ts +3 -3
- package/types/client.d.ts +49 -27
- package/types/proxy-agent.d.ts +3 -0
- package/types/websocket.d.ts +2 -0
package/README.md
CHANGED
|
@@ -407,7 +407,7 @@ Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
|
|
|
407
407
|
|
|
408
408
|
## Workarounds
|
|
409
409
|
|
|
410
|
-
###
|
|
410
|
+
### Network address family autoselection.
|
|
411
411
|
|
|
412
412
|
If you experience problem when connecting to a remote server that is resolved by your DNS servers to a IPv6 (AAAA record)
|
|
413
413
|
first, there are chances that your local router or ISP might have problem connecting to IPv6 networks. In that case
|
package/docs/api/ProxyAgent.md
CHANGED
|
@@ -19,6 +19,7 @@ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
|
|
|
19
19
|
* **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.
|
|
20
20
|
* **token** `string` (optional) - It can be passed by a string of token for authentication.
|
|
21
21
|
* **auth** `string` (**deprecated**) - Use token.
|
|
22
|
+
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
22
23
|
|
|
23
24
|
Examples:
|
|
24
25
|
|
|
@@ -83,7 +84,8 @@ import { setGlobalDispatcher, request, ProxyAgent } from 'undici';
|
|
|
83
84
|
|
|
84
85
|
const proxyAgent = new ProxyAgent({
|
|
85
86
|
uri: 'my.proxy.server',
|
|
86
|
-
token: 'Bearer xxxx'
|
|
87
|
+
// token: 'Bearer xxxx'
|
|
88
|
+
token: `Basic ${Buffer.from('username:password').toString('base64')}`
|
|
87
89
|
});
|
|
88
90
|
setGlobalDispatcher(proxyAgent);
|
|
89
91
|
|
package/lib/api/api-stream.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { finished } = require('stream')
|
|
3
|
+
const { finished, PassThrough } = require('stream')
|
|
4
4
|
const {
|
|
5
5
|
InvalidArgumentError,
|
|
6
6
|
InvalidReturnValueError,
|
|
7
|
-
RequestAbortedError
|
|
7
|
+
RequestAbortedError,
|
|
8
|
+
ResponseStatusCodeError
|
|
8
9
|
} = require('../core/errors')
|
|
9
10
|
const util = require('../core/util')
|
|
10
11
|
const { AsyncResource } = require('async_hooks')
|
|
@@ -16,7 +17,7 @@ class StreamHandler extends AsyncResource {
|
|
|
16
17
|
throw new InvalidArgumentError('invalid opts')
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
20
|
+
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
23
|
if (typeof callback !== 'function') {
|
|
@@ -57,6 +58,7 @@ class StreamHandler extends AsyncResource {
|
|
|
57
58
|
this.trailers = null
|
|
58
59
|
this.body = body
|
|
59
60
|
this.onInfo = onInfo || null
|
|
61
|
+
this.throwOnError = throwOnError || false
|
|
60
62
|
|
|
61
63
|
if (util.isStream(body)) {
|
|
62
64
|
body.on('error', (err) => {
|
|
@@ -76,8 +78,8 @@ class StreamHandler extends AsyncResource {
|
|
|
76
78
|
this.context = context
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
onHeaders (statusCode, rawHeaders, resume) {
|
|
80
|
-
const { factory, opaque, context } = this
|
|
81
|
+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
82
|
+
const { factory, opaque, context, callback } = this
|
|
81
83
|
|
|
82
84
|
if (statusCode < 200) {
|
|
83
85
|
if (this.onInfo) {
|
|
@@ -96,6 +98,32 @@ class StreamHandler extends AsyncResource {
|
|
|
96
98
|
context
|
|
97
99
|
})
|
|
98
100
|
|
|
101
|
+
if (this.throwOnError && statusCode >= 400) {
|
|
102
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
103
|
+
const chunks = []
|
|
104
|
+
const pt = new PassThrough()
|
|
105
|
+
pt
|
|
106
|
+
.on('data', (chunk) => chunks.push(chunk))
|
|
107
|
+
.on('end', () => {
|
|
108
|
+
const payload = Buffer.concat(chunks).toString('utf8')
|
|
109
|
+
this.runInAsyncScope(
|
|
110
|
+
callback,
|
|
111
|
+
null,
|
|
112
|
+
new ResponseStatusCodeError(
|
|
113
|
+
`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`,
|
|
114
|
+
statusCode,
|
|
115
|
+
headers,
|
|
116
|
+
payload
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
.on('error', (err) => {
|
|
121
|
+
this.onError(err)
|
|
122
|
+
})
|
|
123
|
+
this.res = pt
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
99
127
|
if (
|
|
100
128
|
!res ||
|
|
101
129
|
typeof res.write !== 'function' ||
|
package/lib/api/readable.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const assert = require('assert')
|
|
6
6
|
const { Readable } = require('stream')
|
|
7
|
-
const { RequestAbortedError, NotSupportedError } = require('../core/errors')
|
|
7
|
+
const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = require('../core/errors')
|
|
8
8
|
const util = require('../core/util')
|
|
9
9
|
const { ReadableStreamFrom, toUSVString } = require('../core/util')
|
|
10
10
|
|
|
@@ -146,15 +146,31 @@ module.exports = class BodyReadable extends Readable {
|
|
|
146
146
|
|
|
147
147
|
async dump (opts) {
|
|
148
148
|
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
|
|
149
|
+
const signal = opts && opts.signal
|
|
150
|
+
const abortFn = () => {
|
|
151
|
+
this.destroy()
|
|
152
|
+
}
|
|
153
|
+
if (signal) {
|
|
154
|
+
if (typeof signal !== 'object' || !('aborted' in signal)) {
|
|
155
|
+
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
156
|
+
}
|
|
157
|
+
util.throwIfAborted(signal)
|
|
158
|
+
signal.addEventListener('abort', abortFn, { once: true })
|
|
159
|
+
}
|
|
149
160
|
try {
|
|
150
161
|
for await (const chunk of this) {
|
|
162
|
+
util.throwIfAborted(signal)
|
|
151
163
|
limit -= Buffer.byteLength(chunk)
|
|
152
164
|
if (limit < 0) {
|
|
153
165
|
return
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
} catch {
|
|
157
|
-
|
|
169
|
+
util.throwIfAborted(signal)
|
|
170
|
+
} finally {
|
|
171
|
+
if (signal) {
|
|
172
|
+
signal.removeEventListener('abort', abortFn)
|
|
173
|
+
}
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
176
|
}
|
package/lib/client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict'
|
|
2
4
|
|
|
3
5
|
/* global WebAssembly */
|
|
@@ -85,7 +87,15 @@ try {
|
|
|
85
87
|
channels.connected = { hasSubscribers: false }
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @type {import('../types/client').default}
|
|
92
|
+
*/
|
|
88
93
|
class Client extends DispatcherBase {
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param {string|URL} url
|
|
97
|
+
* @param {import('../types/client').Client.Options} options
|
|
98
|
+
*/
|
|
89
99
|
constructor (url, {
|
|
90
100
|
interceptors,
|
|
91
101
|
maxHeaderSize,
|
|
@@ -1658,6 +1668,8 @@ class AsyncWriter {
|
|
|
1658
1668
|
process.emitWarning(new RequestContentLengthMismatchError())
|
|
1659
1669
|
}
|
|
1660
1670
|
|
|
1671
|
+
socket.cork()
|
|
1672
|
+
|
|
1661
1673
|
if (bytesWritten === 0) {
|
|
1662
1674
|
if (!expectsPayload) {
|
|
1663
1675
|
socket[kReset] = true
|
|
@@ -1678,6 +1690,8 @@ class AsyncWriter {
|
|
|
1678
1690
|
|
|
1679
1691
|
const ret = socket.write(chunk)
|
|
1680
1692
|
|
|
1693
|
+
socket.uncork()
|
|
1694
|
+
|
|
1681
1695
|
request.onBodySent(chunk)
|
|
1682
1696
|
|
|
1683
1697
|
if (!ret) {
|
package/lib/core/symbols.js
CHANGED
|
@@ -41,7 +41,7 @@ module.exports = {
|
|
|
41
41
|
kClient: Symbol('client'),
|
|
42
42
|
kParser: Symbol('parser'),
|
|
43
43
|
kOnDestroyed: Symbol('destroy callbacks'),
|
|
44
|
-
kPipelining: Symbol('
|
|
44
|
+
kPipelining: Symbol('pipelining'),
|
|
45
45
|
kSocket: Symbol('socket'),
|
|
46
46
|
kHostHeader: Symbol('host header'),
|
|
47
47
|
kConnector: Symbol('connector'),
|
package/lib/core/util.js
CHANGED
|
@@ -15,7 +15,7 @@ const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(
|
|
|
15
15
|
function nop () {}
|
|
16
16
|
|
|
17
17
|
function isStream (obj) {
|
|
18
|
-
return obj && typeof obj.pipe === 'function'
|
|
18
|
+
return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
|
|
@@ -46,34 +46,40 @@ function buildURL (url, queryParams) {
|
|
|
46
46
|
function parseURL (url) {
|
|
47
47
|
if (typeof url === 'string') {
|
|
48
48
|
url = new URL(url)
|
|
49
|
+
|
|
50
|
+
if (!/^https?:/.test(url.origin || url.protocol)) {
|
|
51
|
+
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return url
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
if (!url || typeof url !== 'object') {
|
|
52
|
-
throw new InvalidArgumentError('
|
|
58
|
+
throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.')
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) {
|
|
56
|
-
throw new InvalidArgumentError('
|
|
62
|
+
throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.')
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
if (url.path != null && typeof url.path !== 'string') {
|
|
60
|
-
throw new InvalidArgumentError('
|
|
66
|
+
throw new InvalidArgumentError('Invalid URL path: the path must be a string or null/undefined.')
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
if (url.pathname != null && typeof url.pathname !== 'string') {
|
|
64
|
-
throw new InvalidArgumentError('
|
|
70
|
+
throw new InvalidArgumentError('Invalid URL pathname: the pathname must be a string or null/undefined.')
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
if (url.hostname != null && typeof url.hostname !== 'string') {
|
|
68
|
-
throw new InvalidArgumentError('
|
|
74
|
+
throw new InvalidArgumentError('Invalid URL hostname: the hostname must be a string or null/undefined.')
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
if (url.origin != null && typeof url.origin !== 'string') {
|
|
72
|
-
throw new InvalidArgumentError('
|
|
78
|
+
throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.')
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
if (!/^https?:/.test(url.origin || url.protocol)) {
|
|
76
|
-
throw new InvalidArgumentError('
|
|
82
|
+
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
if (!(url instanceof URL)) {
|
|
@@ -375,23 +381,49 @@ function ReadableStreamFrom (iterable) {
|
|
|
375
381
|
|
|
376
382
|
// The chunk should be a FormData instance and contains
|
|
377
383
|
// all the required methods.
|
|
378
|
-
function isFormDataLike (
|
|
379
|
-
return (
|
|
380
|
-
|
|
381
|
-
typeof
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
typeof chunk.keys === 'function' &&
|
|
390
|
-
typeof chunk.values === 'function' &&
|
|
391
|
-
typeof chunk.forEach === 'function')
|
|
384
|
+
function isFormDataLike (object) {
|
|
385
|
+
return (
|
|
386
|
+
object &&
|
|
387
|
+
typeof object === 'object' &&
|
|
388
|
+
typeof object.append === 'function' &&
|
|
389
|
+
typeof object.delete === 'function' &&
|
|
390
|
+
typeof object.get === 'function' &&
|
|
391
|
+
typeof object.getAll === 'function' &&
|
|
392
|
+
typeof object.has === 'function' &&
|
|
393
|
+
typeof object.set === 'function' &&
|
|
394
|
+
object[Symbol.toStringTag] === 'FormData'
|
|
392
395
|
)
|
|
393
396
|
}
|
|
394
397
|
|
|
398
|
+
function throwIfAborted (signal) {
|
|
399
|
+
if (!signal) { return }
|
|
400
|
+
if (typeof signal.throwIfAborted === 'function') {
|
|
401
|
+
signal.throwIfAborted()
|
|
402
|
+
} else {
|
|
403
|
+
if (signal.aborted) {
|
|
404
|
+
// DOMException not available < v17.0.0
|
|
405
|
+
const err = new Error('The operation was aborted')
|
|
406
|
+
err.name = 'AbortError'
|
|
407
|
+
throw err
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const hasToWellFormed = !!String.prototype.toWellFormed
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @param {string} val
|
|
416
|
+
*/
|
|
417
|
+
function toUSVString (val) {
|
|
418
|
+
if (hasToWellFormed) {
|
|
419
|
+
return `${val}`.toWellFormed()
|
|
420
|
+
} else if (nodeUtil.toUSVString) {
|
|
421
|
+
return nodeUtil.toUSVString(val)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return `${val}`
|
|
425
|
+
}
|
|
426
|
+
|
|
395
427
|
const kEnumerableProperty = Object.create(null)
|
|
396
428
|
kEnumerableProperty.enumerable = true
|
|
397
429
|
|
|
@@ -401,7 +433,7 @@ module.exports = {
|
|
|
401
433
|
isDisturbed,
|
|
402
434
|
isErrored,
|
|
403
435
|
isReadable,
|
|
404
|
-
toUSVString
|
|
436
|
+
toUSVString,
|
|
405
437
|
isReadableAborted,
|
|
406
438
|
isBlobLike,
|
|
407
439
|
parseOrigin,
|
|
@@ -423,6 +455,7 @@ module.exports = {
|
|
|
423
455
|
getSocketInfo,
|
|
424
456
|
isFormDataLike,
|
|
425
457
|
buildURL,
|
|
458
|
+
throwIfAborted,
|
|
426
459
|
nodeMajor,
|
|
427
460
|
nodeMinor,
|
|
428
461
|
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13)
|
package/lib/fetch/constants.js
CHANGED
|
@@ -48,11 +48,17 @@ const requestCache = [
|
|
|
48
48
|
'only-if-cached'
|
|
49
49
|
]
|
|
50
50
|
|
|
51
|
+
// https://fetch.spec.whatwg.org/#request-body-header-name
|
|
51
52
|
const requestBodyHeader = [
|
|
52
53
|
'content-encoding',
|
|
53
54
|
'content-language',
|
|
54
55
|
'content-location',
|
|
55
|
-
'content-type'
|
|
56
|
+
'content-type',
|
|
57
|
+
// See https://github.com/nodejs/undici/issues/2021
|
|
58
|
+
// 'Content-Length' is a forbidden header name, which is typically
|
|
59
|
+
// removed in the Headers implementation. However, undici doesn't
|
|
60
|
+
// filter out headers, so we add it here.
|
|
61
|
+
'content-length'
|
|
56
62
|
]
|
|
57
63
|
|
|
58
64
|
// https://fetch.spec.whatwg.org/#enumdef-requestduplex
|
package/lib/fetch/dataURL.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const { atob } = require('buffer')
|
|
3
|
-
const { format } = require('url')
|
|
4
3
|
const { isValidHTTPToken, isomorphicDecode } = require('./util')
|
|
5
4
|
|
|
6
5
|
const encoder = new TextEncoder()
|
|
@@ -118,7 +117,17 @@ function dataURLProcessor (dataURL) {
|
|
|
118
117
|
* @param {boolean} excludeFragment
|
|
119
118
|
*/
|
|
120
119
|
function URLSerializer (url, excludeFragment = false) {
|
|
121
|
-
|
|
120
|
+
const href = url.href
|
|
121
|
+
|
|
122
|
+
if (!excludeFragment) {
|
|
123
|
+
return href
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const hash = href.lastIndexOf('#')
|
|
127
|
+
if (hash === -1) {
|
|
128
|
+
return href
|
|
129
|
+
}
|
|
130
|
+
return href.slice(0, hash)
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
package/lib/fetch/formdata.js
CHANGED
|
@@ -61,14 +61,7 @@ class FormData {
|
|
|
61
61
|
|
|
62
62
|
// The delete(name) method steps are to remove all entries whose name
|
|
63
63
|
// is name from this’s entry list.
|
|
64
|
-
|
|
65
|
-
for (const entry of this[kState]) {
|
|
66
|
-
if (entry.name !== name) {
|
|
67
|
-
next.push(entry)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this[kState] = next
|
|
64
|
+
this[kState] = this[kState].filter(entry => entry.name !== name)
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
get (name) {
|
package/lib/fetch/index.js
CHANGED
|
@@ -37,7 +37,10 @@ const {
|
|
|
37
37
|
isErrorLike,
|
|
38
38
|
fullyReadBody,
|
|
39
39
|
readableStreamClose,
|
|
40
|
-
isomorphicEncode
|
|
40
|
+
isomorphicEncode,
|
|
41
|
+
urlIsLocal,
|
|
42
|
+
urlIsHttpHttpsScheme,
|
|
43
|
+
urlHasHttpsScheme
|
|
41
44
|
} = require('./util')
|
|
42
45
|
const { kState, kHeaders, kGuard, kRealm, kHeadersCaseInsensitive } = require('./symbols')
|
|
43
46
|
const assert = require('assert')
|
|
@@ -272,7 +275,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
|
|
|
272
275
|
let cacheState = response.cacheState
|
|
273
276
|
|
|
274
277
|
// 6. If originalURL’s scheme is not an HTTP(S) scheme, then return.
|
|
275
|
-
if (
|
|
278
|
+
if (!urlIsHttpHttpsScheme(originalURL)) {
|
|
276
279
|
return
|
|
277
280
|
}
|
|
278
281
|
|
|
@@ -297,7 +300,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
|
|
|
297
300
|
// capability.
|
|
298
301
|
// TODO: given global’s relevant settings object’s cross-origin isolated
|
|
299
302
|
// capability?
|
|
300
|
-
|
|
303
|
+
timingInfo.endTime = coarsenedSharedCurrentTime()
|
|
301
304
|
|
|
302
305
|
// 10. Set response’s timing info to timingInfo.
|
|
303
306
|
response.timingInfo = timingInfo
|
|
@@ -530,10 +533,7 @@ async function mainFetch (fetchParams, recursive = false) {
|
|
|
530
533
|
|
|
531
534
|
// 3. If request’s local-URLs-only flag is set and request’s current URL is
|
|
532
535
|
// not local, then set response to a network error.
|
|
533
|
-
if (
|
|
534
|
-
request.localURLsOnly &&
|
|
535
|
-
!/^(about|blob|data):/.test(requestCurrentURL(request).protocol)
|
|
536
|
-
) {
|
|
536
|
+
if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
|
|
537
537
|
response = makeNetworkError('local URLs only')
|
|
538
538
|
}
|
|
539
539
|
|
|
@@ -623,7 +623,7 @@ async function mainFetch (fetchParams, recursive = false) {
|
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
// request’s current URL’s scheme is not an HTTP(S) scheme
|
|
626
|
-
if (
|
|
626
|
+
if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
|
|
627
627
|
// Return a network error.
|
|
628
628
|
return makeNetworkError('URL scheme must be a HTTP(S) scheme')
|
|
629
629
|
}
|
|
@@ -1130,7 +1130,7 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1130
1130
|
|
|
1131
1131
|
// 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network
|
|
1132
1132
|
// error.
|
|
1133
|
-
if (
|
|
1133
|
+
if (!urlIsHttpHttpsScheme(locationURL)) {
|
|
1134
1134
|
return makeNetworkError('URL scheme must be a HTTP(S) scheme')
|
|
1135
1135
|
}
|
|
1136
1136
|
|
|
@@ -1205,7 +1205,7 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1205
1205
|
// 14. If request’s body is non-null, then set request’s body to the first return
|
|
1206
1206
|
// value of safely extracting request’s body’s source.
|
|
1207
1207
|
if (request.body != null) {
|
|
1208
|
-
assert(request.body.source)
|
|
1208
|
+
assert(request.body.source != null)
|
|
1209
1209
|
request.body = safelyExtractBody(request.body.source)[0]
|
|
1210
1210
|
}
|
|
1211
1211
|
|
|
@@ -1399,7 +1399,7 @@ async function httpNetworkOrCacheFetch (
|
|
|
1399
1399
|
// header if httpRequest’s header list contains that header’s name.
|
|
1400
1400
|
// TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129
|
|
1401
1401
|
if (!httpRequest.headersList.contains('accept-encoding')) {
|
|
1402
|
-
if (
|
|
1402
|
+
if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) {
|
|
1403
1403
|
httpRequest.headersList.append('accept-encoding', 'br, gzip, deflate')
|
|
1404
1404
|
} else {
|
|
1405
1405
|
httpRequest.headersList.append('accept-encoding', 'gzip, deflate')
|
|
@@ -1845,6 +1845,7 @@ async function httpNetworkFetch (
|
|
|
1845
1845
|
// 4. Set bytes to the result of handling content codings given
|
|
1846
1846
|
// codings and bytes.
|
|
1847
1847
|
let bytes
|
|
1848
|
+
let isFailure
|
|
1848
1849
|
try {
|
|
1849
1850
|
const { done, value } = await fetchParams.controller.next()
|
|
1850
1851
|
|
|
@@ -1859,6 +1860,10 @@ async function httpNetworkFetch (
|
|
|
1859
1860
|
bytes = undefined
|
|
1860
1861
|
} else {
|
|
1861
1862
|
bytes = err
|
|
1863
|
+
|
|
1864
|
+
// err may be propagated from the result of calling readablestream.cancel,
|
|
1865
|
+
// which might not be an error. https://github.com/nodejs/undici/issues/2009
|
|
1866
|
+
isFailure = true
|
|
1862
1867
|
}
|
|
1863
1868
|
}
|
|
1864
1869
|
|
|
@@ -1878,7 +1883,7 @@ async function httpNetworkFetch (
|
|
|
1878
1883
|
timingInfo.decodedBodySize += bytes?.byteLength ?? 0
|
|
1879
1884
|
|
|
1880
1885
|
// 6. If bytes is failure, then terminate fetchParams’s controller.
|
|
1881
|
-
if (
|
|
1886
|
+
if (isFailure) {
|
|
1882
1887
|
fetchParams.controller.terminate(bytes)
|
|
1883
1888
|
return
|
|
1884
1889
|
}
|
|
@@ -1979,7 +1984,9 @@ async function httpNetworkFetch (
|
|
|
1979
1984
|
const val = headersList[n + 1].toString('latin1')
|
|
1980
1985
|
|
|
1981
1986
|
if (key.toLowerCase() === 'content-encoding') {
|
|
1982
|
-
|
|
1987
|
+
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
|
|
1988
|
+
// "All content-coding values are case-insensitive..."
|
|
1989
|
+
codings = val.toLowerCase().split(',').map((x) => x.trim())
|
|
1983
1990
|
} else if (key.toLowerCase() === 'location') {
|
|
1984
1991
|
location = val
|
|
1985
1992
|
}
|
|
@@ -1998,9 +2005,10 @@ async function httpNetworkFetch (
|
|
|
1998
2005
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
|
1999
2006
|
if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
|
|
2000
2007
|
for (const coding of codings) {
|
|
2001
|
-
|
|
2008
|
+
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
|
|
2009
|
+
if (coding === 'x-gzip' || coding === 'gzip') {
|
|
2002
2010
|
decoders.push(zlib.createGunzip())
|
|
2003
|
-
} else if (
|
|
2011
|
+
} else if (coding === 'deflate') {
|
|
2004
2012
|
decoders.push(zlib.createInflate())
|
|
2005
2013
|
} else if (coding === 'br') {
|
|
2006
2014
|
decoders.push(zlib.createBrotliDecompress())
|
package/lib/fetch/request.js
CHANGED
|
@@ -9,7 +9,8 @@ const util = require('../core/util')
|
|
|
9
9
|
const {
|
|
10
10
|
isValidHTTPToken,
|
|
11
11
|
sameOrigin,
|
|
12
|
-
normalizeMethod
|
|
12
|
+
normalizeMethod,
|
|
13
|
+
makePolicyContainer
|
|
13
14
|
} = require('./util')
|
|
14
15
|
const {
|
|
15
16
|
forbiddenMethods,
|
|
@@ -33,6 +34,7 @@ const { setMaxListeners, getEventListeners, defaultMaxListeners } = require('eve
|
|
|
33
34
|
let TransformStream = globalThis.TransformStream
|
|
34
35
|
|
|
35
36
|
const kInit = Symbol('init')
|
|
37
|
+
const kAbortController = Symbol('abortController')
|
|
36
38
|
|
|
37
39
|
const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
|
|
38
40
|
signal.removeEventListener('abort', abort)
|
|
@@ -51,10 +53,14 @@ class Request {
|
|
|
51
53
|
input = webidl.converters.RequestInfo(input)
|
|
52
54
|
init = webidl.converters.RequestInit(init)
|
|
53
55
|
|
|
54
|
-
//
|
|
56
|
+
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
|
|
55
57
|
this[kRealm] = {
|
|
56
58
|
settingsObject: {
|
|
57
|
-
baseUrl: getGlobalOrigin()
|
|
59
|
+
baseUrl: getGlobalOrigin(),
|
|
60
|
+
get origin () {
|
|
61
|
+
return this.baseUrl?.origin
|
|
62
|
+
},
|
|
63
|
+
policyContainer: makePolicyContainer()
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -123,12 +129,12 @@ class Request {
|
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
// 10. If init["window"] exists and is non-null, then throw a TypeError.
|
|
126
|
-
if (init.window
|
|
132
|
+
if (init.window != null) {
|
|
127
133
|
throw new TypeError(`'window' option '${window}' must be null`)
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
// 11. If init["window"] exists, then set window to "no-window".
|
|
131
|
-
if (
|
|
137
|
+
if ('window' in init) {
|
|
132
138
|
window = 'no-window'
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -349,17 +355,30 @@ class Request {
|
|
|
349
355
|
if (signal.aborted) {
|
|
350
356
|
ac.abort(signal.reason)
|
|
351
357
|
} else {
|
|
358
|
+
// Keep a strong ref to ac while request object
|
|
359
|
+
// is alive. This is needed to prevent AbortController
|
|
360
|
+
// from being prematurely garbage collected.
|
|
361
|
+
// See, https://github.com/nodejs/undici/issues/1926.
|
|
362
|
+
this[kAbortController] = ac
|
|
363
|
+
|
|
352
364
|
const acRef = new WeakRef(ac)
|
|
353
365
|
const abort = function () {
|
|
354
|
-
acRef.deref()
|
|
366
|
+
const ac = acRef.deref()
|
|
367
|
+
if (ac !== undefined) {
|
|
368
|
+
ac.abort(this.reason)
|
|
369
|
+
}
|
|
355
370
|
}
|
|
356
371
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
372
|
+
// Third-party AbortControllers may not work with these.
|
|
373
|
+
// See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619.
|
|
374
|
+
try {
|
|
375
|
+
if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
|
|
376
|
+
setMaxListeners(100, signal)
|
|
377
|
+
}
|
|
378
|
+
} catch {}
|
|
360
379
|
|
|
361
380
|
signal.addEventListener('abort', abort, { once: true })
|
|
362
|
-
requestFinalizer.register(
|
|
381
|
+
requestFinalizer.register(ac, { signal, abort })
|
|
363
382
|
}
|
|
364
383
|
}
|
|
365
384
|
|
|
@@ -419,7 +438,7 @@ class Request {
|
|
|
419
438
|
// non-null, and request’s method is `GET` or `HEAD`, then throw a
|
|
420
439
|
// TypeError.
|
|
421
440
|
if (
|
|
422
|
-
(
|
|
441
|
+
(init.body != null || inputBody != null) &&
|
|
423
442
|
(request.method === 'GET' || request.method === 'HEAD')
|
|
424
443
|
) {
|
|
425
444
|
throw new TypeError('Request with GET/HEAD method cannot have body.')
|
|
@@ -429,7 +448,7 @@ class Request {
|
|
|
429
448
|
let initBody = null
|
|
430
449
|
|
|
431
450
|
// 36. If init["body"] exists and is non-null, then:
|
|
432
|
-
if (init.body
|
|
451
|
+
if (init.body != null) {
|
|
433
452
|
// 1. Let Content-Type be null.
|
|
434
453
|
// 2. Set initBody and Content-Type to the result of extracting
|
|
435
454
|
// init["body"], with keepalive set to request’s keepalive.
|
package/lib/fetch/response.js
CHANGED
|
@@ -348,9 +348,7 @@ function makeNetworkError (reason) {
|
|
|
348
348
|
status: 0,
|
|
349
349
|
error: isError
|
|
350
350
|
? reason
|
|
351
|
-
: new Error(reason ? String(reason) : reason,
|
|
352
|
-
cause: isError ? reason : undefined
|
|
353
|
-
}),
|
|
351
|
+
: new Error(reason ? String(reason) : reason),
|
|
354
352
|
aborted: reason && reason.name === 'AbortError'
|
|
355
353
|
})
|
|
356
354
|
}
|