undici 4.11.3 → 4.12.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/README.md +8 -8
- package/lib/core/util.js +22 -7
- package/lib/fetch/body.js +3 -13
- package/lib/fetch/index.js +107 -110
- package/lib/fetch/request.js +61 -13
- package/lib/fetch/symbols.js +1 -2
- package/lib/fetch/util.js +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -203,13 +203,13 @@ not support or does not fully implement.
|
|
|
203
203
|
* https://fetch.spec.whatwg.org/#garbage-collection
|
|
204
204
|
|
|
205
205
|
The [Fetch Standard](https://fetch.spec.whatwg.org) allows users to skip consuming the response body by relying on
|
|
206
|
-
[garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#garbage_collection) to release connection resources. Undici does the same.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
[garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#garbage_collection) to release connection resources. Undici does not do the same. Therefore, it is important to always either consume or cancel the response body.
|
|
207
|
+
|
|
208
|
+
Garbage collection in Node is less aggressive and deterministic
|
|
209
|
+
(due to the lack of clear idle periods that browser have through the rendering refresh rate)
|
|
210
|
+
which means that leaving the release of connection resources to the garbage collector can lead
|
|
211
|
+
to excessive connection usage, reduced performance (due to less connection re-use), and even
|
|
212
|
+
stalls or deadlocks when running out of connections.
|
|
213
213
|
|
|
214
214
|
```js
|
|
215
215
|
// Do
|
|
@@ -289,7 +289,7 @@ pipeline requests, without checking whether the connection is persistent.
|
|
|
289
289
|
Hence, automatic fallback to HTTP/1.0 or HTTP/1.1 without pipelining is
|
|
290
290
|
not supported.
|
|
291
291
|
|
|
292
|
-
Undici will immediately pipeline when retrying requests
|
|
292
|
+
Undici will immediately pipeline when retrying requests after a failed
|
|
293
293
|
connection. However, Undici will not retry the first remaining requests in
|
|
294
294
|
the prior pipeline and instead error the corresponding callback/promise/stream.
|
|
295
295
|
|
package/lib/core/util.js
CHANGED
|
@@ -150,7 +150,7 @@ function isDestroyed (stream) {
|
|
|
150
150
|
return !stream || !!(stream.destroyed || stream[kDestroyed])
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function
|
|
153
|
+
function isReadableAborted (stream) {
|
|
154
154
|
const state = stream && stream._readableState
|
|
155
155
|
return isDestroyed(stream) && state && !state.endEmitted
|
|
156
156
|
}
|
|
@@ -244,15 +244,28 @@ function validateHandler (handler, method, upgrade) {
|
|
|
244
244
|
// A body is disturbed if it has been read from and it cannot
|
|
245
245
|
// be re-used without losing state or data.
|
|
246
246
|
function isDisturbed (body) {
|
|
247
|
-
const state = body && body._readableState
|
|
248
247
|
return !!(body && (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
stream.isDisturbed
|
|
249
|
+
? stream.isDisturbed(body) || body[kBodyUsed] // TODO (fix): Why is body[kBodyUsed] needed?
|
|
250
|
+
: body[kBodyUsed] ||
|
|
251
|
+
body.readableDidRead ||
|
|
252
|
+
(body._readableState && body._readableState.dataEmitted) ||
|
|
253
|
+
isReadableAborted(body)
|
|
253
254
|
))
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
function isErrored (body) {
|
|
258
|
+
return !!(body && (
|
|
259
|
+
stream.isErrored
|
|
260
|
+
? stream.isErrored(body)
|
|
261
|
+
: /state: 'errored'/.test(nodeUtil.inspect(body)
|
|
262
|
+
)))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function isReadable (body) {
|
|
266
|
+
return !!(body && /state: 'readable'/.test(nodeUtil.inspect(body)))
|
|
267
|
+
}
|
|
268
|
+
|
|
256
269
|
function getSocketInfo (socket) {
|
|
257
270
|
return {
|
|
258
271
|
localAddress: socket.localAddress,
|
|
@@ -310,8 +323,10 @@ module.exports = {
|
|
|
310
323
|
kEnumerableProperty,
|
|
311
324
|
nop,
|
|
312
325
|
isDisturbed,
|
|
326
|
+
isErrored,
|
|
327
|
+
isReadable,
|
|
313
328
|
toUSVString: nodeUtil.toUSVString || ((val) => `${val}`),
|
|
314
|
-
|
|
329
|
+
isReadableAborted,
|
|
315
330
|
isBlobLike,
|
|
316
331
|
parseOrigin,
|
|
317
332
|
parseURL,
|
package/lib/fetch/body.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
const util = require('../core/util')
|
|
4
4
|
const { ReadableStreamFrom, toUSVString, isBlobLike } = require('./util')
|
|
5
5
|
const { FormData } = require('./formdata')
|
|
6
|
-
const { kState
|
|
6
|
+
const { kState } = require('./symbols')
|
|
7
7
|
const { Blob } = require('buffer')
|
|
8
8
|
const { kBodyUsed } = require('../core/symbols')
|
|
9
9
|
const assert = require('assert')
|
|
10
10
|
const nodeUtil = require('util')
|
|
11
11
|
const { NotSupportedError } = require('../core/errors')
|
|
12
|
+
const { isErrored } = require('../core/util')
|
|
12
13
|
|
|
13
14
|
let ReadableStream
|
|
14
15
|
|
|
@@ -187,7 +188,7 @@ function extractBody (object, keepalive = false) {
|
|
|
187
188
|
// Whenever one or more bytes are available and stream is not errored,
|
|
188
189
|
// enqueue a Uint8Array wrapping an ArrayBuffer containing the available
|
|
189
190
|
// bytes into stream.
|
|
190
|
-
if (
|
|
191
|
+
if (!isErrored(stream)) {
|
|
191
192
|
controller.enqueue(new Uint8Array(value))
|
|
192
193
|
}
|
|
193
194
|
}
|
|
@@ -268,10 +269,6 @@ const methods = {
|
|
|
268
269
|
if (this[kState].body) {
|
|
269
270
|
const stream = this[kState].body.stream
|
|
270
271
|
|
|
271
|
-
if (stream[kError]) {
|
|
272
|
-
throw stream[kError]
|
|
273
|
-
}
|
|
274
|
-
|
|
275
272
|
if (util.isDisturbed(stream)) {
|
|
276
273
|
throw new TypeError('disturbed')
|
|
277
274
|
}
|
|
@@ -352,19 +349,12 @@ const properties = {
|
|
|
352
349
|
}
|
|
353
350
|
}
|
|
354
351
|
|
|
355
|
-
function cancelBody (body, reason) {
|
|
356
|
-
if (body.stream && !/state: 'errored'/.test(nodeUtil.inspect(body.stream))) {
|
|
357
|
-
body.stream.cancel(reason)
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
352
|
function mixinBody (prototype) {
|
|
362
353
|
Object.assign(prototype, methods)
|
|
363
354
|
Object.defineProperties(prototype, properties)
|
|
364
355
|
}
|
|
365
356
|
|
|
366
357
|
module.exports = {
|
|
367
|
-
cancelBody,
|
|
368
358
|
extractBody,
|
|
369
359
|
safelyExtractBody,
|
|
370
360
|
cloneBody,
|
package/lib/fetch/index.js
CHANGED
|
@@ -24,17 +24,17 @@ const {
|
|
|
24
24
|
requestCurrentURL,
|
|
25
25
|
setRequestReferrerPolicyOnRedirect,
|
|
26
26
|
tryUpgradeRequestToAPotentiallyTrustworthyURL,
|
|
27
|
-
|
|
27
|
+
createOpaqueTimingInfo,
|
|
28
28
|
appendFetchMetadata,
|
|
29
29
|
corsCheck,
|
|
30
30
|
crossOriginResourcePolicyCheck,
|
|
31
31
|
determineRequestsReferrer,
|
|
32
32
|
coarsenedSharedCurrentTime
|
|
33
33
|
} = require('./util')
|
|
34
|
-
const { kState, kHeaders, kGuard, kRealm
|
|
34
|
+
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
|
|
35
35
|
const { AbortError } = require('../core/errors')
|
|
36
36
|
const assert = require('assert')
|
|
37
|
-
const { safelyExtractBody
|
|
37
|
+
const { safelyExtractBody } = require('./body')
|
|
38
38
|
const {
|
|
39
39
|
redirectStatus,
|
|
40
40
|
nullBodyStatus,
|
|
@@ -44,14 +44,32 @@ const {
|
|
|
44
44
|
} = require('./constants')
|
|
45
45
|
const { kHeadersList } = require('../core/symbols')
|
|
46
46
|
const EE = require('events')
|
|
47
|
-
const { PassThrough, pipeline
|
|
47
|
+
const { PassThrough, pipeline } = require('stream')
|
|
48
|
+
const { isErrored, isReadable } = require('../core/util')
|
|
48
49
|
|
|
49
50
|
let ReadableStream
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
class Fetch extends EE {
|
|
53
|
+
constructor (dispatcher) {
|
|
54
|
+
super()
|
|
55
|
+
|
|
56
|
+
this.dispatcher = dispatcher
|
|
57
|
+
this.terminated = null
|
|
58
|
+
this.connection = null
|
|
59
|
+
this.dump = false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
terminate ({ reason, aborted } = {}) {
|
|
63
|
+
if (this.terminated) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
this.terminated = { aborted, reason }
|
|
67
|
+
|
|
68
|
+
this.connection?.destroy(reason)
|
|
69
|
+
|
|
70
|
+
this.emit('terminated', reason)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
55
73
|
|
|
56
74
|
// https://fetch.spec.whatwg.org/#fetch-method
|
|
57
75
|
async function fetch (...args) {
|
|
@@ -73,25 +91,7 @@ async function fetch (...args) {
|
|
|
73
91
|
const resource = args[0]
|
|
74
92
|
const init = args.length >= 1 ? args[1] ?? {} : {}
|
|
75
93
|
|
|
76
|
-
const context =
|
|
77
|
-
dispatcher: this,
|
|
78
|
-
terminated: false,
|
|
79
|
-
connection: null,
|
|
80
|
-
dump: false,
|
|
81
|
-
terminate ({ reason, aborted } = {}) {
|
|
82
|
-
if (this.terminated) {
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
this.terminated = { aborted }
|
|
86
|
-
|
|
87
|
-
if (this.connection) {
|
|
88
|
-
this.connection.destroy(reason)
|
|
89
|
-
this.connection = null
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
this.emit('terminated', reason)
|
|
93
|
-
}
|
|
94
|
-
})
|
|
94
|
+
const context = new Fetch(this)
|
|
95
95
|
|
|
96
96
|
// 1. Let p be a new promise.
|
|
97
97
|
const p = createDeferredPromise()
|
|
@@ -153,7 +153,7 @@ async function fetch (...args) {
|
|
|
153
153
|
const handleFetchDone = (response) =>
|
|
154
154
|
finalizeAndReportTiming(response, 'fetch')
|
|
155
155
|
|
|
156
|
-
// 12. Fetch request with
|
|
156
|
+
// 12. Fetch request with processResponseEndOfBody set to handleFetchDone,
|
|
157
157
|
// and processResponse given response being these substeps:
|
|
158
158
|
const processResponse = (response) => {
|
|
159
159
|
// 1. If locallyAborted is true, terminate these substeps.
|
|
@@ -193,7 +193,7 @@ async function fetch (...args) {
|
|
|
193
193
|
fetching
|
|
194
194
|
.call(context, {
|
|
195
195
|
request,
|
|
196
|
-
|
|
196
|
+
processResponseEndOfBody: handleFetchDone,
|
|
197
197
|
processResponse
|
|
198
198
|
})
|
|
199
199
|
.catch((err) => {
|
|
@@ -226,11 +226,9 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
|
|
|
226
226
|
|
|
227
227
|
// 6. If response’s timing allow passed flag is not set, then:
|
|
228
228
|
if (!timingInfo.timingAllowPassed) {
|
|
229
|
-
// 1. Set timingInfo to a
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
startTime: timingInfo.startTime,
|
|
233
|
-
postRedirectStartTime: timingInfo.postRedirectStartTime
|
|
229
|
+
// 1. Set timingInfo to a the result of creating an opaque timing info for timingInfo.
|
|
230
|
+
timingInfo = createOpaqueTimingInfo({
|
|
231
|
+
startTime: timingInfo.startTime
|
|
234
232
|
})
|
|
235
233
|
|
|
236
234
|
// 2. Set cacheState to the empty string.
|
|
@@ -273,8 +271,14 @@ function abortFetch (p, request, responseObject) {
|
|
|
273
271
|
|
|
274
272
|
// 3. If request’s body is not null and is readable, then cancel request’s
|
|
275
273
|
// body with error.
|
|
276
|
-
if (request.body != null) {
|
|
277
|
-
|
|
274
|
+
if (request.body != null && isReadable(request.body?.stream)) {
|
|
275
|
+
request.body.stream.cancel(error).catch((err) => {
|
|
276
|
+
if (err.code === 'ERR_INVALID_STATE') {
|
|
277
|
+
// Node bug?
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
throw err
|
|
281
|
+
})
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
// 4. If responseObject is null, then return.
|
|
@@ -287,13 +291,27 @@ function abortFetch (p, request, responseObject) {
|
|
|
287
291
|
|
|
288
292
|
// 6. If response’s body is not null and is readable, then error response’s
|
|
289
293
|
// body with error.
|
|
290
|
-
if (response.body != null) {
|
|
291
|
-
|
|
294
|
+
if (response.body != null && isReadable(response.body?.stream)) {
|
|
295
|
+
response.body.stream.cancel(error).catch((err) => {
|
|
296
|
+
if (err.code === 'ERR_INVALID_STATE') {
|
|
297
|
+
// Node bug?
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
throw err
|
|
301
|
+
})
|
|
292
302
|
}
|
|
293
303
|
}
|
|
294
304
|
|
|
295
305
|
// https://fetch.spec.whatwg.org/#fetching
|
|
296
|
-
function fetching ({
|
|
306
|
+
function fetching ({
|
|
307
|
+
request,
|
|
308
|
+
processRequestBodyChunkLength,
|
|
309
|
+
processRequestEndOfBody,
|
|
310
|
+
processResponse,
|
|
311
|
+
processResponseEndOfBody,
|
|
312
|
+
processResponseConsumeBody,
|
|
313
|
+
useParallelQueue = false,
|
|
314
|
+
}) {
|
|
297
315
|
// 1. Let taskDestination be null.
|
|
298
316
|
let taskDestination = null
|
|
299
317
|
|
|
@@ -319,26 +337,28 @@ function fetching ({ request, processResponse, processResponseDone }) {
|
|
|
319
337
|
// post-redirect start time are the coarsened shared current time given
|
|
320
338
|
// crossOriginIsolatedCapability.
|
|
321
339
|
const currenTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability)
|
|
322
|
-
const timingInfo =
|
|
323
|
-
startTime: currenTime
|
|
324
|
-
postRedirectStartTime: currenTime
|
|
340
|
+
const timingInfo = createOpaqueTimingInfo({
|
|
341
|
+
startTime: currenTime
|
|
325
342
|
})
|
|
326
343
|
|
|
327
|
-
// 6. Let fetchParams be a new fetch params whose
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
// is
|
|
344
|
+
// 6. Let fetchParams be a new fetch params whose
|
|
345
|
+
// request is request,
|
|
346
|
+
// timing info is timingInfo,
|
|
347
|
+
// process request body chunk length is processRequestBodyChunkLength,
|
|
348
|
+
// process request end-of-body is processRequestEndOfBody,
|
|
349
|
+
// process response is processResponse,
|
|
350
|
+
// process response consume body is processResponseConsumeBody,
|
|
351
|
+
// process response end-of-body is processResponseEndOfBody,
|
|
352
|
+
// task destination is taskDestination,
|
|
353
|
+
// and cross-origin isolated capability is crossOriginIsolatedCapability.
|
|
334
354
|
const fetchParams = {
|
|
335
355
|
request,
|
|
336
356
|
timingInfo,
|
|
337
|
-
|
|
338
|
-
processRequestEndOfBody
|
|
357
|
+
processRequestBodyChunkLength,
|
|
358
|
+
processRequestEndOfBody,
|
|
339
359
|
processResponse,
|
|
340
|
-
|
|
341
|
-
|
|
360
|
+
processResponseConsumeBody,
|
|
361
|
+
processResponseEndOfBody,
|
|
342
362
|
taskDestination,
|
|
343
363
|
crossOriginIsolatedCapability
|
|
344
364
|
}
|
|
@@ -656,10 +676,9 @@ async function mainFetch (fetchParams, recursive = false) {
|
|
|
656
676
|
const processBodyError = (reason) =>
|
|
657
677
|
fetchFinale(fetchParams, makeNetworkError(reason))
|
|
658
678
|
|
|
659
|
-
// 2. If request’s response tainting is "opaque", response is
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
if (request.responseTainting === 'opaque' && response.status === 0) {
|
|
679
|
+
// 2. If request’s response tainting is "opaque", or response’s body is null,
|
|
680
|
+
// then run processBodyError and abort these steps.
|
|
681
|
+
if (request.responseTainting === 'opaque' || response.body == null) {
|
|
663
682
|
processBodyError(response.error)
|
|
664
683
|
return
|
|
665
684
|
}
|
|
@@ -717,13 +736,13 @@ function fetchFinale (fetchParams, response) {
|
|
|
717
736
|
fetchParams.processResponse(response)
|
|
718
737
|
}
|
|
719
738
|
|
|
720
|
-
// 2. If fetchParams’s process response
|
|
739
|
+
// 2. If fetchParams’s process response consume is non-null, then:.
|
|
721
740
|
// TODO
|
|
722
741
|
// 1. Let processBody given nullOrBytes be this step: run fetchParams’s
|
|
723
|
-
// process response
|
|
742
|
+
// process response consume given response and nullOrBytes.on.
|
|
724
743
|
// TODO
|
|
725
744
|
// 2. Let processBodyError be this step: run fetchParams’s process
|
|
726
|
-
// response
|
|
745
|
+
// response consume given response and failure.on.
|
|
727
746
|
// TODO
|
|
728
747
|
// 3. If response’s body is null, then queue a fetch task to run
|
|
729
748
|
// processBody given null, with fetchParams’s task destination.on.
|
|
@@ -1256,11 +1275,15 @@ async function httpNetworkOrCacheFetch (
|
|
|
1256
1275
|
// 3. If the ongoing fetch is terminated, then:
|
|
1257
1276
|
if (context.terminated) {
|
|
1258
1277
|
// 1. Let aborted be the termination’s aborted flag.
|
|
1278
|
+
const aborted = context.terminated.aborted
|
|
1279
|
+
|
|
1259
1280
|
// 2. If aborted is set, then return an aborted network error.
|
|
1281
|
+
if (aborted) {
|
|
1282
|
+
return makeNetworkError(new AbortError())
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1260
1285
|
// 3. Return a network error.
|
|
1261
|
-
return makeNetworkError(
|
|
1262
|
-
context.terminated.aborted ? new AbortError() : null
|
|
1263
|
-
)
|
|
1286
|
+
return makeNetworkError(context.terminated.reason)
|
|
1264
1287
|
}
|
|
1265
1288
|
|
|
1266
1289
|
// 4. Prompt the end user as appropriate in request’s window and store
|
|
@@ -1290,10 +1313,12 @@ async function httpNetworkOrCacheFetch (
|
|
|
1290
1313
|
const aborted = context.terminated.aborted
|
|
1291
1314
|
|
|
1292
1315
|
// 2. If aborted is set, then return an aborted network error.
|
|
1293
|
-
|
|
1316
|
+
if (aborted) {
|
|
1317
|
+
return makeNetworkError(new AbortError())
|
|
1318
|
+
}
|
|
1294
1319
|
|
|
1295
1320
|
// 3. Return a network error.
|
|
1296
|
-
return makeNetworkError(reason)
|
|
1321
|
+
return makeNetworkError(context.terminated.reason)
|
|
1297
1322
|
}
|
|
1298
1323
|
|
|
1299
1324
|
// 2. Set response to the result of running HTTP-network-or-cache
|
|
@@ -1480,13 +1505,15 @@ function httpNetworkFetch (
|
|
|
1480
1505
|
const aborted = this.terminated.aborted
|
|
1481
1506
|
|
|
1482
1507
|
// 2. If connection uses HTTP/2, then transmit an RST_STREAM frame.
|
|
1483
|
-
this.connection
|
|
1508
|
+
this.connection.destroy()
|
|
1484
1509
|
|
|
1485
1510
|
// 3. If aborted is set, then return an aborted network error.
|
|
1486
|
-
|
|
1511
|
+
if (aborted) {
|
|
1512
|
+
return resolve(makeNetworkError(new AbortError()))
|
|
1513
|
+
}
|
|
1487
1514
|
|
|
1488
1515
|
// 4. Return a network error.
|
|
1489
|
-
resolve(makeNetworkError(reason))
|
|
1516
|
+
return resolve(makeNetworkError(this.terminated.reason))
|
|
1490
1517
|
}
|
|
1491
1518
|
|
|
1492
1519
|
// 10. Let pullAlgorithm be an action that resumes the ongoing fetch
|
|
@@ -1531,7 +1558,6 @@ function httpNetworkFetch (
|
|
|
1531
1558
|
await pullAlgorithm(controller)
|
|
1532
1559
|
},
|
|
1533
1560
|
async cancel (reason) {
|
|
1534
|
-
stream[kError] = reason
|
|
1535
1561
|
await cancelAlgorithm(reason)
|
|
1536
1562
|
}
|
|
1537
1563
|
},
|
|
@@ -1570,29 +1596,19 @@ function httpNetworkFetch (
|
|
|
1570
1596
|
response.aborted = true
|
|
1571
1597
|
|
|
1572
1598
|
// 2. If stream is readable, error stream with an "AbortError" DOMException.
|
|
1573
|
-
|
|
1599
|
+
if (isReadable(stream)) {
|
|
1574
1600
|
this.controller.error(new AbortError())
|
|
1575
|
-
} catch (err) {
|
|
1576
|
-
// Will throw TypeError if body is not readable.
|
|
1577
|
-
if (err.name !== 'TypeError') {
|
|
1578
|
-
throw err
|
|
1579
|
-
}
|
|
1580
1601
|
}
|
|
1581
1602
|
} else {
|
|
1582
1603
|
// 4. Otherwise, if stream is readable, error stream with a TypeError.
|
|
1583
|
-
|
|
1604
|
+
if (isReadable(stream)) {
|
|
1584
1605
|
this.controller.error(new TypeError('terminated'))
|
|
1585
|
-
} catch (err) {
|
|
1586
|
-
// Will throw TypeError if body is not readable.
|
|
1587
|
-
if (err.name !== 'TypeError') {
|
|
1588
|
-
throw err
|
|
1589
|
-
}
|
|
1590
1606
|
}
|
|
1591
1607
|
}
|
|
1592
1608
|
|
|
1593
1609
|
// 5. If connection uses HTTP/2, then transmit an RST_STREAM frame.
|
|
1594
1610
|
// 6. Otherwise, the user agent should close connection unless it would be bad for performance to do so.
|
|
1595
|
-
this.connection
|
|
1611
|
+
this.connection.destroy()
|
|
1596
1612
|
}
|
|
1597
1613
|
|
|
1598
1614
|
// 19. Return response.
|
|
@@ -1638,8 +1654,6 @@ function httpNetworkFetch (
|
|
|
1638
1654
|
)
|
|
1639
1655
|
}
|
|
1640
1656
|
|
|
1641
|
-
registry.register(stream, this.abort, this)
|
|
1642
|
-
|
|
1643
1657
|
response = makeResponse({
|
|
1644
1658
|
status,
|
|
1645
1659
|
statusText,
|
|
@@ -1672,29 +1686,16 @@ function httpNetworkFetch (
|
|
|
1672
1686
|
}
|
|
1673
1687
|
}
|
|
1674
1688
|
|
|
1675
|
-
let iterator
|
|
1676
|
-
|
|
1677
1689
|
if (decoders.length > 1) {
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
this.decoder = new PassThrough()
|
|
1683
|
-
iterator = pipeline(this.decoder, ...decoders, () => {})[
|
|
1684
|
-
Symbol.asyncIterator
|
|
1685
|
-
]()
|
|
1686
|
-
}
|
|
1687
|
-
} else if (decoders.length === 1) {
|
|
1688
|
-
this.decoder = decoders[0]
|
|
1689
|
-
iterator = this.decoder[Symbol.asyncIterator]()
|
|
1690
|
-
} else {
|
|
1691
|
-
this.decoder = new PassThrough()
|
|
1692
|
-
iterator = this.decoder[Symbol.asyncIterator]()
|
|
1690
|
+
pipeline(...decoders, () => {})
|
|
1691
|
+
} else if (decoders.length === 0) {
|
|
1692
|
+
// TODO (perf): Avoid intermediate.
|
|
1693
|
+
decoders.push(new PassThrough())
|
|
1693
1694
|
}
|
|
1694
1695
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1696
|
+
this.decoder = decoders[0].on('drain', resume)
|
|
1697
|
+
|
|
1698
|
+
const iterator = decoders[decoders.length - 1][Symbol.asyncIterator]()
|
|
1698
1699
|
|
|
1699
1700
|
pullAlgorithm = async (controller) => {
|
|
1700
1701
|
// 4. Set bytes to the result of handling content codings given
|
|
@@ -1738,8 +1739,8 @@ function httpNetworkFetch (
|
|
|
1738
1739
|
controller.enqueue(new Uint8Array(bytes))
|
|
1739
1740
|
|
|
1740
1741
|
// 8. If stream is errored, then terminate the ongoing fetch.
|
|
1741
|
-
if (stream
|
|
1742
|
-
this.context.terminate(
|
|
1742
|
+
if (isErrored(stream)) {
|
|
1743
|
+
this.context.terminate()
|
|
1743
1744
|
return
|
|
1744
1745
|
}
|
|
1745
1746
|
|
|
@@ -1782,14 +1783,10 @@ function httpNetworkFetch (
|
|
|
1782
1783
|
},
|
|
1783
1784
|
|
|
1784
1785
|
onComplete () {
|
|
1785
|
-
registry.unregister(this)
|
|
1786
|
-
|
|
1787
1786
|
this.decoder.end()
|
|
1788
1787
|
},
|
|
1789
1788
|
|
|
1790
1789
|
onError (error) {
|
|
1791
|
-
registry.unregister(this)
|
|
1792
|
-
|
|
1793
1790
|
this.decoder?.destroy(error)
|
|
1794
1791
|
|
|
1795
1792
|
this.context.terminate({ reason: error })
|
package/lib/fetch/request.js
CHANGED
|
@@ -28,6 +28,10 @@ let TransformStream
|
|
|
28
28
|
|
|
29
29
|
const kInit = Symbol('init')
|
|
30
30
|
|
|
31
|
+
const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
|
|
32
|
+
signal.removeEventListener('abort', abort)
|
|
33
|
+
})
|
|
34
|
+
|
|
31
35
|
// https://fetch.spec.whatwg.org/#request-class
|
|
32
36
|
class Request {
|
|
33
37
|
// https://fetch.spec.whatwg.org/#dom-request
|
|
@@ -134,8 +138,48 @@ class Request {
|
|
|
134
138
|
|
|
135
139
|
// 12. Set request to a new request with the following properties:
|
|
136
140
|
request = makeRequest({
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
// URL request’s URL.
|
|
142
|
+
// undici implementation note: this is set as the first item in request's urlList in makeRequest
|
|
143
|
+
// method request’s method.
|
|
144
|
+
method: request.method,
|
|
145
|
+
// header list A copy of request’s header list.
|
|
146
|
+
// undici implementation note: headersList is cloned in makeRequest
|
|
147
|
+
headersList: request.headersList,
|
|
148
|
+
// unsafe-request flag Set.
|
|
149
|
+
unsafeRequest: request.unsafeRequest,
|
|
150
|
+
// client This’s relevant settings object.
|
|
151
|
+
client: request.client,
|
|
152
|
+
// window window.
|
|
153
|
+
window,
|
|
154
|
+
// priority request’s priority.
|
|
155
|
+
priority: request.priority,
|
|
156
|
+
// origin request’s origin. The propagation of the origin is only significant for navigation requests
|
|
157
|
+
// being handled by a service worker. In this scenario a request can have an origin that is different
|
|
158
|
+
// from the current client.
|
|
159
|
+
origin: request.origin,
|
|
160
|
+
// referrer request’s referrer.
|
|
161
|
+
referrer: request.referrer,
|
|
162
|
+
// referrer policy request’s referrer policy.
|
|
163
|
+
referrerPolicy: request.referrerPolicy,
|
|
164
|
+
// mode request’s mode.
|
|
165
|
+
mode: request.mode,
|
|
166
|
+
// credentials mode request’s credentials mode.
|
|
167
|
+
credentials: request.credentials,
|
|
168
|
+
// cache mode request’s cache mode.
|
|
169
|
+
cache: request.cache,
|
|
170
|
+
// redirect mode request’s redirect mode.
|
|
171
|
+
redirect: request.redirect,
|
|
172
|
+
// integrity metadata request’s integrity metadata.
|
|
173
|
+
integrity: request.integrity,
|
|
174
|
+
// keepalive request’s keepalive.
|
|
175
|
+
keepalive: request.keepalive,
|
|
176
|
+
// reload-navigation flag request’s reload-navigation flag.
|
|
177
|
+
reloadNavigation: request.reloadNavigation,
|
|
178
|
+
// history-navigation flag request’s history-navigation flag.
|
|
179
|
+
historyNavigation: request.historyNavigation,
|
|
180
|
+
// URL list A clone of request’s URL list.
|
|
181
|
+
// undici implementation note: urlList is cloned in makeRequest
|
|
182
|
+
urlList: request.urlList
|
|
139
183
|
})
|
|
140
184
|
|
|
141
185
|
// 13. If init is not empty, then:
|
|
@@ -151,11 +195,20 @@ class Request {
|
|
|
151
195
|
// 3. Unset request’s history-navigation flag.
|
|
152
196
|
request.historyNavigation = false
|
|
153
197
|
|
|
154
|
-
// 4. Set request’s
|
|
198
|
+
// 4. Set request’s origin to "client".
|
|
199
|
+
request.origin = 'client'
|
|
200
|
+
|
|
201
|
+
// 5. Set request’s referrer to "client"
|
|
155
202
|
request.referrer = 'client'
|
|
156
203
|
|
|
157
|
-
//
|
|
204
|
+
// 6. Set request’s referrer policy to the empty string.
|
|
158
205
|
request.referrerPolicy = ''
|
|
206
|
+
|
|
207
|
+
// 7. Set request’s URL to request’s current URL.
|
|
208
|
+
request.url = request.urlList[request.urlList.length - 1]
|
|
209
|
+
|
|
210
|
+
// 8. Set request’s URL list to « request’s URL ».
|
|
211
|
+
request.urlList = [request.url]
|
|
159
212
|
}
|
|
160
213
|
|
|
161
214
|
// 14. If init["referrer"] exists, then:
|
|
@@ -164,7 +217,7 @@ class Request {
|
|
|
164
217
|
const referrer = init.referrer
|
|
165
218
|
|
|
166
219
|
// 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
|
|
167
|
-
if (
|
|
220
|
+
if (referrer === '') {
|
|
168
221
|
request.referrer = 'no-referrer'
|
|
169
222
|
} else {
|
|
170
223
|
// 1. Let parsedReferrer be the result of parsing referrer with
|
|
@@ -328,14 +381,9 @@ class Request {
|
|
|
328
381
|
if (signal.aborted) {
|
|
329
382
|
ac.abort()
|
|
330
383
|
} else {
|
|
331
|
-
|
|
332
|
-
signal.addEventListener(
|
|
333
|
-
|
|
334
|
-
function () {
|
|
335
|
-
ac.abort()
|
|
336
|
-
},
|
|
337
|
-
{ once: true }
|
|
338
|
-
)
|
|
384
|
+
const abort = () => ac.abort()
|
|
385
|
+
signal.addEventListener('abort', abort, { once: true })
|
|
386
|
+
requestFinalizer.register(this, { signal, abort })
|
|
339
387
|
}
|
|
340
388
|
}
|
|
341
389
|
|
package/lib/fetch/symbols.js
CHANGED
package/lib/fetch/util.js
CHANGED
|
@@ -253,20 +253,20 @@ function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
|
|
|
253
253
|
return performance.now()
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
// https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
|
|
257
|
+
function createOpaqueTimingInfo (timingInfo) {
|
|
257
258
|
return {
|
|
258
|
-
startTime: 0,
|
|
259
|
+
startTime: timingInfo.startTime ?? 0,
|
|
259
260
|
redirectStartTime: 0,
|
|
260
261
|
redirectEndTime: 0,
|
|
261
|
-
postRedirectStartTime: 0,
|
|
262
|
+
postRedirectStartTime: timingInfo.startTime ?? 0,
|
|
262
263
|
finalServiceWorkerStartTime: 0,
|
|
263
264
|
finalNetworkResponseStartTime: 0,
|
|
264
265
|
finalNetworkRequestStartTime: 0,
|
|
265
266
|
endTime: 0,
|
|
266
267
|
encodedBodySize: 0,
|
|
267
268
|
decodedBodySize: 0,
|
|
268
|
-
finalConnectionTimingInfo: null
|
|
269
|
-
...init
|
|
269
|
+
finalConnectionTimingInfo: null
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
@@ -318,7 +318,7 @@ module.exports = {
|
|
|
318
318
|
TAOCheck,
|
|
319
319
|
corsCheck,
|
|
320
320
|
crossOriginResourcePolicyCheck,
|
|
321
|
-
|
|
321
|
+
createOpaqueTimingInfo,
|
|
322
322
|
setRequestReferrerPolicyOnRedirect,
|
|
323
323
|
isValidHTTPToken,
|
|
324
324
|
requestBadPort,
|