Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/deps
diff options
context:
space:
mode:
authorNode.js GitHub Bot <github-bot@iojs.org>2022-04-03 03:23:31 +0300
committerRich Trott <rtrott@gmail.com>2022-04-07 08:06:20 +0300
commitc7ac11fa252f751d8fd26e90884cb70a91c0f7ab (patch)
tree9562e57e3c810dbd2bd6870502ac77bfeaa66415 /deps
parent85a65c326064c016d840bb4d640dcc8bf31f624b (diff)
deps: update undici to 5.0.0
PR-URL: https://github.com/nodejs/node/pull/42583 Reviewed-By: Michaƫl Zasso <targos@protonmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com> Reviewed-By: Mestery <mestery@protonmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com>
Diffstat (limited to 'deps')
-rw-r--r--deps/undici/src/docs/api/MockAgent.md54
-rw-r--r--deps/undici/src/docs/api/MockClient.md5
-rw-r--r--deps/undici/src/docs/api/MockPool.md6
-rw-r--r--deps/undici/src/lib/agent.js128
-rw-r--r--deps/undici/src/lib/client.js218
-rw-r--r--deps/undici/src/lib/core/symbols.js3
-rw-r--r--deps/undici/src/lib/dispatcher-base.js159
-rw-r--r--deps/undici/src/lib/fetch/constants.js3
-rw-r--r--deps/undici/src/lib/fetch/headers.js7
-rw-r--r--deps/undici/src/lib/fetch/index.js1024
-rw-r--r--deps/undici/src/lib/fetch/request.js26
-rw-r--r--deps/undici/src/lib/fetch/response.js109
-rw-r--r--deps/undici/src/lib/fetch/util.js28
-rw-r--r--deps/undici/src/lib/mock/mock-interceptor.js2
-rw-r--r--deps/undici/src/lib/mock/mock-utils.js8
-rw-r--r--deps/undici/src/lib/pool-base.js120
-rw-r--r--deps/undici/src/lib/proxy-agent.js12
-rw-r--r--deps/undici/src/package.json6
-rw-r--r--deps/undici/src/types/mock-interceptor.d.ts4
-rw-r--r--deps/undici/undici.js1192
20 files changed, 1576 insertions, 1538 deletions
diff --git a/deps/undici/src/docs/api/MockAgent.md b/deps/undici/src/docs/api/MockAgent.md
index 4500ef13c6b..f94ae339f96 100644
--- a/deps/undici/src/docs/api/MockAgent.md
+++ b/deps/undici/src/docs/api/MockAgent.md
@@ -72,11 +72,7 @@ const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET'
-}).reply(200, 'foo')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const { statusCode, body } = await request('http://localhost:3000/foo')
@@ -95,11 +91,7 @@ import { MockAgent, request } from 'undici'
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET'
-}).reply(200, 'foo')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
@@ -121,11 +113,7 @@ import { MockAgent, request } from 'undici'
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET'
-}).reply(200, 'foo')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
@@ -147,11 +135,7 @@ import { MockAgent, request } from 'undici'
const mockAgent = new MockAgent({ connections: 1 })
const mockClient = mockAgent.get('http://localhost:3000')
-
-mockClient.intercept({
- path: '/foo',
- method: 'GET'
-}).reply(200, 'foo')
+mockClient.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
@@ -174,16 +158,8 @@ const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET'
-}).reply(200, 'foo')
-
-mockPool.intercept({
- path: '/hello',
- method: 'GET'
-}).reply(200, 'hello')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
+mockPool.intercept({ path: '/hello'}).reply(200, 'hello')
const result1 = await request('http://localhost:3000/foo')
@@ -250,11 +226,7 @@ const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get(new RegExp('http://localhost:3000'))
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET',
-}).reply(200, 'foo')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
@@ -277,11 +249,7 @@ const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get((origin) => origin === 'http://localhost:3000')
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET'
-}).reply(200, 'foo')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
@@ -328,11 +296,7 @@ import { MockAgent } from 'undici'
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET'
-}).reply(200, 'foo')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
diff --git a/deps/undici/src/docs/api/MockClient.md b/deps/undici/src/docs/api/MockClient.md
index de73b24702e..ac546913d23 100644
--- a/deps/undici/src/docs/api/MockClient.md
+++ b/deps/undici/src/docs/api/MockClient.md
@@ -58,10 +58,7 @@ import { MockAgent } from 'undici'
const mockAgent = new MockAgent({ connections: 1 })
const mockClient = mockAgent.get('http://localhost:3000')
-mockClient.intercept({
- path: '/foo',
- method: 'GET',
-}).reply(200, 'foo')
+mockClient.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
diff --git a/deps/undici/src/docs/api/MockPool.md b/deps/undici/src/docs/api/MockPool.md
index df861255941..29667f3177b 100644
--- a/deps/undici/src/docs/api/MockPool.md
+++ b/deps/undici/src/docs/api/MockPool.md
@@ -95,11 +95,7 @@ setGlobalDispatcher(mockAgent)
// MockPool
const mockPool = mockAgent.get('http://localhost:3000')
-
-mockPool.intercept({
- path: '/foo',
- method: 'GET',
-}).reply(200, 'foo')
+mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const {
statusCode,
diff --git a/deps/undici/src/lib/agent.js b/deps/undici/src/lib/agent.js
index 30ac4ee1181..47aa2365e61 100644
--- a/deps/undici/src/lib/agent.js
+++ b/deps/undici/src/lib/agent.js
@@ -1,20 +1,14 @@
'use strict'
-const {
- ClientClosedError,
- InvalidArgumentError,
- ClientDestroyedError
-} = require('./core/errors')
-const { kClients, kRunning } = require('./core/symbols')
-const Dispatcher = require('./dispatcher')
+const { InvalidArgumentError } = require('./core/errors')
+const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('./core/symbols')
+const DispatcherBase = require('./dispatcher-base')
const Pool = require('./pool')
const Client = require('./client')
const util = require('./core/util')
const RedirectHandler = require('./handler/redirect')
const { WeakRef, FinalizationRegistry } = require('./compat/dispatcher-weakref')()
-const kDestroyed = Symbol('destroyed')
-const kClosed = Symbol('closed')
const kOnConnect = Symbol('onConnect')
const kOnDisconnect = Symbol('onDisconnect')
const kOnConnectionError = Symbol('onConnectionError')
@@ -30,7 +24,7 @@ function defaultFactory (origin, opts) {
: new Pool(origin, opts)
}
-class Agent extends Dispatcher {
+class Agent extends DispatcherBase {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
super()
@@ -60,8 +54,6 @@ class Agent extends Dispatcher {
this[kClients].delete(key)
}
})
- this[kClosed] = false
- this[kDestroyed] = false
const agent = this
@@ -94,76 +86,38 @@ class Agent extends Dispatcher {
return ret
}
- dispatch (opts, handler) {
- if (!handler || typeof handler !== 'object') {
- throw new InvalidArgumentError('handler must be an object.')
+ [kDispatch] (opts, handler) {
+ let key
+ if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
+ key = String(opts.origin)
+ } else {
+ throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
}
- try {
- if (!opts || typeof opts !== 'object') {
- throw new InvalidArgumentError('opts must be an object.')
- }
-
- let key
- if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
- key = String(opts.origin)
- } else {
- throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
- }
-
- if (this[kDestroyed]) {
- throw new ClientDestroyedError()
- }
-
- if (this[kClosed]) {
- throw new ClientClosedError()
- }
+ const ref = this[kClients].get(key)
- const ref = this[kClients].get(key)
-
- let dispatcher = ref ? ref.deref() : null
- if (!dispatcher) {
- dispatcher = this[kFactory](opts.origin, this[kOptions])
- .on('drain', this[kOnDrain])
- .on('connect', this[kOnConnect])
- .on('disconnect', this[kOnDisconnect])
- .on('connectionError', this[kOnConnectionError])
-
- this[kClients].set(key, new WeakRef(dispatcher))
- this[kFinalizer].register(dispatcher, key)
- }
-
- const { maxRedirections = this[kMaxRedirections] } = opts
- if (maxRedirections != null && maxRedirections !== 0) {
- opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
- handler = new RedirectHandler(this, maxRedirections, opts, handler)
- }
-
- return dispatcher.dispatch(opts, handler)
- } catch (err) {
- if (typeof handler.onError !== 'function') {
- throw new InvalidArgumentError('invalid onError method')
- }
+ let dispatcher = ref ? ref.deref() : null
+ if (!dispatcher) {
+ dispatcher = this[kFactory](opts.origin, this[kOptions])
+ .on('drain', this[kOnDrain])
+ .on('connect', this[kOnConnect])
+ .on('disconnect', this[kOnDisconnect])
+ .on('connectionError', this[kOnConnectionError])
- handler.onError(err)
+ this[kClients].set(key, new WeakRef(dispatcher))
+ this[kFinalizer].register(dispatcher, key)
}
- }
-
- get closed () {
- return this[kClosed]
- }
-
- get destroyed () {
- return this[kDestroyed]
- }
- close (callback) {
- if (callback != null && typeof callback !== 'function') {
- throw new InvalidArgumentError('callback must be a function')
+ const { maxRedirections = this[kMaxRedirections] } = opts
+ if (maxRedirections != null && maxRedirections !== 0) {
+ opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
+ handler = new RedirectHandler(this, maxRedirections, opts, handler)
}
- this[kClosed] = true
+ return dispatcher.dispatch(opts, handler)
+ }
+ async [kClose] () {
const closePromises = []
for (const ref of this[kClients].values()) {
const client = ref.deref()
@@ -173,27 +127,10 @@ class Agent extends Dispatcher {
}
}
- if (!callback) {
- return Promise.all(closePromises)
- }
-
- // Should never error.
- Promise.all(closePromises).then(() => process.nextTick(callback))
+ await Promise.all(closePromises)
}
- destroy (err, callback) {
- if (typeof err === 'function') {
- callback = err
- err = null
- }
-
- if (callback != null && typeof callback !== 'function') {
- throw new InvalidArgumentError('callback must be a function')
- }
-
- this[kClosed] = true
- this[kDestroyed] = true
-
+ async [kDestroy] (err) {
const destroyPromises = []
for (const ref of this[kClients].values()) {
const client = ref.deref()
@@ -203,12 +140,7 @@ class Agent extends Dispatcher {
}
}
- if (!callback) {
- return Promise.all(destroyPromises)
- }
-
- // Should never error.
- Promise.all(destroyPromises).then(() => process.nextTick(callback))
+ await Promise.all(destroyPromises)
}
}
diff --git a/deps/undici/src/lib/client.js b/deps/undici/src/lib/client.js
index 55d9afabf95..d3d4cfc705d 100644
--- a/deps/undici/src/lib/client.js
+++ b/deps/undici/src/lib/client.js
@@ -6,7 +6,7 @@ const assert = require('assert')
const net = require('net')
const util = require('./core/util')
const Request = require('./core/request')
-const Dispatcher = require('./dispatcher')
+const DispatcherBase = require('./dispatcher-base')
const RedirectHandler = require('./handler/redirect')
const {
RequestContentLengthMismatchError,
@@ -16,8 +16,6 @@ const {
RequestAbortedError,
HeadersTimeoutError,
HeadersOverflowError,
- ClientDestroyedError,
- ClientClosedError,
SocketError,
InformationalError,
BodyTimeoutError,
@@ -45,12 +43,9 @@ const {
kNoRef,
kKeepAliveDefaultTimeout,
kHostHeader,
- kClosed,
- kDestroyed,
kPendingIdx,
kRunningIdx,
kError,
- kOnDestroyed,
kPipelining,
kSocket,
kKeepAliveTimeoutValue,
@@ -63,9 +58,14 @@ const {
kConnector,
kMaxRedirections,
kMaxRequests,
- kCounter
+ kCounter,
+ kClose,
+ kDestroy,
+ kDispatch
} = require('./core/symbols')
+const kClosedResolve = Symbol('kClosedResolve')
+
const channels = {}
try {
@@ -81,7 +81,7 @@ try {
channels.connected = { hasSubscribers: false }
}
-class Client extends Dispatcher {
+class Client extends DispatcherBase {
constructor (url, {
maxHeaderSize,
headersTimeout,
@@ -189,10 +189,7 @@ class Client extends Dispatcher {
this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout
this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 1e3 : keepAliveTimeoutThreshold
this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout]
- this[kClosed] = false
- this[kDestroyed] = false
this[kServerName] = null
- this[kOnDestroyed] = []
this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming
this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming
this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n`
@@ -201,6 +198,7 @@ class Client extends Dispatcher {
this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
this[kMaxRedirections] = maxRedirections
this[kMaxRequests] = maxRequestsPerClient
+ this[kClosedResolve] = null
// kQueue is built up of 3 sections separated by
// the kRunningIdx and kPendingIdx indices.
@@ -216,25 +214,15 @@ class Client extends Dispatcher {
this[kPendingIdx] = 0
}
- // TODO: Make private?
get pipelining () {
return this[kPipelining]
}
- // TODO: Make private?
set pipelining (value) {
this[kPipelining] = value
resume(this, true)
}
- get destroyed () {
- return this[kDestroyed]
- }
-
- get closed () {
- return this[kClosed]
- }
-
get [kPending] () {
return this[kQueue].length - this[kPendingIdx]
}
@@ -266,141 +254,68 @@ class Client extends Dispatcher {
this.once('connect', cb)
}
- dispatch (opts, handler) {
- if (!handler || typeof handler !== 'object') {
- throw new InvalidArgumentError('handler must be an object')
- }
-
- try {
- if (!opts || typeof opts !== 'object') {
- throw new InvalidArgumentError('opts must be an object.')
- }
-
- if (this[kDestroyed]) {
- throw new ClientDestroyedError()
- }
-
- if (this[kClosed]) {
- throw new ClientClosedError()
- }
-
- const { maxRedirections = this[kMaxRedirections] } = opts
- if (maxRedirections) {
- handler = new RedirectHandler(this, maxRedirections, opts, handler)
- }
-
- const origin = opts.origin || this[kUrl].origin
-
- const request = new Request(origin, opts, handler)
-
- this[kQueue].push(request)
- if (this[kResuming]) {
- // Do nothing.
- } else if (util.bodyLength(request.body) == null && util.isIterable(request.body)) {
- // Wait a tick in case stream/iterator is ended in the same tick.
- this[kResuming] = 1
- process.nextTick(resume, this)
- } else {
- resume(this, true)
- }
-
- if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) {
- this[kNeedDrain] = 2
- }
- } catch (err) {
- if (typeof handler.onError !== 'function') {
- throw new InvalidArgumentError('invalid onError method')
- }
-
- handler.onError(err)
- }
-
- return this[kNeedDrain] < 2
- }
-
- close (callback) {
- if (callback === undefined) {
- return new Promise((resolve, reject) => {
- this.close((err, data) => {
- return err ? reject(err) : resolve(data)
- })
- })
- }
-
- if (typeof callback !== 'function') {
- throw new InvalidArgumentError('invalid callback')
+ [kDispatch] (opts, handler) {
+ const { maxRedirections = this[kMaxRedirections] } = opts
+ if (maxRedirections) {
+ handler = new RedirectHandler(this, maxRedirections, opts, handler)
}
- if (this[kDestroyed]) {
- queueMicrotask(() => callback(new ClientDestroyedError(), null))
- return
- }
+ const origin = opts.origin || this[kUrl].origin
- this[kClosed] = true
+ const request = new Request(origin, opts, handler)
- if (!this[kSize]) {
- this.destroy(callback)
+ this[kQueue].push(request)
+ if (this[kResuming]) {
+ // Do nothing.
+ } else if (util.bodyLength(request.body) == null && util.isIterable(request.body)) {
+ // Wait a tick in case stream/iterator is ended in the same tick.
+ this[kResuming] = 1
+ process.nextTick(resume, this)
} else {
- this[kOnDestroyed].push(callback)
+ resume(this, true)
}
- }
- destroy (err, callback) {
- if (typeof err === 'function') {
- callback = err
- err = null
+ if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) {
+ this[kNeedDrain] = 2
}
- if (callback === undefined) {
- return new Promise((resolve, reject) => {
- this.destroy(err, (err, data) => {
- return err ? /* istanbul ignore next: should never error */ reject(err) : resolve(data)
- })
- })
- }
-
- if (typeof callback !== 'function') {
- throw new InvalidArgumentError('invalid callback')
- }
+ return this[kNeedDrain] < 2
+ }
- if (this[kDestroyed]) {
- if (this[kOnDestroyed]) {
- this[kOnDestroyed].push(callback)
+ async [kClose] () {
+ return new Promise((resolve) => {
+ if (!this[kSize]) {
+ this.destroy(resolve)
} else {
- queueMicrotask(() => callback(null, null))
+ this[kClosedResolve] = resolve
}
- return
- }
-
- if (!err) {
- err = new ClientDestroyedError()
- }
-
- const requests = this[kQueue].splice(this[kPendingIdx])
- for (let i = 0; i < requests.length; i++) {
- const request = requests[i]
- errorRequest(this, request, err)
- }
+ })
+ }
- this[kClosed] = true
- this[kDestroyed] = true
- this[kOnDestroyed].push(callback)
+ async [kDestroy] (err) {
+ return new Promise((resolve) => {
+ const requests = this[kQueue].splice(this[kPendingIdx])
+ for (let i = 0; i < requests.length; i++) {
+ const request = requests[i]
+ errorRequest(this, request, err)
+ }
- const onDestroyed = () => {
- const callbacks = this[kOnDestroyed]
- this[kOnDestroyed] = null
- for (let i = 0; i < callbacks.length; i++) {
- callbacks[i](null, null)
+ const callback = () => {
+ if (this[kClosedResolve]) {
+ this[kClosedResolve]()
+ this[kClosedResolve] = null
+ }
+ resolve()
}
- }
- if (!this[kSocket]) {
- queueMicrotask(onDestroyed)
- } else {
- util.destroy(this[kSocket].on('close', onDestroyed), err)
- }
+ if (!this[kSocket]) {
+ queueMicrotask(callback)
+ } else {
+ util.destroy(this[kSocket].on('close', callback), err)
+ }
- resume(this)
+ resume(this)
+ })
}
}
@@ -476,7 +391,6 @@ async function lazyllhttp () {
let llhttpInstance = null
let llhttpPromise = lazyllhttp()
.catch(() => {
- // TODO: Emit warning?
})
let currentParser = null
@@ -586,7 +500,6 @@ class Parser {
currentBufferPtr = llhttp.malloc(currentBufferSize)
}
- // TODO (perf): Can we avoid this copy somehow?
new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data)
// Call `execute` on the wasm parser.
@@ -635,12 +548,10 @@ class Parser {
try {
try {
currentParser = this
- this.llhttp.llhttp_finish(this.ptr) // TODO (fix): Check ret?
} finally {
currentParser = null
}
} catch (err) {
- // TODO (fix): What if socket is already destroyed? Error will be swallowed.
/* istanbul ignore next: difficult to make a test case for */
util.destroy(this.socket, err)
}
@@ -782,13 +693,9 @@ class Parser {
return -1
}
- // TODO: Check for content-length mismatch from server?
-
assert(!this.upgrade)
assert(this.statusCode < 200)
- // TODO: More statusCode validation?
-
if (statusCode === 100) {
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
return -1
@@ -979,7 +886,6 @@ class Parser {
util.destroy(socket, new InformationalError('reset'))
return constants.ERROR.PAUSED
} else if (!shouldKeepAlive) {
- // TODO: What if running > 0?
util.destroy(socket, new InformationalError('reset'))
return constants.ERROR.PAUSED
} else if (socket[kReset] && client[kRunning] === 0) {
@@ -1079,7 +985,7 @@ function onSocketClose () {
client[kSocket] = null
- if (client[kDestroyed]) {
+ if (client.destroyed) {
assert(client[kPending] === 0)
// Fail entire queue.
@@ -1251,14 +1157,14 @@ function resume (client, sync) {
function _resume (client, sync) {
while (true) {
- if (client[kDestroyed]) {
+ if (client.destroyed) {
assert(client[kPending] === 0)
return
}
- if (client[kClosed] && !client[kSize]) {
- client.destroy(util.nop)
- continue
+ if (client.closed && !client[kSize]) {
+ client.destroy()
+ return
}
const socket = client[kSocket]
@@ -1479,8 +1385,6 @@ function write (client, request) {
socket[kBlocking] = true
}
- // TODO: Expect: 100-continue
-
let header = `${method} ${path} HTTP/1.1\r\n`
if (typeof host === 'string') {
@@ -1600,7 +1504,6 @@ function writeStream ({ body, client, request, socket, contentLength, header, ex
writer.destroy(err)
- // TODO (fix): Avoid using err.message for logic.
if (err && (err.code !== 'UND_ERR_INFO' || err.message !== 'reset')) {
util.destroy(body, err)
} else {
@@ -1679,7 +1582,6 @@ async function writeIterable ({ body, client, request, socket, contentLength, he
const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
try {
- // TODO (fix): What if socket errors while waiting for body?
// It's up to the user to somehow abort the async iterable.
for await (const chunk of body) {
if (socket[kError]) {
@@ -1730,7 +1632,6 @@ class AsyncWriter {
return true
}
- // TODO: What if not ended and bytesWritten === contentLength?
// We should defer writing chunks.
if (contentLength !== null && bytesWritten + len > contentLength) {
if (client[kStrictContentLength]) {
@@ -1800,7 +1701,6 @@ class AsyncWriter {
}
}
- // TODO (fix): Add comment clarifying what this does?
if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) {
// istanbul ignore else: only for jest
if (socket[kParser].timeout.refresh) {
diff --git a/deps/undici/src/lib/core/symbols.js b/deps/undici/src/lib/core/symbols.js
index 1d28bc15e0c..30108827a84 100644
--- a/deps/undici/src/lib/core/symbols.js
+++ b/deps/undici/src/lib/core/symbols.js
@@ -1,4 +1,7 @@
module.exports = {
+ kClose: Symbol('close'),
+ kDestroy: Symbol('destroy'),
+ kDispatch: Symbol('dispatch'),
kUrl: Symbol('url'),
kWriting: Symbol('writing'),
kResuming: Symbol('resuming'),
diff --git a/deps/undici/src/lib/dispatcher-base.js b/deps/undici/src/lib/dispatcher-base.js
new file mode 100644
index 00000000000..2c12ba80f35
--- /dev/null
+++ b/deps/undici/src/lib/dispatcher-base.js
@@ -0,0 +1,159 @@
+'use strict'
+
+const Dispatcher = require('./dispatcher')
+const {
+ ClientDestroyedError,
+ ClientClosedError,
+ InvalidArgumentError
+} = require('./core/errors')
+const { kDestroy, kClose, kDispatch } = require('./core/symbols')
+
+const kDestroyed = Symbol('destroyed')
+const kClosed = Symbol('closed')
+const kOnDestroyed = Symbol('onDestroyed')
+const kOnClosed = Symbol('onClosed')
+
+class DispatcherBase extends Dispatcher {
+ constructor () {
+ super()
+
+ this[kDestroyed] = false
+ this[kOnDestroyed] = []
+ this[kClosed] = false
+ this[kOnClosed] = []
+ }
+
+ get destroyed () {
+ return this[kDestroyed]
+ }
+
+ get closed () {
+ return this[kClosed]
+ }
+
+ close (callback) {
+ if (callback === undefined) {
+ return new Promise((resolve, reject) => {
+ this.close((err, data) => {
+ return err ? reject(err) : resolve(data)
+ })
+ })
+ }
+
+ if (typeof callback !== 'function') {
+ throw new InvalidArgumentError('invalid callback')
+ }
+
+ if (this[kDestroyed]) {
+ queueMicrotask(() => callback(new ClientDestroyedError(), null))
+ return
+ }
+
+ if (this[kClosed]) {
+ if (this[kOnClosed]) {
+ this[kOnClosed].push(callback)
+ } else {
+ queueMicrotask(() => callback(null, null))
+ }
+ return
+ }
+
+ this[kClosed] = true
+ this[kOnClosed].push(callback)
+
+ const onClosed = () => {
+ const callbacks = this[kOnClosed]
+ this[kOnClosed] = null
+ for (let i = 0; i < callbacks.length; i++) {
+ callbacks[i](null, null)
+ }
+ }
+
+ // Should not error.
+ this[kClose]()
+ .then(() => this.destroy())
+ .then(() => {
+ queueMicrotask(onClosed)
+ })
+ }
+
+ destroy (err, callback) {
+ if (typeof err === 'function') {
+ callback = err
+ err = null
+ }
+
+ if (callback === undefined) {
+ return new Promise((resolve, reject) => {
+ this.destroy(err, (err, data) => {
+ return err ? /* istanbul ignore next: should never error */ reject(err) : resolve(data)
+ })
+ })
+ }
+
+ if (typeof callback !== 'function') {
+ throw new InvalidArgumentError('invalid callback')
+ }
+
+ if (this[kDestroyed]) {
+ if (this[kOnDestroyed]) {
+ this[kOnDestroyed].push(callback)
+ } else {
+ queueMicrotask(() => callback(null, null))
+ }
+ return
+ }
+
+ if (!err) {
+ err = new ClientDestroyedError()
+ }
+
+ this[kDestroyed] = true
+ this[kOnDestroyed].push(callback)
+
+ const onDestroyed = () => {
+ const callbacks = this[kOnDestroyed]
+ this[kOnDestroyed] = null
+ for (let i = 0; i < callbacks.length; i++) {
+ callbacks[i](null, null)
+ }
+ }
+
+ // Should not error.
+ this[kDestroy](err).then(() => {
+ queueMicrotask(onDestroyed)
+ })
+ }
+
+ dispatch (opts, handler) {
+ if (!handler || typeof handler !== 'object') {
+ throw new InvalidArgumentError('handler must be an object')
+ }
+
+ try {
+ if (!opts || typeof opts !== 'object') {
+ throw new InvalidArgumentError('opts must be an object.')
+ }
+
+ if (this[kDestroyed]) {
+ throw new ClientDestroyedError()
+ }
+
+ if (this[kClosed]) {
+ throw new ClientClosedError()
+ }
+
+ return this[kDispatch](opts, handler)
+ } catch (err) {
+ if (typeof handler.onError !== 'function') {
+ throw new InvalidArgumentError('invalid onError method')
+ }
+
+ handler.onError(err)
+
+ return false
+ }
+ }
+}
+
+module.exports = DispatcherBase
diff --git a/deps/undici/src/lib/fetch/constants.js b/deps/undici/src/lib/fetch/constants.js
index 2eff7596968..75c9265e8c1 100644
--- a/deps/undici/src/lib/fetch/constants.js
+++ b/deps/undici/src/lib/fetch/constants.js
@@ -86,9 +86,12 @@ const subresource = [
''
]
+const corsSafeListedResponseHeaderNames = [] // TODO
+
module.exports = {
subresource,
forbiddenResponseHeaderNames,
+ corsSafeListedResponseHeaderNames,
forbiddenMethods,
requestBodyHeader,
referrerPolicy,
diff --git a/deps/undici/src/lib/fetch/headers.js b/deps/undici/src/lib/fetch/headers.js
index 7a59f15371e..a5dd5b7a413 100644
--- a/deps/undici/src/lib/fetch/headers.js
+++ b/deps/undici/src/lib/fetch/headers.js
@@ -346,11 +346,12 @@ class Headers {
const callback = args[0]
const thisArg = args[1]
- for (let index = 0; index < this[kHeadersList].length; index += 2) {
+ const clone = this[kHeadersList].slice()
+ for (let index = 0; index < clone.length; index += 2) {
callback.call(
thisArg,
- this[kHeadersList][index + 1],
- this[kHeadersList][index],
+ clone[index + 1],
+ clone[index],
this
)
}
diff --git a/deps/undici/src/lib/fetch/index.js b/deps/undici/src/lib/fetch/index.js
index d5ca150b151..dfc7b824082 100644
--- a/deps/undici/src/lib/fetch/index.js
+++ b/deps/undici/src/lib/fetch/index.js
@@ -5,6 +5,7 @@
const {
Response,
makeNetworkError,
+ makeAppropriateNetworkError,
filterResponse,
makeResponse
} = require('./response')
@@ -12,8 +13,6 @@ const { Headers } = require('./headers')
const { Request, makeRequest } = require('./request')
const zlib = require('zlib')
const {
- ServiceWorkerGlobalScope,
- Window,
matchRequestIntegrity,
makePolicyContainer,
clonePolicyContainer,
@@ -33,7 +32,9 @@ const {
createDeferredPromise,
isBlobLike,
CORBCheck,
- sameOrigin
+ sameOrigin,
+ isCancelled,
+ isAborted
} = require('./util')
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
const { AbortError } = require('../core/errors')
@@ -48,10 +49,11 @@ const {
} = require('./constants')
const { kHeadersList } = require('../core/symbols')
const EE = require('events')
-const { PassThrough, pipeline } = require('stream')
+const { Readable, pipeline } = require('stream')
const { isErrored, isReadable } = require('../core/util')
-const { kIsMockActive } = require('../mock/mock-symbols')
const { dataURLProcessor } = require('./dataURL')
+const { kIsMockActive } = require('../mock/mock-symbols')
+const { TransformStream } = require('stream/web')
/** @type {import('buffer').resolveObjectURL} */
let resolveObjectURL
@@ -62,19 +64,30 @@ class Fetch extends EE {
super()
this.dispatcher = dispatcher
- this.terminated = null
this.connection = null
this.dump = false
+ this.state = 'ongoing'
}
- terminate ({ reason, aborted } = {}) {
- if (this.terminated) {
+ terminate (reason) {
+ if (this.state !== 'ongoing') {
return
}
- this.terminated = { aborted, reason }
+ this.state = 'terminated'
this.connection?.destroy(reason)
+ this.emit('terminated', reason)
+ }
+
+ abort () {
+ if (this.state !== 'ongoing') {
+ return
+ }
+ const reason = new AbortError()
+
+ this.state = 'aborted'
+ this.connection?.destroy(reason)
this.emit('terminated', reason)
}
}
@@ -99,8 +112,6 @@ async function fetch (...args) {
const resource = args[0]
const init = args.length >= 1 ? args[1] ?? {} : {}
- const context = new Fetch(this)
-
// 1. Let p be a new promise.
const p = createDeferredPromise()
@@ -115,19 +126,18 @@ async function fetch (...args) {
// 4. If requestObjectā€™s signalā€™s aborted flag is set, then:
if (requestObject.signal.aborted) {
// 1. Abort fetch with p, request, and null.
- abortFetch.call(context, p, request, null)
+ abortFetch(p, request, null)
// 2. Return p.
return p.promise
}
// 5. Let globalObject be requestā€™s clientā€™s global object.
- // TODO: What if request.client is null?
- const globalObject = request.client?.globalObject
+ const globalObject = request.client.globalObject
// 6. If globalObject is a ServiceWorkerGlobalScope object, then set
// requestā€™s service-workers mode to "none".
- if (globalObject instanceof ServiceWorkerGlobalScope) {
+ if (globalObject?.constructor?.name === 'ServiceWorkerGlobalScope') {
request.serviceWorkers = 'none'
}
@@ -140,7 +150,10 @@ async function fetch (...args) {
// 9. Let locallyAborted be false.
let locallyAborted = false
- // 10. Add the following abort steps to requestObjectā€™s signal:
+ // 10. Let controller be null.
+ let controller = null
+
+ // 11. Add the following abort steps to requestObjectā€™s signal:
requestObject.signal.addEventListener(
'abort',
() => {
@@ -148,21 +161,25 @@ async function fetch (...args) {
locallyAborted = true
// 2. Abort fetch with p, request, and responseObject.
- abortFetch.call(context, p, request, responseObject)
+ abortFetch(p, request, responseObject)
- // 3. Terminate the ongoing fetch with the aborted flag set.
- context.terminate({ aborted: true })
+ // 3. If controller is not null, then abort controller.
+ if (controller != null) {
+ controller.abort()
+ }
},
{ once: true }
)
- // 11. Let handleFetchDone given response response be to finalize and
+ // 12. Let handleFetchDone given response response be to finalize and
// report timing with response, globalObject, and "fetch".
const handleFetchDone = (response) =>
finalizeAndReportTiming(response, 'fetch')
- // 12. Fetch request with processResponseEndOfBody set to handleFetchDone,
- // and processResponse given response being these substeps:
+ // 13. Set controller to the result of calling fetch given request,
+ // with processResponseEndOfBody set to handleFetchDone, and processResponse
+ // given response being these substeps:
+
const processResponse = (response) => {
// 1. If locallyAborted is true, terminate these substeps.
if (locallyAborted) {
@@ -172,7 +189,7 @@ async function fetch (...args) {
// 2. If responseā€™s aborted flag is set, then abort fetch with p,
// request, and responseObject, and terminate these substeps.
if (response.aborted) {
- abortFetch.call(context, p, request, responseObject)
+ abortFetch(p, request, responseObject)
return
}
@@ -198,17 +215,14 @@ async function fetch (...args) {
p.resolve(responseObject)
}
- fetching
- .call(context, {
- request,
- processResponseEndOfBody: handleFetchDone,
- processResponse
- })
- .catch((err) => {
- p.reject(err)
- })
+ controller = fetching({
+ request,
+ processResponseEndOfBody: handleFetchDone,
+ processResponse,
+ dispatcher: this // undici
+ })
- // 13. Return p.
+ // 14. Return p.
return p.promise
}
@@ -329,7 +343,8 @@ function fetching ({
processResponse,
processResponseEndOfBody,
processResponseConsumeBody,
- useParallelQueue = false
+ useParallelQueue = false,
+ dispatcher // undici
}) {
// 1. Let taskDestination be null.
let taskDestination = null
@@ -371,6 +386,7 @@ function fetching ({
// task destination is taskDestination,
// and cross-origin isolated capability is crossOriginIsolatedCapability.
const fetchParams = {
+ controller: new Fetch(dispatcher),
request,
timingInfo,
processRequestBodyChunkLength,
@@ -394,7 +410,7 @@ function fetching ({
if (request.window === 'client') {
// TODO: What if request.client is null?
request.window =
- request.client?.globalObject instanceof Window
+ request.client?.globalObject?.constructor?.name === 'Window'
? request.client
: 'no-window'
}
@@ -406,7 +422,10 @@ function fetching ({
request.origin = request.client?.origin
}
- // 10. If requestā€™s policy container is "client", then:
+ // 10. If all of the following conditions are true:
+ // TODO
+
+ // 11. If requestā€™s policy container is "client", then:
if (request.policyContainer === 'client') {
// 1. If requestā€™s client is non-null, then set requestā€™s policy
// container to a clone of requestā€™s clientā€™s policy container. [HTML]
@@ -421,7 +440,7 @@ function fetching ({
}
}
- // 11. If requestā€™s header list does not contain `Accept`, then:
+ // 12. If requestā€™s header list does not contain `Accept`, then:
if (!request.headersList.has('accept')) {
// 1. Let value be `*/*`.
const value = '*/*'
@@ -442,38 +461,37 @@ function fetching ({
request.headersList.append('accept', value)
}
- // 12. If requestā€™s header list does not contain `Accept-Language`, then
+ // 13. If requestā€™s header list does not contain `Accept-Language`, then
// user agents should append `Accept-Language`/an appropriate value to
// requestā€™s header list.
if (!request.headersList.has('accept-language')) {
request.headersList.append('accept-language', '*')
}
- // 13. If requestā€™s priority is null, then use requestā€™s initiator and
+ // 14. If requestā€™s priority is null, then use requestā€™s initiator and
// destination appropriately in setting requestā€™s priority to a
// user-agent-defined object.
if (request.priority === null) {
// TODO
}
- // 14. If request is a subresource request, then:
+ // 15. If request is a subresource request, then:
if (subresource.includes(request.destination)) {
- // 1. Let record be a new fetch record consisting of request and this
- // instance of the fetch algorithm.
- // TODO
- // 2. Append record to requestā€™s clientā€™s fetch group list of fetch
- // records.
// TODO
}
- // 15. Run main fetch given fetchParams.
- return mainFetch.call(this, fetchParams)
+ // 16. Run main fetch given fetchParams.
+ mainFetch(fetchParams)
+ .catch(err => {
+ fetchParams.controller.terminate(err)
+ })
+
+ // 17. Return fetchParam's controller
+ return fetchParams.controller
}
// https://fetch.spec.whatwg.org/#concept-main-fetch
async function mainFetch (fetchParams, recursive = false) {
- const context = this
-
// 1. Let request be fetchParamsā€™s request.
const request = fetchParams.request
@@ -548,8 +566,7 @@ async function mainFetch (fetchParams, recursive = false) {
request.responseTainting = 'basic'
// 2. Return the result of running scheme fetch given fetchParams.
- return await schemeFetch
- .call(this, fetchParams)
+ return await schemeFetch(fetchParams)
}
// requestā€™s mode is "same-origin"
@@ -573,8 +590,7 @@ async function mainFetch (fetchParams, recursive = false) {
// 3. Let noCorsResponse be the result of running scheme fetch given
// fetchParams.
- const noCorsResponse = await schemeFetch
- .call(this, fetchParams)
+ const noCorsResponse = await schemeFetch(fetchParams)
// 4. If noCorsResponse is a filtered response or the CORB check with
// request and noCorsResponse returns allowed, then return noCorsResponse.
@@ -609,9 +625,7 @@ async function mainFetch (fetchParams, recursive = false) {
request.responseTainting = 'cors'
// 2. Return the result of running HTTP fetch given fetchParams.
- return await httpFetch
- .call(this, fetchParams)
- .catch((err) => makeNetworkError(err))
+ return await httpFetch(fetchParams)
})()
}
@@ -699,7 +713,7 @@ async function mainFetch (fetchParams, recursive = false) {
nullBodyStatus.includes(internalResponse.status))
) {
internalResponse.body = null
- context.dump = true
+ fetchParams.controller.dump = true
}
// 20. If requestā€™s integrity metadata is not the empty string, then:
@@ -707,7 +721,7 @@ async function mainFetch (fetchParams, recursive = false) {
// 1. Let processBodyError be this step: run fetch finale given fetchParams
// and a network error.
const processBodyError = (reason) =>
- fetchFinale.call(context, fetchParams, makeNetworkError(reason))
+ fetchFinale(fetchParams, makeNetworkError(reason))
// 2. If requestā€™s response tainting is "opaque", or responseā€™s body is null,
// then run processBodyError and abort these steps.
@@ -730,7 +744,7 @@ async function mainFetch (fetchParams, recursive = false) {
response.body = safelyExtractBody(bytes)[0]
// 3. Run fetch finale given fetchParams and response.
- fetchFinale.call(context, fetchParams, response)
+ fetchFinale(fetchParams, response)
}
// 4. Fully read responseā€™s body given processBody and processBodyError.
@@ -741,15 +755,13 @@ async function mainFetch (fetchParams, recursive = false) {
}
} else {
// 21. Otherwise, run fetch finale given fetchParams and response.
- fetchFinale.call(context, fetchParams, response)
+ fetchFinale(fetchParams, response)
}
}
// https://fetch.spec.whatwg.org/#concept-scheme-fetch
// given a fetch params fetchParams
async function schemeFetch (fetchParams) {
- const context = this
-
// let request be fetchParamsā€™s request
const { request } = fetchParams
@@ -782,12 +794,10 @@ async function schemeFetch (fetchParams) {
case 'blob:': {
resolveObjectURL ??= require('buffer').resolveObjectURL
- context.on('terminated', onRequestAborted)
-
// 1. Run these steps, but abort when the ongoing fetch is terminated:
- // 1a. Let blob be requestā€™s current URLā€™s blob URL entryā€™s object.
- // https://w3c.github.io/FileAPI/#blob-url-entry
- // P.S. Thank God this method is available in node.
+ // 1. Let blob be requestā€™s current URLā€™s blob URL entryā€™s object.
+ // https://w3c.github.io/FileAPI/#blob-url-entry
+ // P.S. Thank God this method is available in node.
const currentURL = requestCurrentURL(request)
// https://github.com/web-platform-tests/wpt/blob/7b0ebaccc62b566a1965396e5be7bb2bc06f841f/FileAPI/url/resources/fetch-tests.js#L52-L56
@@ -798,42 +808,29 @@ async function schemeFetch (fetchParams) {
const blob = resolveObjectURL(currentURL.toString())
- // 2a. If requestā€™s method is not `GET` or blob is not a Blob object, then return a network error. [FILEAPI]
+ // 2. If requestā€™s method is not `GET` or blob is not a Blob object, then return a network error. [FILEAPI]
if (request.method !== 'GET' || !isBlobLike(blob)) {
return makeNetworkError('invalid method')
}
- // 3a. Let response be a new response whose status message is `OK`.
+ // 3. Let response be a new response whose status message is `OK`.
const response = makeResponse({ statusText: 'OK', urlList: [currentURL] })
- // 4a. Append (`Content-Length`, blobā€™s size attribute value) to responseā€™s header list.
+ // 4. Append (`Content-Length`, blobā€™s size attribute value) to responseā€™s header list.
response.headersList.set('content-length', `${blob.size}`)
- // 5a. Append (`Content-Type`, blobā€™s type attribute value) to responseā€™s header list.
+ // 5. Append (`Content-Type`, blobā€™s type attribute value) to responseā€™s header list.
response.headersList.set('content-type', blob.type)
- // 6a. Set responseā€™s body to the result of performing the read operation on blob.
+ // 6. Set responseā€™s body to the result of performing the read operation on blob.
+ // TODO (fix): This needs to read?
response.body = extractBody(blob)[0]
- // since the request has not been aborted, we can safely remove the listener.
- context.off('terminated', onRequestAborted)
-
- // 7a. Return response.
+ // 7. Return response.
return response
- // 2. If aborted, then:
- function onRequestAborted () {
- // 1. Let aborted be the terminationā€™s aborted flag.
- const aborted = context.terminated.aborted
-
- // 2. If aborted is set, then return an aborted network error.
- if (aborted) {
- return makeNetworkError(new AbortError())
- }
-
- // 3. Return a network error.
- return makeNetworkError(context.terminated.reason)
- }
+ // 2. If aborted, then return the appropriate network error for fetchParams.
+ // TODO
}
case 'data:': {
// 1. Let dataURLStruct be the result of running the
@@ -876,7 +873,7 @@ async function schemeFetch (fetchParams) {
headersList: [
'content-type', contentType
],
- body: dataURLStruct.body
+ body: extractBody(dataURLStruct.body)[0]
})
}
case 'file:': {
@@ -888,8 +885,7 @@ async function schemeFetch (fetchParams) {
case 'https:': {
// Return the result of running HTTP fetch given fetchParams.
- return await httpFetch
- .call(this, fetchParams)
+ return await httpFetch(fetchParams)
.catch((err) => makeNetworkError(err))
}
default: {
@@ -907,14 +903,12 @@ function finalizeResponse (fetchParams, response) {
// task to run fetchParamsā€™s process response done given response, with
// fetchParamsā€™s task destination.
if (fetchParams.processResponseDone != null) {
- fetchParams.processResponseDone(response)
+ queueMicrotask(() => fetchParams.processResponseDone(response))
}
}
// https://fetch.spec.whatwg.org/#fetch-finale
-function fetchFinale (fetchParams, response) {
- const context = this
-
+async function fetchFinale (fetchParams, response) {
// 1. If response is a network error, then:
if (response.type === 'error') {
// 1. Set responseā€™s URL list to Ā« fetchParamsā€™s requestā€™s URL list[0] Ā».
@@ -928,40 +922,79 @@ function fetchFinale (fetchParams, response) {
}
// 2. Let processResponseEndOfBody be the following steps:
- // TODO
+ const processResponseEndOfBody = () => {
+ // 1. Set fetchParamsā€™s requestā€™s done flag.
+ fetchParams.request.done = true
+
+ // If fetchParamsā€™s process response end-of-body is not null,
+ // then queue a fetch task to run fetchParamsā€™s process response
+ // end-of-body given response with fetchParamsā€™s task destination.
+ if (fetchParams.processResponseEndOfBody != null) {
+ queueMicrotask(() => fetchParams.processResponseEndOfBody(response))
+ }
+ }
// 3. If fetchParamsā€™s process response is non-null, then queue a fetch task
// to run fetchParamsā€™s process response given response, with fetchParamsā€™s
// task destination.
if (fetchParams.processResponse != null) {
- fetchParams.processResponse(response)
+ queueMicrotask(() => fetchParams.processResponse(response))
}
- // 4. If fetchParamsā€™s process response is non-null, then queue a fetch task
- // to run fetchParamsā€™s process response given response, with fetchParamsā€™s
- // task destination.
- // TODO
+ // 4. If responseā€™s body is null, then run processResponseEndOfBody.
+ if (response.body == null) {
+ processResponseEndOfBody()
+ } else {
+ // 5. Otherwise:
- // 5. If responseā€™s body is null, then run processResponseEndOfBody.
- // TODO
+ // 1. Let transformStream be a new a TransformStream.
- // 6. Otherwise:
- // TODO
+ // 2. Let identityTransformAlgorithm be an algorithm which, given chunk,
+ // enqueues chunk in transformStream.
+ const identityTransformAlgorithm = (chunk, controller) => {
+ controller.enqueue(chunk)
+ }
- // 7. If fetchParamsā€™s process response consume body is non-null, then:
- // TODO
+ // 3. Set up transformStream with transformAlgorithm set to identityTransformAlgorithm
+ // and flushAlgorithm set to processResponseEndOfBody.
+ const transformStream = new TransformStream({
+ start () {},
+ transform: identityTransformAlgorithm,
+ flush: processResponseEndOfBody
+ })
- // TODO: This is a workaround. Until the above has been implemented, i.e.
- // we need to either fully consume the body or terminate the fetch.
- if (response.type === 'error') {
- context.terminate({ reason: response.error })
+ // 4. Set responseā€™s body to the result of piping responseā€™s body through transformStream.
+ response.body = { stream: response.body.stream.pipeThrough(transformStream) }
+ }
+
+ // 6. If fetchParamsā€™s process response consume body is non-null, then:
+ if (fetchParams.processResponseConsumeBody != null) {
+ // 1. Let processBody given nullOrBytes be this step: run fetchParamsā€™s
+ // process response consume body given response and nullOrBytes.
+ const processBody = (nullOrBytes) => fetchParams.processResponseConsumeBody(response, nullOrBytes)
+
+ // 2. Let processBodyError be this step: run fetchParamsā€™s process
+ // response consume body given response and failure.
+ const processBodyError = (failure) => fetchParams.processResponseConsumeBody(response, failure)
+
+ // 3. If responseā€™s body is null, then queue a fetch task to run processBody
+ // given null, with fetchParamsā€™s task destination.
+ if (response.body == null) {
+ queueMicrotask(() => processBody(null))
+ } else {
+ // 4. Otherwise, fully read responseā€™s body given processBody, processBodyError,
+ // and fetchParamsā€™s task destination.
+ try {
+ processBody(await response.body.stream.arrayBuffer())
+ } catch (err) {
+ processBodyError(err)
+ }
+ }
}
}
// https://fetch.spec.whatwg.org/#http-fetch
async function httpFetch (fetchParams) {
- const context = this
-
// 1. Let request be fetchParamsā€™s request.
const request = fetchParams.request
@@ -992,10 +1025,7 @@ async function httpFetch (fetchParams) {
// 3. Set response and actualResponse to the result of running
// HTTP-network-or-cache fetch given fetchParams.
- actualResponse = response = await httpNetworkOrCacheFetch.call(
- this,
- fetchParams
- )
+ actualResponse = response = await httpNetworkOrCacheFetch(fetchParams)
// 4. If requestā€™s response tainting is "cors" and a CORS check
// for request and response returns failure, then return a network error.
@@ -1035,7 +1065,7 @@ async function httpFetch (fetchParams) {
// and the connection uses HTTP/2, then user agents may, and are even
// encouraged to, transmit an RST_STREAM frame.
// See, https://github.com/whatwg/fetch/issues/1288
- context.connection.destroy()
+ fetchParams.controller.connection.destroy()
// 2. Switch on requestā€™s redirect mode:
if (request.redirect === 'error') {
@@ -1051,7 +1081,7 @@ async function httpFetch (fetchParams) {
} else if (request.redirect === 'follow') {
// Set response to the result of running HTTP-redirect fetch given
// fetchParams and response.
- response = await httpRedirectFetch.call(this, fetchParams, response)
+ response = await httpRedirectFetch(fetchParams, response)
} else {
assert(false)
}
@@ -1190,7 +1220,7 @@ async function httpRedirectFetch (fetchParams, response) {
setRequestReferrerPolicyOnRedirect(request, actualResponse)
// 19. Return the result of running main fetch given fetchParams and true.
- return mainFetch.call(this, fetchParams, true)
+ return mainFetch(fetchParams, true)
}
// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
@@ -1199,8 +1229,6 @@ async function httpNetworkOrCacheFetch (
isAuthenticationFetch = false,
isNewConnectionFetch = false
) {
- const context = this
-
// 1. Let request be fetchParamsā€™s request.
const request = fetchParams.request
@@ -1279,13 +1307,16 @@ async function httpNetworkOrCacheFetch (
httpRequest.headersList.append('content-length', contentLengthHeaderValue)
}
- // 9. If contentLength is non-null and httpRequestā€™s keepalive is true,
+ // 9. If contentLengthHeaderValue is non-null, then append (`Content-Length`,
+ // contentLengthHeaderValue) to httpRequestā€™s header list.
+
+ // 10. If contentLength is non-null and httpRequestā€™s keepalive is true,
// then:
if (contentLength != null && httpRequest.keepalive) {
// NOTE: keepalive is a noop outside of browser context.
}
- // 10 .If httpRequestā€™s referrer is a URL, then append
+ // 11. If httpRequestā€™s referrer is a URL, then append
// `Referer`/httpRequestā€™s referrer, serialized and isomorphic encoded,
// to httpRequestā€™s header list.
if (httpRequest.referrer instanceof URL) {
@@ -1293,20 +1324,20 @@ async function httpNetworkOrCacheFetch (
httpRequest.headersList.append('referer', httpRequest.referrer.href)
}
- // 11. Append a request `Origin` header for httpRequest.
+ // 12. Append a request `Origin` header for httpRequest.
appendRequestOriginHeader(httpRequest)
- // 12. Append the Fetch metadata headers for httpRequest. [FETCH-METADATA]
+ // 13. Append the Fetch metadata headers for httpRequest. [FETCH-METADATA]
appendFetchMetadata(httpRequest)
- // 13. If httpRequestā€™s header list does not contain `User-Agent`, then
+ // 14. If httpRequestā€™s header list does not contain `User-Agent`, then
// user agents should append `User-Agent`/default `User-Agent` value to
// httpRequestā€™s header list.
if (!httpRequest.headersList.has('user-agent')) {
httpRequest.headersList.append('user-agent', 'undici')
}
- // 14. If httpRequestā€™s cache mode is "default" and httpRequestā€™s header
+ // 15. If httpRequestā€™s cache mode is "default" and httpRequestā€™s header
// list contains `If-Modified-Since`, `If-None-Match`,
// `If-Unmodified-Since`, `If-Match`, or `If-Range`, then set
// httpRequestā€™s cache mode to "no-store".
@@ -1321,7 +1352,7 @@ async function httpNetworkOrCacheFetch (
httpRequest.cache = 'no-store'
}
- // 15. If httpRequestā€™s cache mode is "no-cache", httpRequestā€™s prevent
+ // 16. If httpRequestā€™s cache mode is "no-cache", httpRequestā€™s prevent
// no-cache cache-control header modification flag is unset, and
// httpRequestā€™s header list does not contain `Cache-Control`, then append
// `Cache-Control`/`max-age=0` to httpRequestā€™s header list.
@@ -1333,7 +1364,7 @@ async function httpNetworkOrCacheFetch (
httpRequest.headersList.append('cache-control', 'max-age=0')
}
- // 16. If httpRequestā€™s cache mode is "no-store" or "reload", then:
+ // 17. If httpRequestā€™s cache mode is "no-store" or "reload", then:
if (httpRequest.cache === 'no-store' || httpRequest.cache === 'reload') {
// 1. If httpRequestā€™s header list does not contain `Pragma`, then append
// `Pragma`/`no-cache` to httpRequestā€™s header list.
@@ -1348,13 +1379,13 @@ async function httpNetworkOrCacheFetch (
}
}
- // 17. If httpRequestā€™s header list contains `Range`, then append
+ // 18. If httpRequestā€™s header list contains `Range`, then append
// `Accept-Encoding`/`identity` to httpRequestā€™s header list.
if (httpRequest.headersList.has('range')) {
httpRequest.headersList.append('accept-encoding', 'identity')
}
- // 18. Modify httpRequestā€™s header list per HTTP. Do not append a given
+ // 19. Modify httpRequestā€™s header list per HTTP. Do not append a given
// header if httpRequestā€™s header list contains that headerā€™s name.
// TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129
if (!httpRequest.headersList.has('accept-encoding')) {
@@ -1365,7 +1396,7 @@ async function httpNetworkOrCacheFetch (
}
}
- // 19. If includeCredentials is true, then:
+ // 20. If includeCredentials is true, then:
if (includeCredentials) {
// 1. If the user agent is not configured to block cookies for httpRequest
// (see section 7 of [COOKIES]), then:
@@ -1374,26 +1405,26 @@ async function httpNetworkOrCacheFetch (
// TODO: credentials
}
- // 20. If thereā€™s a proxy-authentication entry, use it as appropriate.
+ // 21. If thereā€™s a proxy-authentication entry, use it as appropriate.
// TODO: proxy-authentication
- // 21. Set httpCache to the result of determining the HTTP cache
+ // 22. Set httpCache to the result of determining the HTTP cache
// partition, given httpRequest.
// TODO: cache
- // 22. If httpCache is null, then set httpRequestā€™s cache mode to
+ // 23. If httpCache is null, then set httpRequestā€™s cache mode to
// "no-store".
if (httpCache == null) {
httpRequest.cache = 'no-store'
}
- // 23. If httpRequestā€™s cache mode is neither "no-store" nor "reload",
+ // 24. If httpRequestā€™s cache mode is neither "no-store" nor "reload",
// then:
if (httpRequest.mode !== 'no-store' && httpRequest.mode !== 'reload') {
// TODO: cache
}
- // 9. If aborted, then:
+ // 9. If aborted, then return the appropriate network error for fetchParams.
// TODO
// 10. If response is null, then:
@@ -1406,8 +1437,7 @@ async function httpNetworkOrCacheFetch (
// 2. Let forwardResponse be the result of running HTTP-network fetch
// given httpFetchParams, includeCredentials, and isNewConnectionFetch.
- const forwardResponse = await httpNetworkFetch.call(
- this,
+ const forwardResponse = await httpNetworkFetch(
httpFetchParams,
includeCredentials,
isNewConnectionFetch
@@ -1451,12 +1481,15 @@ async function httpNetworkOrCacheFetch (
response.rangeRequested = true
}
- // 13. If responseā€™s status is 401, httpRequestā€™s response tainting is not
+ // 13. Set responseā€™s request-includes-credentials to includeCredentials.
+ response.requestIncludesCredentials = includeCredentials
+
+ // 14. If responseā€™s status is 401, httpRequestā€™s response tainting is not
// "cors", includeCredentials is true, and requestā€™s window is an environment
// settings object, then:
// TODO
- // 14. If responseā€™s status is 407, then:
+ // 15. If responseā€™s status is 407, then:
if (response.status === 407) {
// 1. If requestā€™s window is "no-window", then return a network error.
if (request.window === 'no-window') {
@@ -1465,18 +1498,9 @@ async function httpNetworkOrCacheFetch (
// 2. ???
- // 3. If the ongoing fetch is terminated, then:
- if (context.terminated) {
- // 1. Let aborted be the terminationā€™s aborted flag.
- const aborted = context.terminated.aborted
-
- // 2. If aborted is set, then return an aborted network error.
- if (aborted) {
- return makeNetworkError(new AbortError())
- }
-
- // 3. Return a network error.
- return makeNetworkError(context.terminated.reason)
+ // 3. If fetchParams is canceled, then return the appropriate network error for fetchParams.
+ if (isCancelled(fetchParams)) {
+ return makeAppropriateNetworkError(fetchParams)
}
// 4. Prompt the end user as appropriate in requestā€™s window and store
@@ -1489,7 +1513,7 @@ async function httpNetworkOrCacheFetch (
return makeNetworkError('proxy authentication required')
}
- // 15. If all of the following are true
+ // 16. If all of the following are true
if (
// responseā€™s status is 421
response.status === 421 &&
@@ -1500,18 +1524,9 @@ async function httpNetworkOrCacheFetch (
) {
// then:
- // 1. If the ongoing fetch is terminated, then:
- if (context.terminated) {
- // 1. Let aborted be the terminationā€™s aborted flag.
- const aborted = context.terminated.aborted
-
- // 2. If aborted is set, then return an aborted network error.
- if (aborted) {
- return makeNetworkError(new AbortError())
- }
-
- // 3. Return a network error.
- return makeNetworkError(context.terminated.reason)
+ // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
+ if (isCancelled(fetchParams)) {
+ return makeAppropriateNetworkError(fetchParams)
}
// 2. Set response to the result of running HTTP-network-or-cache
@@ -1520,313 +1535,405 @@ async function httpNetworkOrCacheFetch (
// TODO (spec): The spec doesn't specify this but we need to cancel
// the active response before we can start a new one.
// https://github.com/whatwg/fetch/issues/1293
- context.connection.destroy()
+ fetchParams.controller.connection.destroy()
- response = await httpNetworkOrCacheFetch.call(
- this,
+ response = await httpNetworkOrCacheFetch(
fetchParams,
isAuthenticationFetch,
true
)
}
- // 16. If isAuthenticationFetch is true, then create an authentication entry
+ // 17. If isAuthenticationFetch is true, then create an authentication entry
if (isAuthenticationFetch) {
// TODO
}
- // 17. Return response.
+ // 18. Return response.
return response
}
// https://fetch.spec.whatwg.org/#http-network-fetch
-function httpNetworkFetch (
+async function httpNetworkFetch (
fetchParams,
includeCredentials = false,
forceNewConnection = false
) {
- const context = this
-
- return new Promise((resolve) => {
- assert(!context.connection || context.connection.destroyed)
-
- context.connection = {
- abort: null,
- destroyed: false,
- destroy (err) {
- if (!this.destroyed) {
- this.destroyed = true
- this.abort?.(err ?? new AbortError())
- }
+ assert(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed)
+
+ fetchParams.controller.connection = {
+ abort: null,
+ destroyed: false,
+ destroy (err) {
+ if (!this.destroyed) {
+ this.destroyed = true
+ this.abort?.(err ?? new AbortError())
}
}
+ }
- // 1. Let request be fetchParamsā€™s request.
- const request = fetchParams.request
+ // 1. Let request be fetchParamsā€™s request.
+ const request = fetchParams.request
- // 2. Let response be null.
- let response = null
+ // 2. Let response be null.
+ let response = null
- // 3. Let timingInfo be fetchParamsā€™s timing info.
- const timingInfo = fetchParams.timingInfo
+ // 3. Let timingInfo be fetchParamsā€™s timing info.
+ const timingInfo = fetchParams.timingInfo
- // 4. Let httpCache be the result of determining the HTTP cache partition,
- // given request.
- // TODO: cache
- const httpCache = null
+ // 4. Let httpCache be the result of determining the HTTP cache partition,
+ // given request.
+ // TODO: cache
+ const httpCache = null
- // 5. If httpCache is null, then set requestā€™s cache mode to "no-store".
- if (httpCache == null) {
- request.cache = 'no-store'
- }
+ // 5. If httpCache is null, then set requestā€™s cache mode to "no-store".
+ if (httpCache == null) {
+ request.cache = 'no-store'
+ }
+
+ // 6. Let networkPartitionKey be the result of determining the network
+ // partition key given request.
+ // TODO
- // 6. Let networkPartitionKey be the result of determining the network
- // partition key given request.
+ // 7. Let newConnection be "yes" if forceNewConnection is true; otherwise
+ // "no".
+ const newConnection = forceNewConnection ? 'yes' : 'no' // eslint-disable-line no-unused-vars
+
+ // 8. Switch on requestā€™s mode:
+ if (request.mode === 'websocket') {
+ // Let connection be the result of obtaining a WebSocket connection,
+ // given requestā€™s current URL.
+ // TODO
+ } else {
+ // Let connection be the result of obtaining a connection, given
+ // networkPartitionKey, requestā€™s current URLā€™s origin,
+ // includeCredentials, and forceNewConnection.
// TODO
+ }
- // 7. Switch on requestā€™s mode:
- if (request.mode === 'websocket') {
- // Let connection be the result of obtaining a WebSocket connection,
- // given requestā€™s current URL.
- // TODO
- } else {
- // Let connection be the result of obtaining a connection, given
- // networkPartitionKey, requestā€™s current URLā€™s origin,
- // includeCredentials, and forceNewConnection.
- // TODO
- }
+ // 9. Run these steps, but abort when the ongoing fetch is terminated:
- // 8. Run these steps, but abort when the ongoing fetch is terminated:
- // TODO: When do we cleanup this listener?
- context.on('terminated', onRequestAborted)
+ // 1. If connection is failure, then return a network error.
- // 5. Set response to the result of making an HTTP request over connection
- // using request with the following caveats:
+ // 2. Set timingInfoā€™s final connection timing info to the result of
+ // calling clamp and coarsen connection timing info with connectionā€™s
+ // timing info, timingInfoā€™s post-redirect start time, and fetchParamsā€™s
+ // cross-origin isolated capability.
- // Follow the relevant requirements from HTTP. [HTTP] [HTTP-SEMANTICS]
- // [HTTP-COND] [HTTP-CACHING] [HTTP-AUTH]
+ // 3. If connection is not an HTTP/2 connection, requestā€™s body is non-null,
+ // and requestā€™s bodyā€™s source is null, then append (`Transfer-Encoding`,
+ // `chunked`) to requestā€™s header list.
- // If requestā€™s body is non-null, and requestā€™s bodyā€™s source is null,
- // then the user agent may have a buffer of up to 64 kibibytes and store
- // a part of requestā€™s body in that buffer. If the user agent reads from
- // requestā€™s body beyond that bufferā€™s size and the user agent needs to
- // resend request, then instead return a network error.
- // TODO
+ // 4. Set timingInfoā€™s final network-request start time to the coarsened
+ // shared current time given fetchParamsā€™s cross-origin isolated
+ // capability.
- // Set timingInfoā€™s final network-response start time to the coarsened
- // shared current time given fetchParamsā€™s cross-origin isolated capability,
- // immediately after the user agentā€™s HTTP parser receives the first byte
- // of the response (e.g., frame header bytes for HTTP/2 or response status
- // line for HTTP/1.x).
- // TODO
+ // 5. Set response to the result of making an HTTP request over connection
+ // using request with the following caveats:
- // Wait until all the headers are transmitted.
+ // - Follow the relevant requirements from HTTP. [HTTP] [HTTP-SEMANTICS]
+ // [HTTP-COND] [HTTP-CACHING] [HTTP-AUTH]
- // Any responses whose status is in the range 100 to 199, inclusive,
- // and is not 101, are to be ignored, except for the purposes of setting
- // timingInfoā€™s final network-response start time above.
+ // - If requestā€™s body is non-null, and requestā€™s bodyā€™s source is null,
+ // then the user agent may have a buffer of up to 64 kibibytes and store
+ // a part of requestā€™s body in that buffer. If the user agent reads from
+ // requestā€™s body beyond that bufferā€™s size and the user agent needs to
+ // resend request, then instead return a network error.
- // If requestā€™s header list contains `Transfer-Encoding`/`chunked` and
- // response is transferred via HTTP/1.0 or older, then return a network
- // error.
+ // - Set timingInfoā€™s final network-response start time to the coarsened
+ // shared current time given fetchParamsā€™s cross-origin isolated capability,
+ // immediately after the user agentā€™s HTTP parser receives the first byte
+ // of the response (e.g., frame header bytes for HTTP/2 or response status
+ // line for HTTP/1.x).
- // If the HTTP request results in a TLS client certificate dialog, then:
+ // - Wait until all the headers are transmitted.
- // 1. If requestā€™s window is an environment settings object, make the
- // dialog available in requestā€™s window.
+ // - Any responses whose status is in the range 100 to 199, inclusive,
+ // and is not 101, are to be ignored, except for the purposes of setting
+ // timingInfoā€™s final network-response start time above.
- // 2. Otherwise, return a network error.
+ // - If requestā€™s header list contains `Transfer-Encoding`/`chunked` and
+ // response is transferred via HTTP/1.0 or older, then return a network
+ // error.
- // To transmit requestā€™s body body, run these steps:
- const body = (async function * () {
- try {
- // 1. If body is null and fetchParamsā€™s process request end-of-body is
- // non-null, then queue a fetch task given fetchParamsā€™s process request
- // end-of-body and fetchParamsā€™s task destination.
- if (request.body === null) {
- fetchParams.processEndOfBody?.()
- return
- }
+ // - If the HTTP request results in a TLS client certificate dialog, then:
- // 2. Otherwise, if body is non-null:
+ // 1. If requestā€™s window is an environment settings object, make the
+ // dialog available in requestā€™s window.
- // 1. Let processBodyChunk given bytes be these steps:
- for await (const bytes of request.body.stream) {
- // 1. If the ongoing fetch is terminated, then abort these steps.
- if (context.terminated) {
- return
- }
+ // 2. Otherwise, return a network error.
- // 2. Run this step in parallel: transmit bytes.
- yield bytes
+ // To transmit requestā€™s body body, run these steps:
+ let requestBody = null
+ // 1. If body is null and fetchParamsā€™s process request end-of-body is
+ // non-null, then queue a fetch task given fetchParamsā€™s process request
+ // end-of-body and fetchParamsā€™s task destination.
+ if (request.body == null && fetchParams.processRequestEndOfBody) {
+ queueMicrotask(() => fetchParams.processRequestEndOfBody())
+ } else if (request.body != null) {
+ // 2. Otherwise, if body is non-null:
- // 3. If fetchParamsā€™s process request body is non-null, then run
- // fetchParamsā€™s process request body given bytesā€™s length.
- fetchParams.processRequestBody?.(bytes.byteLength)
- }
+ // 1. Let processBodyChunk given bytes be these steps:
+ const processBodyChunk = async function * (bytes) {
+ // 1. If the ongoing fetch is terminated, then abort these steps.
+ if (isCancelled(fetchParams)) {
+ return
+ }
- // 2. Let processEndOfBody be these steps:
+ // 2. Run this step in parallel: transmit bytes.
+ yield bytes
- // 1. If the ongoing fetch is terminated, then abort these steps.
- if (context.terminated) {
- return
- }
+ // 3. If fetchParamsā€™s process request body is non-null, then run
+ // fetchParamsā€™s process request body given bytesā€™s length.
+ fetchParams.processRequestBodyChunkLength?.(bytes.byteLength)
+ }
- // 2. If fetchParamsā€™s process request end-of-body is non-null,
- // then run fetchParamsā€™s process request end-of-body.
- fetchParams.processRequestEndOfBody?.()
- } catch (e) {
- // 3. Let processBodyError given e be these steps:
+ // 2. Let processEndOfBody be these steps:
+ const processEndOfBody = () => {
+ // 1. If fetchParams is canceled, then abort these steps.
+ if (isCancelled(fetchParams)) {
+ return
+ }
- // 1. If the ongoing fetch is terminated, then abort these steps.
- if (context.terminated) {
- return
- }
+ // 2. If fetchParamsā€™s process request end-of-body is non-null,
+ // then run fetchParamsā€™s process request end-of-body.
+ if (fetchParams.processRequestEndOfBody) {
+ fetchParams.processRequestEndOfBody()
+ }
+ }
- // 2. If e is an "AbortError" DOMException, then terminate the ongoing fetch with the aborted flag set.
- // 3. Otherwise, terminate the ongoing fetch.
- context.terminate({
- aborted: e.name === 'AbortError',
- reason: e
- })
+ // 3. Let processBodyError given e be these steps:
+ const processBodyError = (e) => {
+ // 1. If fetchParams is canceled, then abort these steps.
+ if (isCancelled(fetchParams)) {
+ return
+ }
+
+ // 2. If e is an "AbortError" DOMException, then abort fetchParamsā€™s controller.
+ if (e.name === 'AbortError') {
+ fetchParams.controller.abort()
+ } else {
+ fetchParams.controller.terminate(e)
+ }
+ }
+
+ // 4. Incrementally read requestā€™s body given processBodyChunk, processEndOfBody,
+ // processBodyError, and fetchParamsā€™s task destination.
+ requestBody = (async function * () {
+ try {
+ for await (const bytes of request.body.stream) {
+ yield * processBodyChunk(bytes)
+ }
+ processEndOfBody()
+ } catch (err) {
+ processBodyError(err)
}
})()
+ }
- // 9. If aborted, then:
- function onRequestAborted () {
- // 1. Let aborted be the terminationā€™s aborted flag.
- const aborted = this.terminated.aborted
+ try {
+ const { body, status, statusText, headersList } = await dispatch({ body: requestBody })
- // 2. If connection uses HTTP/2, then transmit an RST_STREAM frame.
- this.connection.destroy()
+ const iterator = body[Symbol.asyncIterator]()
+ fetchParams.controller.next = () => iterator.next()
- // 3. If aborted is set, then return an aborted network error.
- if (aborted) {
- return resolve(makeNetworkError(new AbortError()))
- }
+ response = makeResponse({ status, statusText, headersList })
+ } catch (err) {
+ // 10. If aborted, then:
+ if (err.name === 'AbortError') {
+ // 1. If connection uses HTTP/2, then transmit an RST_STREAM frame.
+ fetchParams.controller.connection.destroy()
- // 4. Return a network error.
- return resolve(makeNetworkError(this.terminated.reason))
+ // 2. Return the appropriate network error for fetchParams.
+ return makeAppropriateNetworkError(fetchParams)
}
- // 10. Let pullAlgorithm be an action that resumes the ongoing fetch
- // if it is suspended.
- let pullAlgorithm
+ return makeNetworkError(err)
+ }
- // 11. Let cancelAlgorithm be an action that terminates the ongoing
- // fetch with the aborted flag set.
- const cancelAlgorithm = () => {
- context.terminate({ aborted: true })
- }
+ // 11. Let pullAlgorithm be an action that resumes the ongoing fetch
+ // if it is suspended.
+ const pullAlgorithm = () => {
+ fetchParams.controller.resume()
+ }
- // 12. Let highWaterMark be a non-negative, non-NaN number, chosen by
- // the user agent.
- const highWaterMark = 64 * 1024 // Same as nodejs fs streams.
+ // 12. Let cancelAlgorithm be an algorithm that aborts fetchParamsā€™s
+ // controller.
+ const cancelAlgorithm = () => {
+ fetchParams.controller.abort()
+ }
- // 13. Let sizeAlgorithm be an algorithm that accepts a chunk object
- // and returns a non-negative, non-NaN, non-infinite number, chosen by the user agent.
- // TODO
+ // 13. Let highWaterMark be a non-negative, non-NaN number, chosen by
+ // the user agent.
+ // TODO
- // 14. Let stream be a new ReadableStream.
- // 15. Set up stream with pullAlgorithm set to pullAlgorithm,
- // cancelAlgorithm set to cancelAlgorithm, highWaterMark set to
- // highWaterMark, and sizeAlgorithm set to sizeAlgorithm.
- if (!ReadableStream) {
- ReadableStream = require('stream/web').ReadableStream
- }
+ // 14. Let sizeAlgorithm be an algorithm that accepts a chunk object
+ // and returns a non-negative, non-NaN, non-infinite number, chosen by the user agent.
+ // TODO
- let pullResolve
+ // 15. Let stream be a new ReadableStream.
+ // 16. Set up stream with pullAlgorithm set to pullAlgorithm,
+ // cancelAlgorithm set to cancelAlgorithm, highWaterMark set to
+ // highWaterMark, and sizeAlgorithm set to sizeAlgorithm.
+ if (!ReadableStream) {
+ ReadableStream = require('stream/web').ReadableStream
+ }
- const stream = new ReadableStream(
- {
- async start (controller) {
- context.controller = controller
- },
- async pull (controller) {
- if (!pullAlgorithm) {
- await new Promise((resolve) => {
- pullResolve = resolve
- })
- }
- await pullAlgorithm(controller)
- },
- async cancel (reason) {
- await cancelAlgorithm(reason)
- }
+ const stream = new ReadableStream(
+ {
+ async start (controller) {
+ fetchParams.controller.controller = controller
},
- { highWaterMark }
- )
+ async pull (controller) {
+ await pullAlgorithm(controller)
+ },
+ async cancel (reason) {
+ await cancelAlgorithm(reason)
+ }
+ },
+ { highWaterMark: 0 }
+ )
- // 16. Run these steps, but abort when the ongoing fetch is terminated:
- // TODO
+ // 17. Run these steps, but abort when the ongoing fetch is terminated:
+
+ // 1. Set responseā€™s body to a new body whose stream is stream.
+ response.body = { stream }
+
+ // 2. If response is not a network error and requestā€™s cache mode is
+ // not "no-store", then update response in httpCache for request.
+ // TODO
- // 17. If aborted, then:
- // TODO: How can this happen? The steps above are not async?
-
- // 18. Run these steps in parallel:
- // 1. Run these steps, but abort when the ongoing fetch is terminated:
- // 1. While true:
- // 1. If one or more bytes have been transmitted from responseā€™s
- // message body, then:
- // NOTE: See onHeaders
- // 2. Otherwise, if the bytes transmission for responseā€™s message
- // body is done normally and stream is readable, then close stream,
- // finalize response for fetchParams and response, and abort these
- // in-parallel steps.
- // NOTE: See onHeaders
-
- // 2. If aborted, then:
- function onResponseAborted () {
- // 1. Let aborted be the terminationā€™s aborted flag.
- const aborted = this.terminated.aborted
-
- // 2. If aborted is set, then:
- if (aborted) {
- // 1. Set responseā€™s aborted flag.
- response.aborted = true
-
- // 2. If stream is readable, error stream with an "AbortError" DOMException.
- if (isReadable(stream)) {
- this.controller.error(new AbortError())
+ // 3. If includeCredentials is true and the user agent is not configured
+ // to block cookies for request (see section 7 of [COOKIES]), then run the
+ // "set-cookie-string" parsing algorithm (see section 5.2 of [COOKIES]) on
+ // the value of each header whose name is a byte-case-insensitive match for
+ // `Set-Cookie` in responseā€™s header list, if any, and requestā€™s current URL.
+ // TODO
+
+ // 18. If aborted, then:
+ // TODO
+
+ // 19. Run these steps in parallel:
+
+ // 1. Run these steps, but abort when fetchParams is canceled:
+ fetchParams.controller.on('terminated', onAborted)
+ fetchParams.controller.resume = async () => {
+ // 1. While true
+ while (true) {
+ // 1-3. See onData...
+
+ // 4. Set bytes to the result of handling content codings given
+ // codings and bytes.
+ let bytes
+ try {
+ const { done, value } = await fetchParams.controller.next()
+ bytes = done ? undefined : value
+ } catch (err) {
+ if (fetchParams.controller.ended && !timingInfo.encodedBodySize) {
+ // zlib doesn't like empty streams.
+ bytes = undefined
+ } else {
+ bytes = err
}
- } else {
- // 3. Otherwise, if stream is readable, error stream with a TypeError.
- if (isReadable(stream)) {
- this.controller.error(new TypeError('terminated'))
+ }
+
+ if (bytes === undefined) {
+ // 2. Otherwise, if the bytes transmission for responseā€™s message
+ // body is done normally and stream is readable, then close
+ // stream, finalize response for fetchParams and response, and
+ // abort these in-parallel steps.
+ try {
+ fetchParams.controller.controller.close()
+ } catch (err) {
+ // TODO (fix): How/Why can this happen? Do we have a bug?
+ if (!/Controller is already closed/.test(err)) {
+ throw err
+ }
}
+
+ finalizeResponse(fetchParams, response)
+
+ return
}
- // 4. If connection uses HTTP/2, then transmit an RST_STREAM frame.
- // 5. Otherwise, the user agent should close connection unless it would be bad for performance to do so.
- this.connection.destroy()
+ // 5. Increase timingInfoā€™s decoded body size by bytesā€™s length.
+ timingInfo.decodedBodySize += bytes?.byteLength ?? 0
+
+ // 6. If bytes is failure, then terminate fetchParamsā€™s controller.
+ if (bytes instanceof Error) {
+ fetchParams.controller.terminate(bytes)
+ return
+ }
+
+ // 7. Enqueue a Uint8Array wrapping an ArrayBuffer containing bytes
+ // into stream.
+ fetchParams.controller.controller.enqueue(new Uint8Array(bytes))
+
+ // 8. If stream is errored, then terminate the ongoing fetch.
+ if (isErrored(stream)) {
+ fetchParams.controller.terminate()
+ return
+ }
+
+ // 9. If stream doesnā€™t need more data ask the user agent to suspend
+ // the ongoing fetch.
+ if (!fetchParams.controller.controller.desiredSize) {
+ return
+ }
+ }
+ }
+
+ // 2. If aborted, then:
+ function onAborted (reason) {
+ // 2. If fetchParams is aborted, then:
+ if (isAborted(fetchParams)) {
+ // 1. Set responseā€™s aborted flag.
+ response.aborted = true
+
+ // 2. If stream is readable, error stream with an "AbortError" DOMException.
+ if (isReadable(stream)) {
+ fetchParams.controller.controller.error(new AbortError())
+ }
+ } else {
+ // 3. Otherwise, if stream is readable, error stream with a TypeError.
+ if (isReadable(stream)) {
+ fetchParams.controller.controller.error(new TypeError('terminated', {
+ cause: reason instanceof Error ? reason : undefined
+ }))
+ }
}
- // 19. Return response.
- // NOTE: See onHeaders
+ // 4. If connection uses HTTP/2, then transmit an RST_STREAM frame.
+ // 5. Otherwise, the user agent should close connection unless it would be bad for performance to do so.
+ fetchParams.controller.connection.destroy()
+ }
+
+ // 20. Return response.
+ return response
- // Implementation
+ async function dispatch ({ body }) {
const url = requestCurrentURL(request)
- context.dispatcher.dispatch(
+ return new Promise((resolve, reject) => fetchParams.controller.dispatcher.dispatch(
{
path: url.pathname + url.search,
origin: url.origin,
method: request.method,
- body: context.dispatcher[kIsMockActive] ? request.body && request.body.source : body,
+ body: fetchParams.controller.dispatcher[kIsMockActive] ? request.body && request.body.source : body,
headers: request.headersList,
maxRedirections: 0
},
{
- decoder: null,
+ body: null,
abort: null,
- context,
onConnect (abort) {
// TODO (fix): Do we need connection here?
- const { connection } = this.context
+ const { connection } = fetchParams.controller
if (connection.destroyed) {
abort(new AbortError())
} else {
+ fetchParams.controller.on('terminated', abort)
this.abort = connection.abort = abort
}
},
@@ -1836,29 +1943,21 @@ function httpNetworkFetch (
return
}
+ let codings = []
+
const headers = new Headers()
for (let n = 0; n < headersList.length; n += 2) {
- headers.append(
- headersList[n + 0].toString(),
- headersList[n + 1].toString()
- )
- }
+ const key = headersList[n + 0].toString()
+ const val = headersList[n + 1].toString()
- response = makeResponse({
- status,
- statusText,
- headersList: headers[kHeadersList],
- body: { stream }
- })
+ if (key.toLowerCase() === 'content-encoding') {
+ codings = val.split(',').map((x) => x.trim())
+ }
- this.context.on('terminated', onResponseAborted)
+ headers.append(key, val)
+ }
- const codings =
- headers
- .get('content-encoding')
- ?.toLowerCase()
- .split(',')
- .map((x) => x.trim()) ?? []
+ this.body = new Readable({ read: resume })
const decoders = []
@@ -1876,118 +1975,65 @@ function httpNetworkFetch (
}
}
- if (decoders.length > 1) {
- pipeline(...decoders, () => {})
- } else if (decoders.length === 0) {
- // TODO (perf): Avoid intermediate.
- decoders.push(new PassThrough())
- }
-
- this.decoder = decoders[0].on('drain', resume)
-
- const iterator = decoders[decoders.length - 1][Symbol.asyncIterator]()
-
- pullAlgorithm = async (controller) => {
- // 4. Set bytes to the result of handling content codings given
- // codings and bytes.
- let bytes
- try {
- const { done, value } = await iterator.next()
- bytes = done ? undefined : value
- } catch (err) {
- if (this.decoder.writableEnded && !timingInfo.encodedBodySize) {
- // zlib doesn't like empty streams.
- bytes = undefined
- } else {
- bytes = err
- }
- }
-
- if (bytes === undefined) {
- // 2. Otherwise, if the bytes transmission for responseā€™s message
- // body is done normally and stream is readable, then close
- // stream, finalize response for fetchParams and response, and
- // abort these in-parallel steps.
- finalizeResponse(fetchParams, response)
-
- controller.close()
-
- return
- }
-
- // 5. Increase timingInfoā€™s decoded body size by bytesā€™s length.
- timingInfo.decodedBodySize += bytes?.byteLength ?? 0
-
- // 6. If bytes is failure, then terminate the ongoing fetch.
- if (bytes instanceof Error) {
- this.context.terminate({ reason: bytes })
- return
- }
-
- // 7. Enqueue a Uint8Array wrapping an ArrayBuffer containing bytes
- // into stream.
- controller.enqueue(new Uint8Array(bytes))
-
- // 8. If stream is errored, then terminate the ongoing fetch.
- if (isErrored(stream)) {
- this.context.terminate()
- return
- }
-
- // 9. If stream doesnā€™t need more data ask the user agent to suspend
- // the ongoing fetch.
- return controller.desiredSize > 0
- }
-
- if (pullResolve) {
- pullResolve()
- pullResolve = null
- }
-
- resolve(response)
+ resolve({
+ status,
+ statusText,
+ headersList: headers[kHeadersList],
+ body: decoders.length
+ ? pipeline(this.body, ...decoders, () => {})
+ : this.body.on('error', () => {})
+ })
return true
},
onData (chunk) {
- if (this.context.dump) {
+ if (fetchParams.controller.dump) {
return
}
- // 1. If one or more bytes have been transmitted from responseā€™s
- // message body, then:
+ // 1. If one or more bytes have been transmitted from responseā€™s
+ // message body, then:
- // 1. Let bytes be the transmitted bytes.
+ // 1. Let bytes be the transmitted bytes.
const bytes = chunk
- // 2. Let codings be the result of extracting header list values
- // given `Content-Encoding` and responseā€™s header list.
- // See pullAlgorithm.
+ // 2. Let codings be the result of extracting header list values
+ // given `Content-Encoding` and responseā€™s header list.
+ // See pullAlgorithm.
- // 3. Increase timingInfoā€™s encoded body size by bytesā€™s length.
+ // 3. Increase timingInfoā€™s encoded body size by bytesā€™s length.
timingInfo.encodedBodySize += bytes.byteLength
- // 4. See pullAlgorithm...
+ // 4. See pullAlgorithm...
- return this.decoder.write(bytes)
+ return this.body.push(bytes)
},
onComplete () {
- this.decoder.end()
+ if (this.abort) {
+ fetchParams.controller.off('terminated', this.abort)
+ }
+
+ fetchParams.controller.ended = true
+
+ this.body.push(null)
},
onError (error) {
- this.decoder?.destroy(error)
+ if (this.abort) {
+ fetchParams.controller.off('terminated', this.abort)
+ }
- this.context.terminate({ reason: error })
+ this.body?.destroy(error)
- if (!response) {
- resolve(makeNetworkError(error))
- }
+ fetchParams.controller.terminate(error)
+
+ reject(makeNetworkError(error))
}
}
- )
- })
+ ))
+ }
}
module.exports = fetch
diff --git a/deps/undici/src/lib/fetch/request.js b/deps/undici/src/lib/fetch/request.js
index adb5639229a..151dc8e4411 100644
--- a/deps/undici/src/lib/fetch/request.js
+++ b/deps/undici/src/lib/fetch/request.js
@@ -7,9 +7,9 @@ const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
const util = require('../core/util')
const {
isValidHTTPToken,
- EnvironmentSettingsObject,
sameOrigin,
- toUSVString
+ toUSVString,
+ normalizeMethod
} = require('./util')
const {
forbiddenMethods,
@@ -81,9 +81,7 @@ class Request {
try {
parsedURL = new URL(input, baseUrl)
} catch (err) {
- const error = new TypeError('Failed to parse URL from ' + input)
- error.cause = err
- throw error
+ throw new TypeError('Failed to parse URL from ' + input, { cause: err })
}
// 3. If parsedURL includes credentials, then throw a TypeError.
@@ -121,7 +119,7 @@ class Request {
// 9. If requestā€™s window is an environment settings object and its origin
// is same origin with origin, then set window to requestā€™s window.
if (
- request.window instanceof EnvironmentSettingsObject &&
+ request.window?.constructor?.name === 'EnvironmentSettingsObject' &&
sameOrigin(request.window, origin)
) {
window = request.window
@@ -149,7 +147,7 @@ class Request {
// unsafe-request flag Set.
unsafeRequest: request.unsafeRequest,
// client Thisā€™s relevant settings object.
- client: request.client,
+ client: this[kRealm].settingsObject,
// window window.
window,
// priority requestā€™s priority.
@@ -179,8 +177,7 @@ class Request {
// history-navigation flag requestā€™s history-navigation flag.
historyNavigation: request.historyNavigation,
// URL list A clone of requestā€™s URL list.
- // undici implementation note: urlList is cloned in makeRequest
- urlList: request.urlList
+ urlList: [...request.urlList]
})
// 13. If init is not empty, then:
@@ -228,11 +225,7 @@ class Request {
try {
parsedReferrer = new URL(referrer, baseUrl)
} catch (err) {
- const error = new TypeError(
- `Referrer "${referrer}" is not a valid URL.`
- )
- error.cause = err
- throw error
+ throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err })
}
// 3. If one of the following is true
@@ -346,8 +339,7 @@ class Request {
}
// 3. Normalize method.
- // https://fetch.spec.whatwg.org/#concept-method-normalize
- method = init.method.toUpperCase()
+ method = normalizeMethod(init.method)
// 4. Set requestā€™s method to method.
request.method = method
@@ -722,7 +714,7 @@ class Request {
}
// 1. If this is unusable, then throw a TypeError.
- if (this.bodyUsed || (this.body && this.body.locked)) {
+ if (this.bodyUsed || this.body?.locked) {
throw new TypeError('unusable')
}
diff --git a/deps/undici/src/lib/fetch/response.js b/deps/undici/src/lib/fetch/response.js
index 4449d364005..64fc3170dac 100644
--- a/deps/undici/src/lib/fetch/response.js
+++ b/deps/undici/src/lib/fetch/response.js
@@ -1,14 +1,16 @@
'use strict'
const { Headers, HeadersList, fill } = require('./headers')
+const { AbortError } = require('../core/errors')
const { extractBody, cloneBody, mixinBody } = require('./body')
const util = require('../core/util')
const { kEnumerableProperty } = util
-const { responseURL, isValidReasonPhrase, toUSVString } = require('./util')
+const { responseURL, isValidReasonPhrase, toUSVString, isCancelled, isAborted } = require('./util')
const {
redirectStatus,
nullBodyStatus,
- forbiddenResponseHeaderNames
+ forbiddenResponseHeaderNames,
+ corsSafeListedResponseHeaderNames
} = require('./constants')
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
const { kHeadersList } = require('../core/symbols')
@@ -337,10 +339,10 @@ function cloneResponse (response) {
function makeResponse (init) {
return {
- internalResponse: null,
aborted: false,
rangeRequested: false,
timingAllowPassed: false,
+ requestIncludesCredentials: false,
type: 'default',
status: 200,
timingInfo: null,
@@ -361,11 +363,55 @@ function makeNetworkError (reason) {
error:
reason instanceof Error
? reason
- : new Error(reason ? String(reason) : reason),
+ : new Error(reason ? String(reason) : reason, {
+ cause: reason instanceof Error ? reason : undefined
+ }),
aborted: reason && reason.name === 'AbortError'
})
}
+function makeFilteredResponse (response, state) {
+ state = {
+ internalResponse: response,
+ ...state
+ }
+
+ return new Proxy(response, {
+ get (target, p) {
+ return p in state ? state[p] : target[p]
+ },
+ set (target, p, value) {
+ assert(!(p in state))
+ target[p] = value
+ return true
+ }
+ })
+}
+
+function makeFilteredHeadersList (headersList, filter) {
+ return new Proxy(headersList, {
+ get (target, prop) {
+ // Override methods used by Headers class.
+ if (prop === 'get' || prop === 'has') {
+ return (name) => filter(name) ? target[prop](name) : undefined
+ } else if (prop === 'slice') {
+ return (...args) => {
+ assert(args.length === 0)
+ const arr = []
+ for (let index = 0; index < target.length; index += 2) {
+ if (filter(target[index])) {
+ arr.push(target[index], target[index + 1])
+ }
+ }
+ return arr
+ }
+ } else {
+ return target[prop]
+ }
+ }
+ })
+}
+
// https://fetch.spec.whatwg.org/#concept-filtered-response
function filterResponse (response, type) {
// Set response to the following filtered response with response as its
@@ -375,18 +421,9 @@ function filterResponse (response, type) {
// and header list excludes any headers in internal responseā€™s header list
// whose name is a forbidden response-header name.
- const headers = []
- for (let n = 0; n < response.headersList.length; n += 2) {
- if (!forbiddenResponseHeaderNames.includes(response.headersList[n])) {
- headers.push(response.headersList[n + 0], response.headersList[n + 1])
- }
- }
-
- return makeResponse({
- ...response,
- internalResponse: response,
- headersList: new HeadersList(...headers),
- type: 'basic'
+ return makeFilteredResponse(response, {
+ type: 'basic',
+ headersList: makeFilteredHeadersList(response.headersList, (name) => !forbiddenResponseHeaderNames.includes(name))
})
} else if (type === 'cors') {
// A CORS filtered response is a filtered response whose type is "cors"
@@ -394,22 +431,18 @@ function filterResponse (response, type) {
// list whose name is not a CORS-safelisted response-header name, given
// internal responseā€™s CORS-exposed header-name list.
- // TODO: This is not correct...
- return makeResponse({
- ...response,
- internalResponse: response,
- type: 'cors'
+ return makeFilteredResponse(response, {
+ type: 'cors',
+ headersList: makeFilteredHeadersList(response.headersList, (name) => !corsSafeListedResponseHeaderNames.includes(name))
})
} else if (type === 'opaque') {
// An opaque filtered response is a filtered response whose type is
// "opaque", URL list is the empty list, status is 0, status message
// is the empty byte sequence, header list is empty, and body is null.
- return makeResponse({
- ...response,
- internalResponse: response,
+ return makeFilteredResponse(response, {
type: 'opaque',
- urlList: [],
+ urlList: Object.freeze([]),
status: 0,
statusText: '',
body: null
@@ -419,13 +452,11 @@ function filterResponse (response, type) {
// is "opaqueredirect", status is 0, status message is the empty byte
// sequence, header list is empty, and body is null.
- return makeResponse({
- ...response,
- internalResponse: response,
+ return makeFilteredResponse(response, {
type: 'opaqueredirect',
status: 0,
statusText: '',
- headersList: new HeadersList(),
+ headersList: makeFilteredHeadersList(response.headersList, () => false),
body: null
})
} else {
@@ -433,4 +464,22 @@ function filterResponse (response, type) {
}
}
-module.exports = { makeNetworkError, makeResponse, filterResponse, Response }
+// https://fetch.spec.whatwg.org/#appropriate-network-error
+function makeAppropriateNetworkError (fetchParams) {
+ // 1. Assert: fetchParams is canceled.
+ assert(isCancelled(fetchParams))
+
+ // 2. Return an aborted network error if fetchParams is aborted;
+ // otherwise return a network error.
+ return isAborted(fetchParams)
+ ? makeNetworkError(new AbortError())
+ : makeNetworkError(fetchParams.controller.terminated.reason)
+}
+
+module.exports = {
+ makeNetworkError,
+ makeResponse,
+ makeAppropriateNetworkError,
+ filterResponse,
+ Response
+}
diff --git a/deps/undici/src/lib/fetch/util.js b/deps/undici/src/lib/fetch/util.js
index 4e6e79838f3..eea3905586d 100644
--- a/deps/undici/src/lib/fetch/util.js
+++ b/deps/undici/src/lib/fetch/util.js
@@ -195,7 +195,7 @@ function appendFetchMetadata (httpRequest) {
header = httpRequest.mode
// 4. Set a structured field value `Sec-Fetch-Mode`/header in rā€™s header list.
- httpRequest.headersList.append('sec-fetch-mode', header)
+ httpRequest.headersList.set('sec-fetch-mode', header)
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header
// TODO
@@ -333,14 +333,25 @@ function createDeferredPromise () {
return { promise, resolve: res, reject: rej }
}
-class ServiceWorkerGlobalScope {} // dummy
-class Window {} // dummy
-class EnvironmentSettingsObject {} // dummy
+function isAborted (fetchParams) {
+ return fetchParams.controller.state === 'aborted'
+}
+
+function isCancelled (fetchParams) {
+ return fetchParams.controller.state === 'aborted' ||
+ fetchParams.controller.state === 'terminated'
+}
+
+// https://fetch.spec.whatwg.org/#concept-method-normalize
+function normalizeMethod (method) {
+ return /^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$/i.test(method)
+ ? method.toUpperCase()
+ : method
+}
module.exports = {
- ServiceWorkerGlobalScope,
- Window,
- EnvironmentSettingsObject,
+ isAborted,
+ isCancelled,
createDeferredPromise,
ReadableStreamFrom,
toUSVString,
@@ -366,5 +377,6 @@ module.exports = {
isFileLike,
isValidReasonPhrase,
sameOrigin,
- CORBCheck
+ CORBCheck,
+ normalizeMethod
}
diff --git a/deps/undici/src/lib/mock/mock-interceptor.js b/deps/undici/src/lib/mock/mock-interceptor.js
index a10c71debb5..699bec41287 100644
--- a/deps/undici/src/lib/mock/mock-interceptor.js
+++ b/deps/undici/src/lib/mock/mock-interceptor.js
@@ -64,7 +64,7 @@ class MockInterceptor {
throw new InvalidArgumentError('opts.path must be defined')
}
if (typeof opts.method === 'undefined') {
- throw new InvalidArgumentError('opts.method must be defined')
+ opts.method = 'GET'
}
// See https://github.com/nodejs/undici/issues/1245
// As per RFC 3986, clients are not supposed to send URI
diff --git a/deps/undici/src/lib/mock/mock-utils.js b/deps/undici/src/lib/mock/mock-utils.js
index fc47bcd23a5..8bd4df51a09 100644
--- a/deps/undici/src/lib/mock/mock-utils.js
+++ b/deps/undici/src/lib/mock/mock-utils.js
@@ -33,6 +33,14 @@ function lowerCaseEntries (headers) {
function matchHeaders (mockDispatch, headers) {
if (typeof mockDispatch.headers === 'function') {
+ if (Array.isArray(headers)) { // fetch HeadersList
+ const clone = headers.slice()
+ const entries = []
+ for (let index = 0; index < clone.length; index += 2) {
+ entries.push([clone[index], clone[index + 1]])
+ }
+ headers = Object.fromEntries(entries)
+ }
return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {})
}
if (typeof mockDispatch.headers === 'undefined') {
diff --git a/deps/undici/src/lib/pool-base.js b/deps/undici/src/lib/pool-base.js
index 274280f835d..2a909eee083 100644
--- a/deps/undici/src/lib/pool-base.js
+++ b/deps/undici/src/lib/pool-base.js
@@ -1,20 +1,13 @@
'use strict'
-const Dispatcher = require('./dispatcher')
-const {
- ClientDestroyedError,
- ClientClosedError,
- InvalidArgumentError
-} = require('./core/errors')
+const DispatcherBase = require('./dispatcher-base')
const FixedQueue = require('./node/fixed-queue')
-const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl } = require('./core/symbols')
+const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = require('./core/symbols')
const PoolStats = require('./pool-stats')
const kClients = Symbol('clients')
const kNeedDrain = Symbol('needDrain')
const kQueue = Symbol('queue')
-const kDestroyed = Symbol('destroyed')
-const kClosedPromise = Symbol('closed promise')
const kClosedResolve = Symbol('closed resolve')
const kOnDrain = Symbol('onDrain')
const kOnConnect = Symbol('onConnect')
@@ -25,16 +18,12 @@ const kAddClient = Symbol('add client')
const kRemoveClient = Symbol('remove client')
const kStats = Symbol('stats')
-class PoolBase extends Dispatcher {
+class PoolBase extends DispatcherBase {
constructor () {
super()
this[kQueue] = new FixedQueue()
- this[kClosedPromise] = null
- this[kClosedResolve] = null
- this[kDestroyed] = false
this[kClients] = []
- this[kNeedDrain] = false
this[kQueued] = 0
const pool = this
@@ -122,59 +111,17 @@ class PoolBase extends Dispatcher {
return this[kStats]
}
- get destroyed () {
- return this[kDestroyed]
- }
-
- get closed () {
- return this[kClosedPromise] != null
- }
-
- close (cb) {
- try {
- if (this[kDestroyed]) {
- throw new ClientDestroyedError()
- }
-
- if (!this[kClosedPromise]) {
- if (this[kQueue].isEmpty()) {
- this[kClosedPromise] = Promise.all(this[kClients].map(c => c.close()))
- } else {
- this[kClosedPromise] = new Promise((resolve) => {
- this[kClosedResolve] = resolve
- })
- }
- this[kClosedPromise] = this[kClosedPromise].then(() => {
- this[kDestroyed] = true
- })
- }
-
- if (cb) {
- this[kClosedPromise].then(() => cb(null, null))
- } else {
- return this[kClosedPromise]
- }
- } catch (err) {
- if (cb) {
- cb(err)
- } else {
- return Promise.reject(err)
- }
+ async [kClose] () {
+ if (this[kQueue].isEmpty()) {
+ return Promise.all(this[kClients].map(c => c.close()))
+ } else {
+ return new Promise((resolve) => {
+ this[kClosedResolve] = resolve
+ })
}
}
- destroy (err, cb) {
- this[kDestroyed] = true
-
- if (typeof err === 'function') {
- cb = err
- err = null
- }
-
- if (!err) {
- err = new ClientDestroyedError()
- }
-
+ async [kDestroy] (err) {
while (true) {
const item = this[kQueue].shift()
if (!item) {
@@ -183,44 +130,19 @@ class PoolBase extends Dispatcher {
item.handler.onError(err)
}
- const promise = Promise.all(this[kClients].map(c => c.destroy(err)))
- if (cb) {
- promise.then(() => cb(null, null))
- } else {
- return promise
- }
+ return Promise.all(this[kClients].map(c => c.destroy(err)))
}
- dispatch (opts, handler) {
- if (!handler || typeof handler !== 'object') {
- throw new InvalidArgumentError('handler must be an object')
- }
-
- try {
- if (this[kDestroyed]) {
- throw new ClientDestroyedError()
- }
-
- if (this[kClosedPromise]) {
- throw new ClientClosedError()
- }
-
- const dispatcher = this[kGetDispatcher]()
-
- if (!dispatcher) {
- this[kNeedDrain] = true
- this[kQueue].push({ opts, handler })
- this[kQueued]++
- } else if (!dispatcher.dispatch(opts, handler)) {
- dispatcher[kNeedDrain] = true
- this[kNeedDrain] = !this[kGetDispatcher]()
- }
- } catch (err) {
- if (typeof handler.onError !== 'function') {
- throw new InvalidArgumentError('invalid onError method')
- }
+ [kDispatch] (opts, handler) {
+ const dispatcher = this[kGetDispatcher]()
- handler.onError(err)
+ if (!dispatcher) {
+ this[kNeedDrain] = true
+ this[kQueue].push({ opts, handler })
+ this[kQueued]++
+ } else if (!dispatcher.dispatch(opts, handler)) {
+ dispatcher[kNeedDrain] = true
+ this[kNeedDrain] = !this[kGetDispatcher]()
}
return !this[kNeedDrain]
diff --git a/deps/undici/src/lib/proxy-agent.js b/deps/undici/src/lib/proxy-agent.js
index b0dec86ac00..ee674df646f 100644
--- a/deps/undici/src/lib/proxy-agent.js
+++ b/deps/undici/src/lib/proxy-agent.js
@@ -1,14 +1,14 @@
'use strict'
-const { kProxy } = require('./core/symbols')
+const { kProxy, kClose, kDestroy } = require('./core/symbols')
const { URL } = require('url')
const Agent = require('./agent')
-const Dispatcher = require('./dispatcher')
+const DispatcherBase = require('./dispatcher-base')
const { InvalidArgumentError } = require('./core/errors')
const kAgent = Symbol('proxy agent')
-class ProxyAgent extends Dispatcher {
+class ProxyAgent extends DispatcherBase {
constructor (opts) {
super(opts)
this[kProxy] = buildProxyOptions(opts)
@@ -31,9 +31,13 @@ class ProxyAgent extends Dispatcher {
)
}
- async close () {
+ async [kClose] () {
await this[kAgent].close()
}
+
+ async [kDestroy] () {
+ await this[kAgent].destroy()
+ }
}
function buildProxyOptions (opts) {
diff --git a/deps/undici/src/package.json b/deps/undici/src/package.json
index f0fd80ce7ca..aee29e46387 100644
--- a/deps/undici/src/package.json
+++ b/deps/undici/src/package.json
@@ -1,6 +1,6 @@
{
"name": "undici",
- "version": "4.16.0",
+ "version": "5.0.0",
"description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org",
"bugs": {
@@ -54,10 +54,10 @@
"test:typescript": "tsd",
"coverage": "nyc --reporter=text --reporter=html npm run test",
"coverage:ci": "nyc --reporter=lcov npm run test",
- "bench": "concurrently -k -s first npm:bench:server npm:bench:run",
+ "bench": "PORT=3042 concurrently -k -s first npm:bench:server npm:bench:run",
"bench:server": "node benchmarks/server.js",
"prebench:run": "node benchmarks/wait.js",
- "bench:run": "CONNECTIONS=1 node --experimental-wasm-simd benchmarks/benchmark.js && CONNECTIONS=50 node --experimental-wasm-simd benchmarks/benchmark.js",
+ "bench:run": "CONNECTIONS=1 node --experimental-wasm-simd benchmarks/benchmark.js; CONNECTIONS=50 node --experimental-wasm-simd benchmarks/benchmark.js",
"serve:website": "docsify serve .",
"prepare": "husky install",
"fuzz": "jsfuzz test/fuzzing/fuzz.js corpus"
diff --git a/deps/undici/src/types/mock-interceptor.d.ts b/deps/undici/src/types/mock-interceptor.d.ts
index 0166b1f1db3..2e4272176ad 100644
--- a/deps/undici/src/types/mock-interceptor.d.ts
+++ b/deps/undici/src/types/mock-interceptor.d.ts
@@ -44,8 +44,8 @@ declare namespace MockInterceptor {
export interface Options {
/** Path to intercept on. */
path: string | RegExp | ((path: string) => boolean);
- /** Method to intercept on. */
- method: string | RegExp | ((method: string) => boolean);
+ /** Method to intercept on. Defaults to GET. */
+ method?: string | RegExp | ((method: string) => boolean);
/** Body to intercept on. */
body?: string | RegExp | ((body: string) => boolean);
/** Headers to intercept on. */
diff --git a/deps/undici/undici.js b/deps/undici/undici.js
index 4c52acb9d76..f955b9dc320 100644
--- a/deps/undici/undici.js
+++ b/deps/undici/undici.js
@@ -8,6 +8,9 @@ var __commonJS = (cb, mod) => function __require() {
var require_symbols = __commonJS({
"lib/core/symbols.js"(exports2, module2) {
module2.exports = {
+ kClose: Symbol("close"),
+ kDestroy: Symbol("destroy"),
+ kDispatch: Symbol("dispatch"),
kUrl: Symbol("url"),
kWriting: Symbol("writing"),
kResuming: Symbol("resuming"),
@@ -753,6 +756,138 @@ var require_dispatcher = __commonJS({
}
});
+// lib/dispatcher-base.js
+var require_dispatcher_base = __commonJS({
+ "lib/dispatcher-base.js"(exports2, module2) {
+ "use strict";
+ var Dispatcher2 = require_dispatcher();
+ var {
+ ClientDestroyedError,
+ ClientClosedError,
+ InvalidArgumentError: InvalidArgumentError2
+ } = require_errors();
+ var { kDestroy, kClose, kDispatch } = require_symbols();
+ var kDestroyed = Symbol("destroyed");
+ var kClosed = Symbol("closed");
+ var kOnDestroyed = Symbol("onDestroyed");
+ var kOnClosed = Symbol("onClosed");
+ var DispatcherBase = class extends Dispatcher2 {
+ constructor() {
+ super();
+ this[kDestroyed] = false;
+ this[kOnDestroyed] = [];
+ this[kClosed] = false;
+ this[kOnClosed] = [];
+ }
+ get destroyed() {
+ return this[kDestroyed];
+ }
+ get closed() {
+ return this[kClosed];
+ }
+ close(callback) {
+ if (callback === void 0) {
+ return new Promise((resolve, reject) => {
+ this.close((err, data) => {
+ return err ? reject(err) : resolve(data);
+ });
+ });
+ }
+ if (typeof callback !== "function") {
+ throw new InvalidArgumentError2("invalid callback");
+ }
+ if (this[kDestroyed]) {
+ queueMicrotask(() => callback(new ClientDestroyedError(), null));
+ return;
+ }
+ if (this[kClosed]) {
+ if (this[kOnClosed]) {
+ this[kOnClosed].push(callback);
+ } else {
+ queueMicrotask(() => callback(null, null));
+ }
+ return;
+ }
+ this[kClosed] = true;
+ this[kOnClosed].push(callback);
+ const onClosed = () => {
+ const callbacks = this[kOnClosed];
+ this[kOnClosed] = null;
+ for (let i = 0; i < callbacks.length; i++) {
+ callbacks[i](null, null);
+ }
+ };
+ this[kClose]().then(() => this.destroy()).then(() => {
+ queueMicrotask(onClosed);
+ });
+ }
+ destroy(err, callback) {
+ if (typeof err === "function") {
+ callback = err;
+ err = null;
+ }
+ if (callback === void 0) {
+ return new Promise((resolve, reject) => {
+ this.destroy(err, (err2, data) => {
+ return err2 ? reject(err2) : resolve(data);
+ });
+ });
+ }
+ if (typeof callback !== "function") {
+ throw new InvalidArgumentError2("invalid callback");
+ }
+ if (this[kDestroyed]) {
+ if (this[kOnDestroyed]) {
+ this[kOnDestroyed].push(callback);
+ } else {
+ queueMicrotask(() => callback(null, null));
+ }
+ return;
+ }
+ if (!err) {
+ err = new ClientDestroyedError();
+ }
+ this[kDestroyed] = true;
+ this[kOnDestroyed].push(callback);
+ const onDestroyed = () => {
+ const callbacks = this[kOnDestroyed];
+ this[kOnDestroyed] = null;
+ for (let i = 0; i < callbacks.length; i++) {
+ callbacks[i](null, null);
+ }
+ };
+ this[kDestroy](err).then(() => {
+ queueMicrotask(onDestroyed);
+ });
+ }
+ dispatch(opts, handler) {
+ if (!handler || typeof handler !== "object") {
+ throw new InvalidArgumentError2("handler must be an object");
+ }
+ try {
+ if (!opts || typeof opts !== "object") {
+ throw new InvalidArgumentError2("opts must be an object.");
+ }
+ if (this[kDestroyed]) {
+ throw new ClientDestroyedError();
+ }
+ if (this[kClosed]) {
+ throw new ClientClosedError();
+ }
+ return this[kDispatch](opts, handler);
+ } catch (err) {
+ if (typeof handler.onError !== "function") {
+ throw new InvalidArgumentError2("invalid onError method");
+ }
+ handler.onError(err);
+ return false;
+ }
+ }
+ };
+ module2.exports = DispatcherBase;
+ }
+});
+
// lib/handler/redirect.js
var require_redirect = __commonJS({
"lib/handler/redirect.js"(exports2, module2) {
@@ -1336,7 +1471,7 @@ var require_client = __commonJS({
var net = require("net");
var util2 = require_util();
var Request = require_request();
- var Dispatcher2 = require_dispatcher();
+ var DispatcherBase = require_dispatcher_base();
var RedirectHandler = require_redirect();
var {
RequestContentLengthMismatchError,
@@ -1346,8 +1481,6 @@ var require_client = __commonJS({
RequestAbortedError,
HeadersTimeoutError,
HeadersOverflowError,
- ClientDestroyedError,
- ClientClosedError,
SocketError,
InformationalError,
BodyTimeoutError,
@@ -1375,12 +1508,9 @@ var require_client = __commonJS({
kNoRef,
kKeepAliveDefaultTimeout,
kHostHeader,
- kClosed,
- kDestroyed,
kPendingIdx,
kRunningIdx,
kError,
- kOnDestroyed,
kPipelining,
kSocket,
kKeepAliveTimeoutValue,
@@ -1393,8 +1523,12 @@ var require_client = __commonJS({
kConnector,
kMaxRedirections,
kMaxRequests,
- kCounter
+ kCounter,
+ kClose,
+ kDestroy,
+ kDispatch
} = require_symbols();
+ var kClosedResolve = Symbol("kClosedResolve");
var channels = {};
try {
const diagnosticsChannel = require("diagnostics_channel");
@@ -1408,7 +1542,7 @@ var require_client = __commonJS({
channels.connectError = { hasSubscribers: false };
channels.connected = { hasSubscribers: false };
}
- var Client2 = class extends Dispatcher2 {
+ var Client2 = class extends DispatcherBase {
constructor(url, {
maxHeaderSize,
headersTimeout,
@@ -1498,10 +1632,7 @@ var require_client = __commonJS({
this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 6e5 : keepAliveMaxTimeout;
this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 1e3 : keepAliveTimeoutThreshold;
this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout];
- this[kClosed] = false;
- this[kDestroyed] = false;
this[kServerName] = null;
- this[kOnDestroyed] = [];
this[kResuming] = 0;
this[kNeedDrain] = 0;
this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ""}\r
@@ -1511,6 +1642,7 @@ var require_client = __commonJS({
this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength;
this[kMaxRedirections] = maxRedirections;
this[kMaxRequests] = maxRequestsPerClient;
+ this[kClosedResolve] = null;
this[kQueue] = [];
this[kRunningIdx] = 0;
this[kPendingIdx] = 0;
@@ -1522,12 +1654,6 @@ var require_client = __commonJS({
this[kPipelining] = value;
resume(this, true);
}
- get destroyed() {
- return this[kDestroyed];
- }
- get closed() {
- return this[kClosed];
- }
get [kPending]() {
return this[kQueue].length - this[kPendingIdx];
}
@@ -1548,114 +1674,56 @@ var require_client = __commonJS({
connect(this);
this.once("connect", cb);
}
- dispatch(opts, handler) {
- if (!handler || typeof handler !== "object") {
- throw new InvalidArgumentError2("handler must be an object");
+ [kDispatch](opts, handler) {
+ const { maxRedirections = this[kMaxRedirections] } = opts;
+ if (maxRedirections) {
+ handler = new RedirectHandler(this, maxRedirections, opts, handler);
+ }
+ const origin = opts.origin || this[kUrl].origin;
+ const request = new Request(origin, opts, handler);
+ this[kQueue].push(request);
+ if (this[kResuming]) {
+ } else if (util2.bodyLength(request.body) == null && util2.isIterable(request.body)) {
+ this[kResuming] = 1;
+ process.nextTick(resume, this);
+ } else {
+ resume(this, true);
}
- try {
- if (!opts || typeof opts !== "object") {
- throw new InvalidArgumentError2("opts must be an object.");
- }
- if (this[kDestroyed]) {
- throw new ClientDestroyedError();
- }
- if (this[kClosed]) {
- throw new ClientClosedError();
- }
- const { maxRedirections = this[kMaxRedirections] } = opts;
- if (maxRedirections) {
- handler = new RedirectHandler(this, maxRedirections, opts, handler);
- }
- const origin = opts.origin || this[kUrl].origin;
- const request = new Request(origin, opts, handler);
- this[kQueue].push(request);
- if (this[kResuming]) {
- } else if (util2.bodyLength(request.body) == null && util2.isIterable(request.body)) {
- this[kResuming] = 1;
- process.nextTick(resume, this);
- } else {
- resume(this, true);
- }
- if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) {
- this[kNeedDrain] = 2;
- }
- } catch (err) {
- if (typeof handler.onError !== "function") {
- throw new InvalidArgumentError2("invalid onError method");
- }
- handler.onError(err);
+ if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) {
+ this[kNeedDrain] = 2;
}
return this[kNeedDrain] < 2;
}
- close(callback) {
- if (callback === void 0) {
- return new Promise((resolve, reject) => {
- this.close((err, data) => {
- return err ? reject(err) : resolve(data);
- });
- });
- }
- if (typeof callback !== "function") {
- throw new InvalidArgumentError2("invalid callback");
- }
- if (this[kDestroyed]) {
- queueMicrotask(() => callback(new ClientDestroyedError(), null));
- return;
- }
- this[kClosed] = true;
- if (!this[kSize]) {
- this.destroy(callback);
- } else {
- this[kOnDestroyed].push(callback);
- }
- }
- destroy(err, callback) {
- if (typeof err === "function") {
- callback = err;
- err = null;
- }
- if (callback === void 0) {
- return new Promise((resolve, reject) => {
- this.destroy(err, (err2, data) => {
- return err2 ? reject(err2) : resolve(data);
- });
- });
- }
- if (typeof callback !== "function") {
- throw new InvalidArgumentError2("invalid callback");
- }
- if (this[kDestroyed]) {
- if (this[kOnDestroyed]) {
- this[kOnDestroyed].push(callback);
+ async [kClose]() {
+ return new Promise((resolve) => {
+ if (!this[kSize]) {
+ this.destroy(resolve);
} else {
- queueMicrotask(() => callback(null, null));
+ this[kClosedResolve] = resolve;
}
- return;
- }
- if (!err) {
- err = new ClientDestroyedError();
- }
- const requests = this[kQueue].splice(this[kPendingIdx]);
- for (let i = 0; i < requests.length; i++) {
- const request = requests[i];
- errorRequest(this, request, err);
- }
- this[kClosed] = true;
- this[kDestroyed] = true;
- this[kOnDestroyed].push(callback);
- const onDestroyed = () => {
- const callbacks = this[kOnDestroyed];
- this[kOnDestroyed] = null;
- for (let i = 0; i < callbacks.length; i++) {
- callbacks[i](null, null);
+ });
+ }
+ async [kDestroy](err) {
+ return new Promise((resolve) => {
+ const requests = this[kQueue].splice(this[kPendingIdx]);
+ for (let i = 0; i < requests.length; i++) {
+ const request = requests[i];
+ errorRequest(this, request, err);
+ }
+ const callback = () => {
+ if (this[kClosedResolve]) {
+ this[kClosedResolve]();
+ this[kClosedResolve] = null;
+ }
+ resolve();
+ };
+ if (!this[kSocket]) {
+ queueMicrotask(callback);
+ } else {
+ util2.destroy(this[kSocket].on("close", callback), err);
}
- };
- if (!this[kSocket]) {
- queueMicrotask(onDestroyed);
- } else {
- util2.destroy(this[kSocket].on("close", onDestroyed), err);
- }
- resume(this);
+ resume(this);
+ });
}
};
var constants = require_constants();
@@ -1839,7 +1907,6 @@ var require_client = __commonJS({
try {
try {
currentParser = this;
- this.llhttp.llhttp_finish(this.ptr);
} finally {
currentParser = null;
}
@@ -2156,7 +2223,7 @@ var require_client = __commonJS({
this[kParser] = null;
const err = this[kError] || new SocketError("closed", util2.getSocketInfo(this));
client[kSocket] = null;
- if (client[kDestroyed]) {
+ if (client.destroyed) {
assert(client[kPending] === 0);
const requests = client[kQueue].splice(client[kRunningIdx]);
for (let i = 0; i < requests.length; i++) {
@@ -2291,13 +2358,13 @@ var require_client = __commonJS({
}
function _resume(client, sync) {
while (true) {
- if (client[kDestroyed]) {
+ if (client.destroyed) {
assert(client[kPending] === 0);
return;
}
- if (client[kClosed] && !client[kSize]) {
- client.destroy(util2.nop);
- continue;
+ if (client.closed && !client[kSize]) {
+ client.destroy();
+ return;
}
const socket = client[kSocket];
if (socket) {
@@ -2807,20 +2874,13 @@ var require_pool_stats = __commonJS({
var require_pool_base = __commonJS({
"lib/pool-base.js"(exports2, module2) {
"use strict";
- var Dispatcher2 = require_dispatcher();
- var {
- ClientDestroyedError,
- ClientClosedError,
- InvalidArgumentError: InvalidArgumentError2
- } = require_errors();
+ var DispatcherBase = require_dispatcher_base();
var FixedQueue = require_fixed_queue();
- var { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl } = require_symbols();
+ var { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = require_symbols();
var PoolStats = require_pool_stats();
var kClients = Symbol("clients");
var kNeedDrain = Symbol("needDrain");
var kQueue = Symbol("queue");
- var kDestroyed = Symbol("destroyed");
- var kClosedPromise = Symbol("closed promise");
var kClosedResolve = Symbol("closed resolve");
var kOnDrain = Symbol("onDrain");
var kOnConnect = Symbol("onConnect");
@@ -2830,15 +2890,11 @@ var require_pool_base = __commonJS({
var kAddClient = Symbol("add client");
var kRemoveClient = Symbol("remove client");
var kStats = Symbol("stats");
- var PoolBase = class extends Dispatcher2 {
+ var PoolBase = class extends DispatcherBase {
constructor() {
super();
this[kQueue] = new FixedQueue();
- this[kClosedPromise] = null;
- this[kClosedResolve] = null;
- this[kDestroyed] = false;
this[kClients] = [];
- this[kNeedDrain] = false;
this[kQueued] = 0;
const pool = this;
this[kOnDrain] = function onDrain(origin, targets) {
@@ -2905,51 +2961,16 @@ var require_pool_base = __commonJS({
get stats() {
return this[kStats];
}
- get destroyed() {
- return this[kDestroyed];
- }
- get closed() {
- return this[kClosedPromise] != null;
- }
- close(cb) {
- try {
- if (this[kDestroyed]) {
- throw new ClientDestroyedError();
- }
- if (!this[kClosedPromise]) {
- if (this[kQueue].isEmpty()) {
- this[kClosedPromise] = Promise.all(this[kClients].map((c) => c.close()));
- } else {
- this[kClosedPromise] = new Promise((resolve) => {
- this[kClosedResolve] = resolve;
- });
- }
- this[kClosedPromise] = this[kClosedPromise].then(() => {
- this[kDestroyed] = true;
- });
- }
- if (cb) {
- this[kClosedPromise].then(() => cb(null, null));
- } else {
- return this[kClosedPromise];
- }
- } catch (err) {
- if (cb) {
- cb(err);
- } else {
- return Promise.reject(err);
- }
+ async [kClose]() {
+ if (this[kQueue].isEmpty()) {
+ return Promise.all(this[kClients].map((c) => c.close()));
+ } else {
+ return new Promise((resolve) => {
+ this[kClosedResolve] = resolve;
+ });
}
}
- destroy(err, cb) {
- this[kDestroyed] = true;
- if (typeof err === "function") {
- cb = err;
- err = null;
- }
- if (!err) {
- err = new ClientDestroyedError();
- }
+ async [kDestroy](err) {
while (true) {
const item = this[kQueue].shift();
if (!item) {
@@ -2957,38 +2978,17 @@ var require_pool_base = __commonJS({
}
item.handler.onError(err);
}
- const promise = Promise.all(this[kClients].map((c) => c.destroy(err)));
- if (cb) {
- promise.then(() => cb(null, null));
- } else {
- return promise;
- }
+ return Promise.all(this[kClients].map((c) => c.destroy(err)));
}
- dispatch(opts, handler) {
- if (!handler || typeof handler !== "object") {
- throw new InvalidArgumentError2("handler must be an object");
- }
- try {
- if (this[kDestroyed]) {
- throw new ClientDestroyedError();
- }
- if (this[kClosedPromise]) {
- throw new ClientClosedError();
- }
- const dispatcher = this[kGetDispatcher]();
- if (!dispatcher) {
- this[kNeedDrain] = true;
- this[kQueue].push({ opts, handler });
- this[kQueued]++;
- } else if (!dispatcher.dispatch(opts, handler)) {
- dispatcher[kNeedDrain] = true;
- this[kNeedDrain] = !this[kGetDispatcher]();
- }
- } catch (err) {
- if (typeof handler.onError !== "function") {
- throw new InvalidArgumentError2("invalid onError method");
- }
- handler.onError(err);
+ [kDispatch](opts, handler) {
+ const dispatcher = this[kGetDispatcher]();
+ if (!dispatcher) {
+ this[kNeedDrain] = true;
+ this[kQueue].push({ opts, handler });
+ this[kQueued]++;
+ } else if (!dispatcher.dispatch(opts, handler)) {
+ dispatcher[kNeedDrain] = true;
+ this[kNeedDrain] = !this[kGetDispatcher]();
}
return !this[kNeedDrain];
}
@@ -3213,20 +3213,14 @@ var require_dispatcher_weakref = __commonJS({
var require_agent = __commonJS({
"lib/agent.js"(exports2, module2) {
"use strict";
- var {
- ClientClosedError,
- InvalidArgumentError: InvalidArgumentError2,
- ClientDestroyedError
- } = require_errors();
- var { kClients, kRunning } = require_symbols();
- var Dispatcher2 = require_dispatcher();
+ var { InvalidArgumentError: InvalidArgumentError2 } = require_errors();
+ var { kClients, kRunning, kClose, kDestroy, kDispatch } = require_symbols();
+ var DispatcherBase = require_dispatcher_base();
var Pool2 = require_pool();
var Client2 = require_client();
var util2 = require_util();
var RedirectHandler = require_redirect();
var { WeakRef, FinalizationRegistry: FinalizationRegistry2 } = require_dispatcher_weakref()();
- var kDestroyed = Symbol("destroyed");
- var kClosed = Symbol("closed");
var kOnConnect = Symbol("onConnect");
var kOnDisconnect = Symbol("onDisconnect");
var kOnConnectionError = Symbol("onConnectionError");
@@ -3238,7 +3232,7 @@ var require_agent = __commonJS({
function defaultFactory(origin, opts) {
return opts && opts.connections === 1 ? new Client2(origin, opts) : new Pool2(origin, opts);
}
- var Agent2 = class extends Dispatcher2 {
+ var Agent2 = class extends DispatcherBase {
constructor({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
super();
if (typeof factory !== "function") {
@@ -3263,8 +3257,6 @@ var require_agent = __commonJS({
this[kClients].delete(key);
}
});
- this[kClosed] = false;
- this[kDestroyed] = false;
const agent = this;
this[kOnDrain] = (origin, targets) => {
agent.emit("drain", origin, [agent, ...targets]);
@@ -3289,57 +3281,28 @@ var require_agent = __commonJS({
}
return ret;
}
- dispatch(opts, handler) {
- if (!handler || typeof handler !== "object") {
- throw new InvalidArgumentError2("handler must be an object.");
+ [kDispatch](opts, handler) {
+ let key;
+ if (opts.origin && (typeof opts.origin === "string" || opts.origin instanceof URL)) {
+ key = String(opts.origin);
+ } else {
+ throw new InvalidArgumentError2("opts.origin must be a non-empty string or URL.");
}
- try {
- if (!opts || typeof opts !== "object") {
- throw new InvalidArgumentError2("opts must be an object.");
- }
- let key;
- if (opts.origin && (typeof opts.origin === "string" || opts.origin instanceof URL)) {
- key = String(opts.origin);
- } else {
- throw new InvalidArgumentError2("opts.origin must be a non-empty string or URL.");
- }
- if (this[kDestroyed]) {
- throw new ClientDestroyedError();
- }
- if (this[kClosed]) {
- throw new ClientClosedError();
- }
- const ref = this[kClients].get(key);
- let dispatcher = ref ? ref.deref() : null;
- if (!dispatcher) {
- dispatcher = this[kFactory](opts.origin, this[kOptions]).on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]);
- this[kClients].set(key, new WeakRef(dispatcher));
- this[kFinalizer].register(dispatcher, key);
- }
- const { maxRedirections = this[kMaxRedirections] } = opts;
- if (maxRedirections != null && maxRedirections !== 0) {
- opts = { ...opts, maxRedirections: 0 };
- handler = new RedirectHandler(this, maxRedirections, opts, handler);
- }
- return dispatcher.dispatch(opts, handler);
- } catch (err) {
- if (typeof handler.onError !== "function") {
- throw new InvalidArgumentError2("invalid onError method");
- }
- handler.onError(err);
+ const ref = this[kClients].get(key);
+ let dispatcher = ref ? ref.deref() : null;
+ if (!dispatcher) {
+ dispatcher = this[kFactory](opts.origin, this[kOptions]).on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]);
+ this[kClients].set(key, new WeakRef(dispatcher));
+ this[kFinalizer].register(dispatcher, key);
}
- }
- get closed() {
- return this[kClosed];
- }
- get destroyed() {
- return this[kDestroyed];
- }
- close(callback) {
- if (callback != null && typeof callback !== "function") {
- throw new InvalidArgumentError2("callback must be a function");
+ const { maxRedirections = this[kMaxRedirections] } = opts;
+ if (maxRedirections != null && maxRedirections !== 0) {
+ opts = { ...opts, maxRedirections: 0 };
+ handler = new RedirectHandler(this, maxRedirections, opts, handler);
}
- this[kClosed] = true;
+ return dispatcher.dispatch(opts, handler);
+ }
+ async [kClose]() {
const closePromises = [];
for (const ref of this[kClients].values()) {
const client = ref.deref();
@@ -3347,21 +3310,9 @@ var require_agent = __commonJS({
closePromises.push(client.close());
}
}
- if (!callback) {
- return Promise.all(closePromises);
- }
- Promise.all(closePromises).then(() => process.nextTick(callback));
+ await Promise.all(closePromises);
}
- destroy(err, callback) {
- if (typeof err === "function") {
- callback = err;
- err = null;
- }
- if (callback != null && typeof callback !== "function") {
- throw new InvalidArgumentError2("callback must be a function");
- }
- this[kClosed] = true;
- this[kDestroyed] = true;
+ async [kDestroy](err) {
const destroyPromises = [];
for (const ref of this[kClients].values()) {
const client = ref.deref();
@@ -3369,10 +3320,7 @@ var require_agent = __commonJS({
destroyPromises.push(client.destroy(err));
}
}
- if (!callback) {
- return Promise.all(destroyPromises);
- }
- Promise.all(destroyPromises).then(() => process.nextTick(callback));
+ await Promise.all(destroyPromises);
}
};
module2.exports = Agent2;
@@ -4400,6 +4348,14 @@ var require_mock_utils = __commonJS({
}
function matchHeaders(mockDispatch2, headers) {
if (typeof mockDispatch2.headers === "function") {
+ if (Array.isArray(headers)) {
+ const clone = headers.slice();
+ const entries = [];
+ for (let index = 0; index < clone.length; index += 2) {
+ entries.push([clone[index], clone[index + 1]]);
+ }
+ headers = Object.fromEntries(entries);
+ }
return mockDispatch2.headers(headers ? lowerCaseEntries(headers) : {});
}
if (typeof mockDispatch2.headers === "undefined") {
@@ -4633,7 +4589,7 @@ var require_mock_interceptor = __commonJS({
throw new InvalidArgumentError2("opts.path must be defined");
}
if (typeof opts.method === "undefined") {
- throw new InvalidArgumentError2("opts.method must be defined");
+ opts.method = "GET";
}
if (typeof opts.path === "string") {
const parsedURL = new URL(opts.path, "data://");
@@ -4936,13 +4892,13 @@ var require_mock_agent = __commonJS({
var require_proxy_agent = __commonJS({
"lib/proxy-agent.js"(exports2, module2) {
"use strict";
- var { kProxy } = require_symbols();
+ var { kProxy, kClose, kDestroy } = require_symbols();
var { URL: URL2 } = require("url");
var Agent2 = require_agent();
- var Dispatcher2 = require_dispatcher();
+ var DispatcherBase = require_dispatcher_base();
var { InvalidArgumentError: InvalidArgumentError2 } = require_errors();
var kAgent = Symbol("proxy agent");
- var ProxyAgent2 = class extends Dispatcher2 {
+ var ProxyAgent2 = class extends DispatcherBase {
constructor(opts) {
super(opts);
this[kProxy] = buildProxyOptions(opts);
@@ -4960,9 +4916,12 @@ var require_proxy_agent = __commonJS({
}
}, handler);
}
- async close() {
+ async [kClose]() {
await this[kAgent].close();
}
+ async [kDestroy]() {
+ await this[kAgent].destroy();
+ }
};
function buildProxyOptions(opts) {
if (typeof opts === "string") {
@@ -5069,9 +5028,11 @@ var require_constants2 = __commonJS({
"xslt",
""
];
+ var corsSafeListedResponseHeaderNames = [];
module2.exports = {
subresource,
forbiddenResponseHeaderNames,
+ corsSafeListedResponseHeaderNames,
forbiddenMethods,
requestBodyHeader,
referrerPolicy,
@@ -5320,8 +5281,9 @@ var require_headers = __commonJS({
}
const callback = args[0];
const thisArg = args[1];
- for (let index = 0; index < this[kHeadersList].length; index += 2) {
- callback.call(thisArg, this[kHeadersList][index + 1], this[kHeadersList][index], this);
+ const clone = this[kHeadersList].slice();
+ for (let index = 0; index < clone.length; index += 2) {
+ callback.call(thisArg, clone[index + 1], clone[index], this);
}
}
[Symbol.for("nodejs.util.inspect.custom")]() {
@@ -5625,7 +5587,7 @@ var require_util2 = __commonJS({
function appendFetchMetadata(httpRequest) {
let header = null;
header = httpRequest.mode;
- httpRequest.headersList.append("sec-fetch-mode", header);
+ httpRequest.headersList.set("sec-fetch-mode", header);
}
function appendRequestOriginHeader(request) {
let serializedOrigin = request.origin;
@@ -5707,16 +5669,18 @@ var require_util2 = __commonJS({
});
return { promise, resolve: res, reject: rej };
}
- var ServiceWorkerGlobalScope = class {
- };
- var Window = class {
- };
- var EnvironmentSettingsObject = class {
- };
+ function isAborted(fetchParams) {
+ return fetchParams.controller.state === "aborted";
+ }
+ function isCancelled(fetchParams) {
+ return fetchParams.controller.state === "aborted" || fetchParams.controller.state === "terminated";
+ }
+ function normalizeMethod(method) {
+ return /^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$/i.test(method) ? method.toUpperCase() : method;
+ }
module2.exports = {
- ServiceWorkerGlobalScope,
- Window,
- EnvironmentSettingsObject,
+ isAborted,
+ isCancelled,
createDeferredPromise,
ReadableStreamFrom,
toUSVString,
@@ -5742,7 +5706,8 @@ var require_util2 = __commonJS({
isFileLike,
isValidReasonPhrase,
sameOrigin,
- CORBCheck
+ CORBCheck,
+ normalizeMethod
};
}
});
@@ -6138,14 +6103,16 @@ var require_response = __commonJS({
"lib/fetch/response.js"(exports2, module2) {
"use strict";
var { Headers, HeadersList, fill } = require_headers();
+ var { AbortError } = require_errors();
var { extractBody, cloneBody, mixinBody } = require_body();
var util2 = require_util();
var { kEnumerableProperty } = util2;
- var { responseURL, isValidReasonPhrase, toUSVString } = require_util2();
+ var { responseURL, isValidReasonPhrase, toUSVString, isCancelled, isAborted } = require_util2();
var {
redirectStatus,
nullBodyStatus,
- forbiddenResponseHeaderNames
+ forbiddenResponseHeaderNames,
+ corsSafeListedResponseHeaderNames
} = require_constants2();
var { kState, kHeaders, kGuard, kRealm } = require_symbols2();
var { kHeadersList } = require_symbols();
@@ -6329,10 +6296,10 @@ var require_response = __commonJS({
}
function makeResponse(init) {
return {
- internalResponse: null,
aborted: false,
rangeRequested: false,
timingAllowPassed: false,
+ requestIncludesCredentials: false,
type: "default",
status: 200,
timingInfo: null,
@@ -6347,55 +6314,92 @@ var require_response = __commonJS({
return makeResponse({
type: "error",
status: 0,
- error: reason instanceof Error ? reason : new Error(reason ? String(reason) : reason),
+ error: reason instanceof Error ? reason : new Error(reason ? String(reason) : reason, {
+ cause: reason instanceof Error ? reason : void 0
+ }),
aborted: reason && reason.name === "AbortError"
});
}
- function filterResponse(response, type) {
- if (type === "basic") {
- const headers = [];
- for (let n = 0; n < response.headersList.length; n += 2) {
- if (!forbiddenResponseHeaderNames.includes(response.headersList[n])) {
- headers.push(response.headersList[n + 0], response.headersList[n + 1]);
+ function makeFilteredResponse(response, state) {
+ state = {
+ internalResponse: response,
+ ...state
+ };
+ return new Proxy(response, {
+ get(target, p) {
+ return p in state ? state[p] : target[p];
+ },
+ set(target, p, value) {
+ assert(!(p in state));
+ target[p] = value;
+ return true;
+ }
+ });
+ }
+ function makeFilteredHeadersList(headersList, filter) {
+ return new Proxy(headersList, {
+ get(target, prop) {
+ if (prop === "get" || prop === "has") {
+ return (name) => filter(name) ? target[prop](name) : void 0;
+ } else if (prop === "slice") {
+ return (...args) => {
+ assert(args.length === 0);
+ const arr = [];
+ for (let index = 0; index < target.length; index += 2) {
+ if (filter(target[index])) {
+ arr.push(target[index], target[index + 1]);
+ }
+ }
+ return arr;
+ };
+ } else {
+ return target[prop];
}
}
- return makeResponse({
- ...response,
- internalResponse: response,
- headersList: new HeadersList(...headers),
- type: "basic"
+ });
+ }
+ function filterResponse(response, type) {
+ if (type === "basic") {
+ return makeFilteredResponse(response, {
+ type: "basic",
+ headersList: makeFilteredHeadersList(response.headersList, (name) => !forbiddenResponseHeaderNames.includes(name))
});
} else if (type === "cors") {
- return makeResponse({
- ...response,
- internalResponse: response,
- type: "cors"
+ return makeFilteredResponse(response, {
+ type: "cors",
+ headersList: makeFilteredHeadersList(response.headersList, (name) => !corsSafeListedResponseHeaderNames.includes(name))
});
} else if (type === "opaque") {
- return makeResponse({
- ...response,
- internalResponse: response,
+ return makeFilteredResponse(response, {
type: "opaque",
- urlList: [],
+ urlList: Object.freeze([]),
status: 0,
statusText: "",
body: null
});
} else if (type === "opaqueredirect") {
- return makeResponse({
- ...response,
- internalResponse: response,
+ return makeFilteredResponse(response, {
type: "opaqueredirect",
status: 0,
statusText: "",
- headersList: new HeadersList(),
+ headersList: makeFilteredHeadersList(response.headersList, () => false),
body: null
});
} else {
assert(false);
}
}
- module2.exports = { makeNetworkError, makeResponse, filterResponse, Response };
+ function makeAppropriateNetworkError(fetchParams) {
+ assert(isCancelled(fetchParams));
+ return isAborted(fetchParams) ? makeNetworkError(new AbortError()) : makeNetworkError(fetchParams.controller.terminated.reason);
+ }
+ module2.exports = {
+ makeNetworkError,
+ makeResponse,
+ makeAppropriateNetworkError,
+ filterResponse,
+ Response
+ };
}
});
@@ -6408,9 +6412,9 @@ var require_request2 = __commonJS({
var util2 = require_util();
var {
isValidHTTPToken,
- EnvironmentSettingsObject,
sameOrigin,
- toUSVString
+ toUSVString,
+ normalizeMethod
} = require_util2();
var {
forbiddenMethods,
@@ -6453,9 +6457,7 @@ var require_request2 = __commonJS({
try {
parsedURL = new URL(input, baseUrl);
} catch (err) {
- const error = new TypeError("Failed to parse URL from " + input);
- error.cause = err;
- throw error;
+ throw new TypeError("Failed to parse URL from " + input, { cause: err });
}
if (parsedURL.username || parsedURL.password) {
throw new TypeError("Request cannot be constructed from a URL that includes credentials: " + input);
@@ -6469,7 +6471,7 @@ var require_request2 = __commonJS({
}
const origin = this[kRealm].settingsObject.origin;
let window = "client";
- if (request.window instanceof EnvironmentSettingsObject && sameOrigin(request.window, origin)) {
+ if (request.window?.constructor?.name === "EnvironmentSettingsObject" && sameOrigin(request.window, origin)) {
window = request.window;
}
if (init.window !== void 0 && init.window != null) {
@@ -6482,7 +6484,7 @@ var require_request2 = __commonJS({
method: request.method,
headersList: request.headersList,
unsafeRequest: request.unsafeRequest,
- client: request.client,
+ client: this[kRealm].settingsObject,
window,
priority: request.priority,
origin: request.origin,
@@ -6496,7 +6498,7 @@ var require_request2 = __commonJS({
keepalive: request.keepalive,
reloadNavigation: request.reloadNavigation,
historyNavigation: request.historyNavigation,
- urlList: request.urlList
+ urlList: [...request.urlList]
});
if (Object.keys(init).length > 0) {
if (request.mode === "navigate") {
@@ -6519,9 +6521,7 @@ var require_request2 = __commonJS({
try {
parsedReferrer = new URL(referrer, baseUrl);
} catch (err) {
- const error = new TypeError(`Referrer "${referrer}" is not a valid URL.`);
- error.cause = err;
- throw error;
+ throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err });
}
request.referrer = parsedReferrer;
}
@@ -6582,7 +6582,7 @@ var require_request2 = __commonJS({
if (forbiddenMethods.indexOf(method.toUpperCase()) !== -1) {
throw TypeError(`'${init.method}' HTTP method is unsupported.`);
}
- method = init.method.toUpperCase();
+ method = normalizeMethod(init.method);
request.method = method;
}
if (init.signal !== void 0) {
@@ -6767,7 +6767,7 @@ var require_request2 = __commonJS({
if (!(this instanceof Request)) {
throw new TypeError("Illegal invocation");
}
- if (this.bodyUsed || this.body && this.body.locked) {
+ if (this.bodyUsed || this.body?.locked) {
throw new TypeError("unusable");
}
const clonedRequest = cloneRequest(this[kState]);
@@ -7067,6 +7067,7 @@ var require_fetch = __commonJS({
var {
Response,
makeNetworkError,
+ makeAppropriateNetworkError,
filterResponse,
makeResponse
} = require_response();
@@ -7074,8 +7075,6 @@ var require_fetch = __commonJS({
var { Request, makeRequest } = require_request2();
var zlib = require("zlib");
var {
- ServiceWorkerGlobalScope,
- Window,
matchRequestIntegrity,
makePolicyContainer,
clonePolicyContainer,
@@ -7095,7 +7094,9 @@ var require_fetch = __commonJS({
createDeferredPromise,
isBlobLike,
CORBCheck,
- sameOrigin
+ sameOrigin,
+ isCancelled,
+ isAborted
} = require_util2();
var { kState, kHeaders, kGuard, kRealm } = require_symbols2();
var { AbortError } = require_errors();
@@ -7110,25 +7111,35 @@ var require_fetch = __commonJS({
} = require_constants2();
var { kHeadersList } = require_symbols();
var EE = require("events");
- var { PassThrough, pipeline } = require("stream");
+ var { Readable, pipeline } = require("stream");
var { isErrored, isReadable } = require_util();
- var { kIsMockActive } = require_mock_symbols();
var { dataURLProcessor } = require_dataURL();
+ var { kIsMockActive } = require_mock_symbols();
+ var { TransformStream } = require("stream/web");
var resolveObjectURL;
var ReadableStream;
var Fetch = class extends EE {
constructor(dispatcher) {
super();
this.dispatcher = dispatcher;
- this.terminated = null;
this.connection = null;
this.dump = false;
+ this.state = "ongoing";
+ }
+ terminate(reason) {
+ if (this.state !== "ongoing") {
+ return;
+ }
+ this.state = "terminated";
+ this.connection?.destroy(reason);
+ this.emit("terminated", reason);
}
- terminate({ reason, aborted } = {}) {
- if (this.terminated) {
+ abort() {
+ if (this.state !== "ongoing") {
return;
}
- this.terminated = { aborted, reason };
+ const reason = new AbortError();
+ this.state = "aborted";
this.connection?.destroy(reason);
this.emit("terminated", reason);
}
@@ -7142,25 +7153,27 @@ var require_fetch = __commonJS({
}
const resource = args[0];
const init = args.length >= 1 ? args[1] ?? {} : {};
- const context = new Fetch(this);
const p = createDeferredPromise();
const requestObject = new Request(resource, init);
const request = requestObject[kState];
if (requestObject.signal.aborted) {
- abortFetch.call(context, p, request, null);
+ abortFetch(p, request, null);
return p.promise;
}
- const globalObject = request.client?.globalObject;
- if (globalObject instanceof ServiceWorkerGlobalScope) {
+ const globalObject = request.client.globalObject;
+ if (globalObject?.constructor?.name === "ServiceWorkerGlobalScope") {
request.serviceWorkers = "none";
}
let responseObject = null;
const relevantRealm = null;
let locallyAborted = false;
+ let controller = null;
requestObject.signal.addEventListener("abort", () => {
locallyAborted = true;
- abortFetch.call(context, p, request, responseObject);
- context.terminate({ aborted: true });
+ abortFetch(p, request, responseObject);
+ if (controller != null) {
+ controller.abort();
+ }
}, { once: true });
const handleFetchDone = (response) => finalizeAndReportTiming(response, "fetch");
const processResponse = (response) => {
@@ -7168,7 +7181,7 @@ var require_fetch = __commonJS({
return;
}
if (response.aborted) {
- abortFetch.call(context, p, request, responseObject);
+ abortFetch(p, request, responseObject);
return;
}
if (response.type === "error") {
@@ -7183,12 +7196,11 @@ var require_fetch = __commonJS({
responseObject[kHeaders][kRealm] = relevantRealm;
p.resolve(responseObject);
};
- fetching.call(context, {
+ controller = fetching({
request,
processResponseEndOfBody: handleFetchDone,
- processResponse
- }).catch((err) => {
- p.reject(err);
+ processResponse,
+ dispatcher: this
});
return p.promise;
}
@@ -7251,7 +7263,8 @@ var require_fetch = __commonJS({
processResponse,
processResponseEndOfBody,
processResponseConsumeBody,
- useParallelQueue = false
+ useParallelQueue = false,
+ dispatcher
}) {
let taskDestination = null;
let crossOriginIsolatedCapability = false;
@@ -7264,6 +7277,7 @@ var require_fetch = __commonJS({
startTime: currenTime
});
const fetchParams = {
+ controller: new Fetch(dispatcher),
request,
timingInfo,
processRequestBodyChunkLength,
@@ -7276,7 +7290,7 @@ var require_fetch = __commonJS({
};
assert(!request.body || request.body.stream);
if (request.window === "client") {
- request.window = request.client?.globalObject instanceof Window ? request.client : "no-window";
+ request.window = request.client?.globalObject?.constructor?.name === "Window" ? request.client : "no-window";
}
if (request.origin === "client") {
request.origin = request.client?.origin;
@@ -7299,10 +7313,12 @@ var require_fetch = __commonJS({
}
if (subresource.includes(request.destination)) {
}
- return mainFetch.call(this, fetchParams);
+ mainFetch(fetchParams).catch((err) => {
+ fetchParams.controller.terminate(err);
+ });
+ return fetchParams.controller;
}
async function mainFetch(fetchParams, recursive = false) {
- const context = this;
const request = fetchParams.request;
let response = null;
if (request.localURLsOnly && !/^(about|blob|data):/.test(requestCurrentURL(request).protocol)) {
@@ -7323,7 +7339,7 @@ var require_fetch = __commonJS({
const currentURL = requestCurrentURL(request);
if (sameOrigin(currentURL, request.url) && request.responseTainting === "basic" || currentURL.protocol === "data:" || (request.mode === "navigate" || request.mode === "websocket")) {
request.responseTainting = "basic";
- return await schemeFetch.call(this, fetchParams);
+ return await schemeFetch(fetchParams);
}
if (request.mode === "same-origin") {
return makeNetworkError('request mode cannot be "same-origin"');
@@ -7333,7 +7349,7 @@ var require_fetch = __commonJS({
return makeNetworkError('redirect mode cannot be "follow" for "no-cors" request');
}
request.responseTainting = "opaque";
- const noCorsResponse = await schemeFetch.call(this, fetchParams);
+ const noCorsResponse = await schemeFetch(fetchParams);
if (noCorsResponse.status === 0 || CORBCheck(request, noCorsResponse) === "allowed") {
return noCorsResponse;
}
@@ -7343,7 +7359,7 @@ var require_fetch = __commonJS({
return makeNetworkError("URL scheme must be a HTTP(S) scheme");
}
request.responseTainting = "cors";
- return await httpFetch.call(this, fetchParams).catch((err) => makeNetworkError(err));
+ return await httpFetch(fetchParams);
})();
}
if (recursive) {
@@ -7374,10 +7390,10 @@ var require_fetch = __commonJS({
}
if (response.status !== 0 && (request.method === "HEAD" || request.method === "CONNECT" || nullBodyStatus.includes(internalResponse.status))) {
internalResponse.body = null;
- context.dump = true;
+ fetchParams.controller.dump = true;
}
if (request.integrity) {
- const processBodyError = (reason) => fetchFinale.call(context, fetchParams, makeNetworkError(reason));
+ const processBodyError = (reason) => fetchFinale(fetchParams, makeNetworkError(reason));
if (request.responseTainting === "opaque" || response.body == null) {
processBodyError(response.error);
return;
@@ -7388,7 +7404,7 @@ var require_fetch = __commonJS({
return;
}
response.body = safelyExtractBody(bytes)[0];
- fetchFinale.call(context, fetchParams, response);
+ fetchFinale(fetchParams, response);
};
try {
processBody(await response.arrayBuffer());
@@ -7396,11 +7412,10 @@ var require_fetch = __commonJS({
processBodyError(err);
}
} else {
- fetchFinale.call(context, fetchParams, response);
+ fetchFinale(fetchParams, response);
}
}
async function schemeFetch(fetchParams) {
- const context = this;
const { request } = fetchParams;
const {
protocol: scheme,
@@ -7422,15 +7437,7 @@ var require_fetch = __commonJS({
return makeNetworkError("invalid path called");
}
case "blob:": {
- let onRequestAborted = function() {
- const aborted = context.terminated.aborted;
- if (aborted) {
- return makeNetworkError(new AbortError());
- }
- return makeNetworkError(context.terminated.reason);
- };
resolveObjectURL ??= require("buffer").resolveObjectURL;
- context.on("terminated", onRequestAborted);
const currentURL = requestCurrentURL(request);
if (currentURL.search.length !== 0) {
return makeNetworkError("NetworkError when attempting to fetch resource.");
@@ -7443,7 +7450,6 @@ var require_fetch = __commonJS({
response.headersList.set("content-length", `${blob.size}`);
response.headersList.set("content-type", blob.type);
response.body = extractBody(blob)[0];
- context.off("terminated", onRequestAborted);
return response;
}
case "data:": {
@@ -7472,7 +7478,7 @@ var require_fetch = __commonJS({
"content-type",
contentType
],
- body: dataURLStruct.body
+ body: extractBody(dataURLStruct.body)[0]
});
}
case "file:": {
@@ -7480,7 +7486,7 @@ var require_fetch = __commonJS({
}
case "http:":
case "https:": {
- return await httpFetch.call(this, fetchParams).catch((err) => makeNetworkError(err));
+ return await httpFetch(fetchParams).catch((err) => makeNetworkError(err));
}
default: {
return makeNetworkError("unknown scheme");
@@ -7490,26 +7496,54 @@ var require_fetch = __commonJS({
function finalizeResponse(fetchParams, response) {
fetchParams.request.done = true;
if (fetchParams.processResponseDone != null) {
- fetchParams.processResponseDone(response);
+ queueMicrotask(() => fetchParams.processResponseDone(response));
}
}
- function fetchFinale(fetchParams, response) {
- const context = this;
+ async function fetchFinale(fetchParams, response) {
if (response.type === "error") {
response.urlList = [fetchParams.request.urlList[0]];
response.timingInfo = createOpaqueTimingInfo({
startTime: fetchParams.timingInfo.startTime
});
}
+ const processResponseEndOfBody = () => {
+ fetchParams.request.done = true;
+ if (fetchParams.processResponseEndOfBody != null) {
+ queueMicrotask(() => fetchParams.processResponseEndOfBody(response));
+ }
+ };
if (fetchParams.processResponse != null) {
- fetchParams.processResponse(response);
+ queueMicrotask(() => fetchParams.processResponse(response));
}
- if (response.type === "error") {
- context.terminate({ reason: response.error });
+ if (response.body == null) {
+ processResponseEndOfBody();
+ } else {
+ const identityTransformAlgorithm = (chunk, controller) => {
+ controller.enqueue(chunk);
+ };
+ const transformStream = new TransformStream({
+ start() {
+ },
+ transform: identityTransformAlgorithm,
+ flush: processResponseEndOfBody
+ });
+ response.body = { stream: response.body.stream.pipeThrough(transformStream) };
+ }
+ if (fetchParams.processResponseConsumeBody != null) {
+ const processBody = (nullOrBytes) => fetchParams.processResponseConsumeBody(response, nullOrBytes);
+ const processBodyError = (failure) => fetchParams.processResponseConsumeBody(response, failure);
+ if (response.body == null) {
+ queueMicrotask(() => processBody(null));
+ } else {
+ try {
+ processBody(await response.body.stream.arrayBuffer());
+ } catch (err) {
+ processBodyError(err);
+ }
+ }
}
}
async function httpFetch(fetchParams) {
- const context = this;
const request = fetchParams.request;
let response = null;
let actualResponse = null;
@@ -7520,7 +7554,7 @@ var require_fetch = __commonJS({
if (request.redirect === "follow") {
request.serviceWorkers = "none";
}
- actualResponse = response = await httpNetworkOrCacheFetch.call(this, fetchParams);
+ actualResponse = response = await httpNetworkOrCacheFetch(fetchParams);
if (request.responseTainting === "cors" && corsCheck(request, response) === "failure") {
return makeNetworkError("cors failure");
}
@@ -7532,13 +7566,13 @@ var require_fetch = __commonJS({
return makeNetworkError("blocked");
}
if (redirectStatus.includes(actualResponse.status)) {
- context.connection.destroy();
+ fetchParams.controller.connection.destroy();
if (request.redirect === "error") {
response = makeNetworkError();
} else if (request.redirect === "manual") {
response = actualResponse;
} else if (request.redirect === "follow") {
- response = await httpRedirectFetch.call(this, fetchParams, response);
+ response = await httpRedirectFetch(fetchParams, response);
} else {
assert(false);
}
@@ -7592,10 +7626,9 @@ var require_fetch = __commonJS({
}
request.urlList.push(locationURL);
setRequestReferrerPolicyOnRedirect(request, actualResponse);
- return mainFetch.call(this, fetchParams, true);
+ return mainFetch(fetchParams, true);
}
async function httpNetworkOrCacheFetch(fetchParams, isAuthenticationFetch = false, isNewConnectionFetch = false) {
- const context = this;
const request = fetchParams.request;
let httpFetchParams = null;
let httpRequest = null;
@@ -7667,7 +7700,7 @@ var require_fetch = __commonJS({
if (httpRequest.mode === "only-if-cached") {
return makeNetworkError("only if cached");
}
- const forwardResponse = await httpNetworkFetch.call(this, httpFetchParams, includeCredentials, isNewConnectionFetch);
+ const forwardResponse = await httpNetworkFetch(httpFetchParams, includeCredentials, isNewConnectionFetch);
if (!safeMethods.includes(httpRequest.method) && forwardResponse.status >= 200 && forwardResponse.status <= 399) {
}
if (revalidatingFlag && forwardResponse.status === 304) {
@@ -7680,150 +7713,197 @@ var require_fetch = __commonJS({
if (httpRequest.headersList.has("range")) {
response.rangeRequested = true;
}
+ response.requestIncludesCredentials = includeCredentials;
if (response.status === 407) {
if (request.window === "no-window") {
return makeNetworkError();
}
- if (context.terminated) {
- const aborted = context.terminated.aborted;
- if (aborted) {
- return makeNetworkError(new AbortError());
- }
- return makeNetworkError(context.terminated.reason);
+ if (isCancelled(fetchParams)) {
+ return makeAppropriateNetworkError(fetchParams);
}
return makeNetworkError("proxy authentication required");
}
if (response.status === 421 && !isNewConnectionFetch && (request.body == null || request.body.source != null)) {
- if (context.terminated) {
- const aborted = context.terminated.aborted;
- if (aborted) {
- return makeNetworkError(new AbortError());
- }
- return makeNetworkError(context.terminated.reason);
+ if (isCancelled(fetchParams)) {
+ return makeAppropriateNetworkError(fetchParams);
}
- context.connection.destroy();
- response = await httpNetworkOrCacheFetch.call(this, fetchParams, isAuthenticationFetch, true);
+ fetchParams.controller.connection.destroy();
+ response = await httpNetworkOrCacheFetch(fetchParams, isAuthenticationFetch, true);
}
if (isAuthenticationFetch) {
}
return response;
}
- function httpNetworkFetch(fetchParams, includeCredentials = false, forceNewConnection = false) {
- const context = this;
- return new Promise((resolve) => {
- assert(!context.connection || context.connection.destroyed);
- context.connection = {
- abort: null,
- destroyed: false,
- destroy(err) {
- if (!this.destroyed) {
- this.destroyed = true;
- this.abort?.(err ?? new AbortError());
- }
+ async function httpNetworkFetch(fetchParams, includeCredentials = false, forceNewConnection = false) {
+ assert(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed);
+ fetchParams.controller.connection = {
+ abort: null,
+ destroyed: false,
+ destroy(err) {
+ if (!this.destroyed) {
+ this.destroyed = true;
+ this.abort?.(err ?? new AbortError());
}
- };
- const request = fetchParams.request;
- let response = null;
- const timingInfo = fetchParams.timingInfo;
- const httpCache = null;
- if (httpCache == null) {
- request.cache = "no-store";
- }
- if (request.mode === "websocket") {
- } else {
}
- context.on("terminated", onRequestAborted);
- const body = async function* () {
+ };
+ const request = fetchParams.request;
+ let response = null;
+ const timingInfo = fetchParams.timingInfo;
+ const httpCache = null;
+ if (httpCache == null) {
+ request.cache = "no-store";
+ }
+ const newConnection = forceNewConnection ? "yes" : "no";
+ if (request.mode === "websocket") {
+ } else {
+ }
+ let requestBody = null;
+ if (request.body == null && fetchParams.processRequestEndOfBody) {
+ queueMicrotask(() => fetchParams.processRequestEndOfBody());
+ } else if (request.body != null) {
+ const processBodyChunk = async function* (bytes) {
+ if (isCancelled(fetchParams)) {
+ return;
+ }
+ yield bytes;
+ fetchParams.processRequestBodyChunkLength?.(bytes.byteLength);
+ };
+ const processEndOfBody = () => {
+ if (isCancelled(fetchParams)) {
+ return;
+ }
+ if (fetchParams.processRequestEndOfBody) {
+ fetchParams.processRequestEndOfBody();
+ }
+ };
+ const processBodyError = (e) => {
+ if (isCancelled(fetchParams)) {
+ return;
+ }
+ if (e.name === "AbortError") {
+ fetchParams.controller.abort();
+ } else {
+ fetchParams.controller.terminate(e);
+ }
+ };
+ requestBody = async function* () {
try {
- if (request.body === null) {
- fetchParams.processEndOfBody?.();
- return;
- }
for await (const bytes of request.body.stream) {
- if (context.terminated) {
- return;
- }
- yield bytes;
- fetchParams.processRequestBody?.(bytes.byteLength);
- }
- if (context.terminated) {
- return;
+ yield* processBodyChunk(bytes);
}
- fetchParams.processRequestEndOfBody?.();
- } catch (e) {
- if (context.terminated) {
- return;
- }
- context.terminate({
- aborted: e.name === "AbortError",
- reason: e
- });
+ processEndOfBody();
+ } catch (err) {
+ processBodyError(err);
}
}();
- function onRequestAborted() {
- const aborted = this.terminated.aborted;
- this.connection.destroy();
- if (aborted) {
- return resolve(makeNetworkError(new AbortError()));
- }
- return resolve(makeNetworkError(this.terminated.reason));
+ }
+ try {
+ const { body, status, statusText, headersList } = await dispatch({ body: requestBody });
+ const iterator = body[Symbol.asyncIterator]();
+ fetchParams.controller.next = () => iterator.next();
+ response = makeResponse({ status, statusText, headersList });
+ } catch (err) {
+ if (err.name === "AbortError") {
+ fetchParams.controller.connection.destroy();
+ return makeAppropriateNetworkError(fetchParams);
}
- let pullAlgorithm;
- const cancelAlgorithm = () => {
- context.terminate({ aborted: true });
- };
- const highWaterMark = 64 * 1024;
- if (!ReadableStream) {
- ReadableStream = require("stream/web").ReadableStream;
- }
- let pullResolve;
- const stream = new ReadableStream({
- async start(controller) {
- context.controller = controller;
- },
- async pull(controller) {
- if (!pullAlgorithm) {
- await new Promise((resolve2) => {
- pullResolve = resolve2;
- });
- }
- await pullAlgorithm(controller);
- },
- async cancel(reason) {
- await cancelAlgorithm(reason);
- }
- }, { highWaterMark });
- function onResponseAborted() {
- const aborted = this.terminated.aborted;
- if (aborted) {
- response.aborted = true;
- if (isReadable(stream)) {
- this.controller.error(new AbortError());
+ return makeNetworkError(err);
+ }
+ const pullAlgorithm = () => {
+ fetchParams.controller.resume();
+ };
+ const cancelAlgorithm = () => {
+ fetchParams.controller.abort();
+ };
+ if (!ReadableStream) {
+ ReadableStream = require("stream/web").ReadableStream;
+ }
+ const stream = new ReadableStream({
+ async start(controller) {
+ fetchParams.controller.controller = controller;
+ },
+ async pull(controller) {
+ await pullAlgorithm(controller);
+ },
+ async cancel(reason) {
+ await cancelAlgorithm(reason);
+ }
+ }, { highWaterMark: 0 });
+ response.body = { stream };
+ fetchParams.controller.on("terminated", onAborted);
+ fetchParams.controller.resume = async () => {
+ while (true) {
+ let bytes;
+ try {
+ const { done, value } = await fetchParams.controller.next();
+ bytes = done ? void 0 : value;
+ } catch (err) {
+ if (fetchParams.controller.ended && !timingInfo.encodedBodySize) {
+ bytes = void 0;
+ } else {
+ bytes = err;
}
- } else {
- if (isReadable(stream)) {
- this.controller.error(new TypeError("terminated"));
+ }
+ if (bytes === void 0) {
+ try {
+ fetchParams.controller.controller.close();
+ } catch (err) {
+ if (!/Controller is already closed/.test(err)) {
+ throw err;
+ }
}
+ finalizeResponse(fetchParams, response);
+ return;
+ }
+ timingInfo.decodedBodySize += bytes?.byteLength ?? 0;
+ if (bytes instanceof Error) {
+ fetchParams.controller.terminate(bytes);
+ return;
+ }
+ fetchParams.controller.controller.enqueue(new Uint8Array(bytes));
+ if (isErrored(stream)) {
+ fetchParams.controller.terminate();
+ return;
+ }
+ if (!fetchParams.controller.controller.desiredSize) {
+ return;
+ }
+ }
+ };
+ function onAborted(reason) {
+ if (isAborted(fetchParams)) {
+ response.aborted = true;
+ if (isReadable(stream)) {
+ fetchParams.controller.controller.error(new AbortError());
+ }
+ } else {
+ if (isReadable(stream)) {
+ fetchParams.controller.controller.error(new TypeError("terminated", {
+ cause: reason instanceof Error ? reason : void 0
+ }));
}
- this.connection.destroy();
}
+ fetchParams.controller.connection.destroy();
+ }
+ return response;
+ async function dispatch({ body }) {
const url = requestCurrentURL(request);
- context.dispatcher.dispatch({
+ return new Promise((resolve, reject) => fetchParams.controller.dispatcher.dispatch({
path: url.pathname + url.search,
origin: url.origin,
method: request.method,
- body: context.dispatcher[kIsMockActive] ? request.body && request.body.source : body,
+ body: fetchParams.controller.dispatcher[kIsMockActive] ? request.body && request.body.source : body,
headers: request.headersList,
maxRedirections: 0
}, {
- decoder: null,
+ body: null,
abort: null,
- context,
onConnect(abort) {
- const { connection } = this.context;
+ const { connection } = fetchParams.controller;
if (connection.destroyed) {
abort(new AbortError());
} else {
+ fetchParams.controller.on("terminated", abort);
this.abort = connection.abort = abort;
}
},
@@ -7831,18 +7911,17 @@ var require_fetch = __commonJS({
if (status < 200) {
return;
}
+ let codings = [];
const headers = new Headers();
for (let n = 0; n < headersList.length; n += 2) {
- headers.append(headersList[n + 0].toString(), headersList[n + 1].toString());
+ const key = headersList[n + 0].toString();
+ const val = headersList[n + 1].toString();
+ if (key.toLowerCase() === "content-encoding") {
+ codings = val.split(",").map((x) => x.trim());
+ }
+ headers.append(key, val);
}
- response = makeResponse({
- status,
- statusText,
- headersList: headers[kHeadersList],
- body: { stream }
- });
- this.context.on("terminated", onResponseAborted);
- const codings = headers.get("content-encoding")?.toLowerCase().split(",").map((x) => x.trim()) ?? [];
+ this.body = new Readable({ read: resume });
const decoders = [];
for (const coding of codings) {
if (/(x-)?gzip/.test(coding)) {
@@ -7856,70 +7935,41 @@ var require_fetch = __commonJS({
break;
}
}
- if (decoders.length > 1) {
- pipeline(...decoders, () => {
- });
- } else if (decoders.length === 0) {
- decoders.push(new PassThrough());
- }
- this.decoder = decoders[0].on("drain", resume);
- const iterator = decoders[decoders.length - 1][Symbol.asyncIterator]();
- pullAlgorithm = async (controller) => {
- let bytes;
- try {
- const { done, value } = await iterator.next();
- bytes = done ? void 0 : value;
- } catch (err) {
- if (this.decoder.writableEnded && !timingInfo.encodedBodySize) {
- bytes = void 0;
- } else {
- bytes = err;
- }
- }
- if (bytes === void 0) {
- finalizeResponse(fetchParams, response);
- controller.close();
- return;
- }
- timingInfo.decodedBodySize += bytes?.byteLength ?? 0;
- if (bytes instanceof Error) {
- this.context.terminate({ reason: bytes });
- return;
- }
- controller.enqueue(new Uint8Array(bytes));
- if (isErrored(stream)) {
- this.context.terminate();
- return;
- }
- return controller.desiredSize > 0;
- };
- if (pullResolve) {
- pullResolve();
- pullResolve = null;
- }
- resolve(response);
+ resolve({
+ status,
+ statusText,
+ headersList: headers[kHeadersList],
+ body: decoders.length ? pipeline(this.body, ...decoders, () => {
+ }) : this.body.on("error", () => {
+ })
+ });
return true;
},
onData(chunk) {
- if (this.context.dump) {
+ if (fetchParams.controller.dump) {
return;
}
const bytes = chunk;
timingInfo.encodedBodySize += bytes.byteLength;
- return this.decoder.write(bytes);
+ return this.body.push(bytes);
},
onComplete() {
- this.decoder.end();
+ if (this.abort) {
+ fetchParams.controller.off("terminated", this.abort);
+ }
+ fetchParams.controller.ended = true;
+ this.body.push(null);
},
onError(error) {
- this.decoder?.destroy(error);
- this.context.terminate({ reason: error });
- if (!response) {
- resolve(makeNetworkError(error));
+ if (this.abort) {
+ fetchParams.controller.off("terminated", this.abort);
}
+ this.body?.destroy(error);
+ fetchParams.controller.terminate(error);
+ reject(makeNetworkError(error));
}
- });
- });
+ }));
+ }
}
module2.exports = fetch;
}