undici 6.19.8 → 7.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -9
- package/docs/docs/api/Agent.md +0 -3
- package/docs/docs/api/Client.md +0 -2
- package/docs/docs/api/Dispatcher.md +204 -6
- package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
- package/docs/docs/api/Fetch.md +1 -0
- package/docs/docs/api/Pool.md +0 -1
- package/docs/docs/api/RetryHandler.md +1 -1
- package/index.js +0 -4
- package/lib/api/api-connect.js +3 -1
- package/lib/api/api-pipeline.js +3 -4
- package/lib/api/api-request.js +29 -46
- package/lib/api/api-stream.js +36 -49
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +71 -27
- package/lib/core/connect.js +39 -24
- package/lib/core/errors.js +17 -4
- package/lib/core/request.js +7 -5
- package/lib/core/symbols.js +0 -1
- package/lib/core/tree.js +6 -0
- package/lib/core/util.js +1 -11
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +44 -39
- package/lib/dispatcher/client.js +3 -27
- package/lib/dispatcher/dispatcher-base.js +2 -34
- package/lib/dispatcher/dispatcher.js +3 -24
- package/lib/dispatcher/pool.js +3 -6
- package/lib/dispatcher/proxy-agent.js +3 -6
- package/lib/handler/decorator-handler.js +24 -0
- package/lib/handler/redirect-handler.js +9 -0
- package/lib/handler/retry-handler.js +22 -3
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +89 -0
- package/lib/llhttp/constants.d.ts +97 -0
- package/lib/llhttp/constants.js +412 -192
- package/lib/llhttp/constants.js.map +1 -0
- package/lib/llhttp/llhttp-wasm.js +11 -1
- package/lib/llhttp/llhttp_simd-wasm.js +11 -1
- package/lib/llhttp/utils.d.ts +2 -0
- package/lib/llhttp/utils.js +9 -9
- package/lib/llhttp/utils.js.map +1 -0
- package/lib/mock/mock-client.js +2 -2
- package/lib/mock/mock-pool.js +2 -2
- package/lib/mock/mock-symbols.js +1 -0
- package/lib/util/timers.js +324 -44
- package/lib/web/cookies/index.js +15 -13
- package/lib/web/cookies/parse.js +2 -2
- package/lib/web/eventsource/eventsource-stream.js +9 -8
- package/lib/web/eventsource/eventsource.js +10 -6
- package/lib/web/fetch/body.js +4 -6
- package/lib/web/fetch/data-url.js +1 -1
- package/lib/web/fetch/formdata-parser.js +1 -2
- package/lib/web/fetch/formdata.js +28 -37
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +7 -8
- package/lib/web/fetch/request.js +7 -24
- package/lib/web/fetch/response.js +9 -22
- package/lib/web/fetch/symbols.js +0 -1
- package/lib/web/fetch/util.js +3 -12
- package/lib/web/fetch/webidl.js +73 -62
- package/lib/web/websocket/connection.js +26 -174
- package/lib/web/websocket/constants.js +1 -1
- package/lib/web/websocket/frame.js +45 -3
- package/lib/web/websocket/receiver.js +28 -26
- package/lib/web/websocket/sender.js +18 -13
- package/lib/web/websocket/util.js +20 -74
- package/lib/web/websocket/websocket.js +294 -70
- package/package.json +16 -29
- package/scripts/strip-comments.js +3 -1
- package/types/agent.d.ts +7 -7
- package/types/api.d.ts +24 -24
- package/types/balanced-pool.d.ts +11 -11
- package/types/client.d.ts +11 -12
- package/types/diagnostics-channel.d.ts +10 -10
- package/types/dispatcher.d.ts +96 -97
- package/types/env-http-proxy-agent.d.ts +2 -2
- package/types/errors.d.ts +53 -47
- package/types/eventsource.d.ts +0 -2
- package/types/fetch.d.ts +8 -8
- package/types/formdata.d.ts +7 -7
- package/types/global-dispatcher.d.ts +4 -4
- package/types/global-origin.d.ts +5 -5
- package/types/handlers.d.ts +4 -4
- package/types/header.d.ts +157 -1
- package/types/index.d.ts +42 -46
- package/types/interceptors.d.ts +10 -8
- package/types/mock-agent.d.ts +18 -18
- package/types/mock-client.d.ts +4 -4
- package/types/mock-errors.d.ts +3 -3
- package/types/mock-interceptor.d.ts +19 -19
- package/types/mock-pool.d.ts +4 -4
- package/types/patch.d.ts +0 -42
- package/types/pool-stats.d.ts +8 -8
- package/types/pool.d.ts +12 -12
- package/types/proxy-agent.d.ts +4 -4
- package/types/readable.d.ts +14 -9
- package/types/retry-agent.d.ts +1 -1
- package/types/retry-handler.d.ts +8 -8
- package/types/util.d.ts +3 -3
- package/types/utility.d.ts +7 -0
- package/types/webidl.d.ts +22 -4
- package/types/websocket.d.ts +1 -3
- package/docs/docs/api/DispatchInterceptor.md +0 -60
- package/lib/interceptor/redirect-interceptor.js +0 -21
- package/lib/web/fetch/file.js +0 -126
- package/lib/web/fileapi/encoding.js +0 -290
- package/lib/web/fileapi/filereader.js +0 -344
- package/lib/web/fileapi/progressevent.js +0 -78
- package/lib/web/fileapi/symbols.js +0 -10
- package/lib/web/fileapi/util.js +0 -391
- package/lib/web/websocket/symbols.js +0 -12
- package/types/file.d.ts +0 -39
- package/types/filereader.d.ts +0 -54
package/lib/api/api-stream.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
-
const { finished
|
|
4
|
+
const { finished } = require('node:stream')
|
|
5
|
+
const { AsyncResource } = require('node:async_hooks')
|
|
5
6
|
const { InvalidArgumentError, InvalidReturnValueError } = require('../core/errors')
|
|
6
7
|
const util = require('../core/util')
|
|
7
|
-
const { getResolveErrorBodyCallback } = require('./util')
|
|
8
|
-
const { AsyncResource } = require('node:async_hooks')
|
|
9
8
|
const { addSignal, removeSignal } = require('./abort-signal')
|
|
10
9
|
|
|
11
10
|
class StreamHandler extends AsyncResource {
|
|
@@ -14,7 +13,7 @@ class StreamHandler extends AsyncResource {
|
|
|
14
13
|
throw new InvalidArgumentError('invalid opts')
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
const { signal, method, opaque, body, onInfo, responseHeaders
|
|
16
|
+
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
18
17
|
|
|
19
18
|
try {
|
|
20
19
|
if (typeof callback !== 'function') {
|
|
@@ -55,7 +54,6 @@ class StreamHandler extends AsyncResource {
|
|
|
55
54
|
this.trailers = null
|
|
56
55
|
this.body = body
|
|
57
56
|
this.onInfo = onInfo || null
|
|
58
|
-
this.throwOnError = throwOnError || false
|
|
59
57
|
|
|
60
58
|
if (util.isStream(body)) {
|
|
61
59
|
body.on('error', (err) => {
|
|
@@ -79,7 +77,7 @@ class StreamHandler extends AsyncResource {
|
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
82
|
-
const { factory, opaque, context,
|
|
80
|
+
const { factory, opaque, context, responseHeaders } = this
|
|
83
81
|
|
|
84
82
|
const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
85
83
|
|
|
@@ -92,55 +90,42 @@ class StreamHandler extends AsyncResource {
|
|
|
92
90
|
|
|
93
91
|
this.factory = null
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
if (factory === null) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
const res = this.runInAsyncScope(factory, null, {
|
|
98
|
+
statusCode,
|
|
99
|
+
headers,
|
|
100
|
+
opaque,
|
|
101
|
+
context
|
|
102
|
+
})
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
if (
|
|
105
|
+
!res ||
|
|
106
|
+
typeof res.write !== 'function' ||
|
|
107
|
+
typeof res.end !== 'function' ||
|
|
108
|
+
typeof res.on !== 'function'
|
|
109
|
+
) {
|
|
110
|
+
throw new InvalidReturnValueError('expected Writable')
|
|
111
|
+
}
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
opaque,
|
|
115
|
-
context
|
|
116
|
-
})
|
|
113
|
+
// TODO: Avoid finished. It registers an unnecessary amount of listeners.
|
|
114
|
+
finished(res, { readable: false }, (err) => {
|
|
115
|
+
const { callback, res, opaque, trailers, abort } = this
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
typeof res.end !== 'function' ||
|
|
122
|
-
typeof res.on !== 'function'
|
|
123
|
-
) {
|
|
124
|
-
throw new InvalidReturnValueError('expected Writable')
|
|
117
|
+
this.res = null
|
|
118
|
+
if (err || !res.readable) {
|
|
119
|
+
util.destroy(res, err)
|
|
125
120
|
}
|
|
126
121
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const { callback, res, opaque, trailers, abort } = this
|
|
130
|
-
|
|
131
|
-
this.res = null
|
|
132
|
-
if (err || !res.readable) {
|
|
133
|
-
util.destroy(res, err)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
this.callback = null
|
|
137
|
-
this.runInAsyncScope(callback, null, err || null, { opaque, trailers })
|
|
122
|
+
this.callback = null
|
|
123
|
+
this.runInAsyncScope(callback, null, err || null, { opaque, trailers })
|
|
138
124
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
125
|
+
if (err) {
|
|
126
|
+
abort()
|
|
127
|
+
}
|
|
128
|
+
})
|
|
144
129
|
|
|
145
130
|
res.on('drain', resume)
|
|
146
131
|
|
|
@@ -207,7 +192,9 @@ function stream (opts, factory, callback) {
|
|
|
207
192
|
}
|
|
208
193
|
|
|
209
194
|
try {
|
|
210
|
-
|
|
195
|
+
const handler = new StreamHandler(opts, factory, callback)
|
|
196
|
+
|
|
197
|
+
this.dispatch(opts, handler)
|
|
211
198
|
} catch (err) {
|
|
212
199
|
if (typeof callback !== 'function') {
|
|
213
200
|
throw err
|
package/lib/api/api-upgrade.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const { InvalidArgumentError, SocketError } = require('../core/errors')
|
|
4
4
|
const { AsyncResource } = require('node:async_hooks')
|
|
5
|
+
const assert = require('node:assert')
|
|
5
6
|
const util = require('../core/util')
|
|
6
7
|
const { addSignal, removeSignal } = require('./abort-signal')
|
|
7
|
-
const assert = require('node:assert')
|
|
8
8
|
|
|
9
9
|
class UpgradeHandler extends AsyncResource {
|
|
10
10
|
constructor (opts, callback) {
|
|
@@ -91,11 +91,13 @@ function upgrade (opts, callback) {
|
|
|
91
91
|
|
|
92
92
|
try {
|
|
93
93
|
const upgradeHandler = new UpgradeHandler(opts, callback)
|
|
94
|
-
|
|
94
|
+
const upgradeOpts = {
|
|
95
95
|
...opts,
|
|
96
96
|
method: opts.method || 'GET',
|
|
97
97
|
upgrade: opts.protocol || 'Websocket'
|
|
98
|
-
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.dispatch(upgradeOpts, upgradeHandler)
|
|
99
101
|
} catch (err) {
|
|
100
102
|
if (typeof callback !== 'function') {
|
|
101
103
|
throw err
|
package/lib/api/readable.js
CHANGED
|
@@ -14,6 +14,8 @@ const kBody = Symbol('kBody')
|
|
|
14
14
|
const kAbort = Symbol('kAbort')
|
|
15
15
|
const kContentType = Symbol('kContentType')
|
|
16
16
|
const kContentLength = Symbol('kContentLength')
|
|
17
|
+
const kUsed = Symbol('kUsed')
|
|
18
|
+
const kBytesRead = Symbol('kBytesRead')
|
|
17
19
|
|
|
18
20
|
const noop = () => {}
|
|
19
21
|
|
|
@@ -35,9 +37,11 @@ class BodyReadable extends Readable {
|
|
|
35
37
|
|
|
36
38
|
this[kAbort] = abort
|
|
37
39
|
this[kConsume] = null
|
|
40
|
+
this[kBytesRead] = 0
|
|
38
41
|
this[kBody] = null
|
|
42
|
+
this[kUsed] = false
|
|
39
43
|
this[kContentType] = contentType
|
|
40
|
-
this[kContentLength] = contentLength
|
|
44
|
+
this[kContentLength] = Number.isFinite(contentLength) ? contentLength : null
|
|
41
45
|
|
|
42
46
|
// Is stream being consumed through Readable API?
|
|
43
47
|
// This is an optimization so that we avoid checking
|
|
@@ -46,7 +50,7 @@ class BodyReadable extends Readable {
|
|
|
46
50
|
this[kReading] = false
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
_destroy (err, callback) {
|
|
50
54
|
if (!err && !this._readableState.endEmitted) {
|
|
51
55
|
err = new RequestAbortedError()
|
|
52
56
|
}
|
|
@@ -55,15 +59,11 @@ class BodyReadable extends Readable {
|
|
|
55
59
|
this[kAbort]()
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
return super.destroy(err)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
_destroy (err, callback) {
|
|
62
62
|
// Workaround for Node "bug". If the stream is destroyed in same
|
|
63
63
|
// tick as it is created, then a user who is waiting for a
|
|
64
64
|
// promise (i.e micro tick) for installing a 'error' listener will
|
|
65
65
|
// never get a chance and will always encounter an unhandled exception.
|
|
66
|
-
if (!this[
|
|
66
|
+
if (!this[kUsed]) {
|
|
67
67
|
setImmediate(() => {
|
|
68
68
|
callback(err)
|
|
69
69
|
})
|
|
@@ -75,6 +75,7 @@ class BodyReadable extends Readable {
|
|
|
75
75
|
on (ev, ...args) {
|
|
76
76
|
if (ev === 'data' || ev === 'readable') {
|
|
77
77
|
this[kReading] = true
|
|
78
|
+
this[kUsed] = true
|
|
78
79
|
}
|
|
79
80
|
return super.on(ev, ...args)
|
|
80
81
|
}
|
|
@@ -99,6 +100,8 @@ class BodyReadable extends Readable {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
push (chunk) {
|
|
103
|
+
this[kBytesRead] += chunk ? chunk.length : 0
|
|
104
|
+
|
|
102
105
|
if (this[kConsume] && chunk !== null) {
|
|
103
106
|
consumePush(this[kConsume], chunk)
|
|
104
107
|
return this[kReading] ? super.push(chunk) : true
|
|
@@ -121,6 +124,11 @@ class BodyReadable extends Readable {
|
|
|
121
124
|
return consume(this, 'blob')
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
// https://fetch.spec.whatwg.org/#dom-body-bytes
|
|
128
|
+
async bytes () {
|
|
129
|
+
return consume(this, 'bytes')
|
|
130
|
+
}
|
|
131
|
+
|
|
124
132
|
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
125
133
|
async arrayBuffer () {
|
|
126
134
|
return consume(this, 'arrayBuffer')
|
|
@@ -151,13 +159,14 @@ class BodyReadable extends Readable {
|
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
async dump (opts) {
|
|
154
|
-
let limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024
|
|
155
162
|
const signal = opts?.signal
|
|
156
163
|
|
|
157
164
|
if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) {
|
|
158
165
|
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
159
166
|
}
|
|
160
167
|
|
|
168
|
+
const limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024
|
|
169
|
+
|
|
161
170
|
signal?.throwIfAborted()
|
|
162
171
|
|
|
163
172
|
if (this._readableState.closeEmitted) {
|
|
@@ -165,7 +174,7 @@ class BodyReadable extends Readable {
|
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
return await new Promise((resolve, reject) => {
|
|
168
|
-
if (this[kContentLength] > limit) {
|
|
177
|
+
if (this[kContentLength] > limit || this[kBytesRead] > limit) {
|
|
169
178
|
this.destroy(new AbortError())
|
|
170
179
|
}
|
|
171
180
|
|
|
@@ -185,14 +194,24 @@ class BodyReadable extends Readable {
|
|
|
185
194
|
})
|
|
186
195
|
.on('error', noop)
|
|
187
196
|
.on('data', function (chunk) {
|
|
188
|
-
limit
|
|
189
|
-
if (limit <= 0) {
|
|
197
|
+
if (this[kBytesRead] > limit) {
|
|
190
198
|
this.destroy()
|
|
191
199
|
}
|
|
192
200
|
})
|
|
193
201
|
.resume()
|
|
194
202
|
})
|
|
195
203
|
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @param {BufferEncoding} encoding
|
|
207
|
+
* @returns {BodyReadable}
|
|
208
|
+
*/
|
|
209
|
+
setEncoding (encoding) {
|
|
210
|
+
if (Buffer.isEncoding(encoding)) {
|
|
211
|
+
this._readableState.encoding = encoding
|
|
212
|
+
}
|
|
213
|
+
return this
|
|
214
|
+
}
|
|
196
215
|
}
|
|
197
216
|
|
|
198
217
|
// https://streams.spec.whatwg.org/#readablestream-locked
|
|
@@ -270,10 +289,10 @@ function consumeStart (consume) {
|
|
|
270
289
|
}
|
|
271
290
|
|
|
272
291
|
if (state.endEmitted) {
|
|
273
|
-
consumeEnd(this[kConsume])
|
|
292
|
+
consumeEnd(this[kConsume], this._readableState.encoding)
|
|
274
293
|
} else {
|
|
275
294
|
consume.stream.on('end', function () {
|
|
276
|
-
consumeEnd(this[kConsume])
|
|
295
|
+
consumeEnd(this[kConsume], this._readableState.encoding)
|
|
277
296
|
})
|
|
278
297
|
}
|
|
279
298
|
|
|
@@ -287,8 +306,10 @@ function consumeStart (consume) {
|
|
|
287
306
|
/**
|
|
288
307
|
* @param {Buffer[]} chunks
|
|
289
308
|
* @param {number} length
|
|
309
|
+
* @param {BufferEncoding} encoding
|
|
310
|
+
* @returns {string}
|
|
290
311
|
*/
|
|
291
|
-
function chunksDecode (chunks, length) {
|
|
312
|
+
function chunksDecode (chunks, length, encoding) {
|
|
292
313
|
if (chunks.length === 0 || length === 0) {
|
|
293
314
|
return ''
|
|
294
315
|
}
|
|
@@ -303,29 +324,52 @@ function chunksDecode (chunks, length) {
|
|
|
303
324
|
buffer[2] === 0xbf
|
|
304
325
|
? 3
|
|
305
326
|
: 0
|
|
306
|
-
|
|
327
|
+
if (!encoding || encoding === 'utf8' || encoding === 'utf-8') {
|
|
328
|
+
return buffer.utf8Slice(start, bufferLength)
|
|
329
|
+
} else {
|
|
330
|
+
return buffer.subarray(start, bufferLength).toString(encoding)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @param {Buffer[]} chunks
|
|
336
|
+
* @param {number} length
|
|
337
|
+
* @returns {Uint8Array}
|
|
338
|
+
*/
|
|
339
|
+
function chunksConcat (chunks, length) {
|
|
340
|
+
if (chunks.length === 0 || length === 0) {
|
|
341
|
+
return new Uint8Array(0)
|
|
342
|
+
}
|
|
343
|
+
if (chunks.length === 1) {
|
|
344
|
+
// fast-path
|
|
345
|
+
return new Uint8Array(chunks[0])
|
|
346
|
+
}
|
|
347
|
+
const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer)
|
|
348
|
+
|
|
349
|
+
let offset = 0
|
|
350
|
+
for (let i = 0; i < chunks.length; ++i) {
|
|
351
|
+
const chunk = chunks[i]
|
|
352
|
+
buffer.set(chunk, offset)
|
|
353
|
+
offset += chunk.length
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return buffer
|
|
307
357
|
}
|
|
308
358
|
|
|
309
|
-
function consumeEnd (consume) {
|
|
359
|
+
function consumeEnd (consume, encoding) {
|
|
310
360
|
const { type, body, resolve, stream, length } = consume
|
|
311
361
|
|
|
312
362
|
try {
|
|
313
363
|
if (type === 'text') {
|
|
314
|
-
resolve(chunksDecode(body, length))
|
|
364
|
+
resolve(chunksDecode(body, length, encoding))
|
|
315
365
|
} else if (type === 'json') {
|
|
316
|
-
resolve(JSON.parse(chunksDecode(body, length)))
|
|
366
|
+
resolve(JSON.parse(chunksDecode(body, length, encoding)))
|
|
317
367
|
} else if (type === 'arrayBuffer') {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
let pos = 0
|
|
321
|
-
for (const buf of body) {
|
|
322
|
-
dst.set(buf, pos)
|
|
323
|
-
pos += buf.byteLength
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
resolve(dst.buffer)
|
|
368
|
+
resolve(chunksConcat(body, length).buffer)
|
|
327
369
|
} else if (type === 'blob') {
|
|
328
370
|
resolve(new Blob(body, { type: stream[kContentType] }))
|
|
371
|
+
} else if (type === 'bytes') {
|
|
372
|
+
resolve(chunksConcat(body, length))
|
|
329
373
|
}
|
|
330
374
|
|
|
331
375
|
consumeFinish(consume)
|
package/lib/core/connect.js
CHANGED
|
@@ -4,6 +4,7 @@ const net = require('node:net')
|
|
|
4
4
|
const assert = require('node:assert')
|
|
5
5
|
const util = require('./util')
|
|
6
6
|
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
|
|
7
|
+
const timers = require('../util/timers')
|
|
7
8
|
|
|
8
9
|
let tls // include tls conditionally since it is not always available
|
|
9
10
|
|
|
@@ -130,12 +131,12 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
|
|
|
130
131
|
socket.setKeepAlive(true, keepAliveInitialDelay)
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
const
|
|
134
|
+
const cancelConnectTimeout = setupConnectTimeout(new WeakRef(socket), timeout)
|
|
134
135
|
|
|
135
136
|
socket
|
|
136
137
|
.setNoDelay(true)
|
|
137
138
|
.once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
|
|
138
|
-
|
|
139
|
+
cancelConnectTimeout()
|
|
139
140
|
|
|
140
141
|
if (callback) {
|
|
141
142
|
const cb = callback
|
|
@@ -144,7 +145,7 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
|
|
|
144
145
|
}
|
|
145
146
|
})
|
|
146
147
|
.on('error', function (err) {
|
|
147
|
-
|
|
148
|
+
cancelConnectTimeout()
|
|
148
149
|
|
|
149
150
|
if (callback) {
|
|
150
151
|
const cb = callback
|
|
@@ -157,30 +158,44 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
const setupConnectTimeout = process.platform === 'win32'
|
|
162
|
+
? (socket, timeout) => {
|
|
163
|
+
if (!timeout) {
|
|
164
|
+
return () => { }
|
|
165
|
+
}
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (process.platform === 'win32') {
|
|
167
|
+
let s1 = null
|
|
168
|
+
let s2 = null
|
|
169
|
+
const timer = timers.setTimeout(() => {
|
|
170
|
+
// setImmediate is added to make sure that we prioritize socket error events over timeouts
|
|
171
|
+
s1 = setImmediate(() => {
|
|
171
172
|
// Windows needs an extra setImmediate probably due to implementation differences in the socket logic
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
s2 = setImmediate(() => onConnectTimeout(socket.deref()))
|
|
174
|
+
})
|
|
175
|
+
}, timeout)
|
|
176
|
+
return () => {
|
|
177
|
+
timers.clearTimeout(timer)
|
|
178
|
+
clearImmediate(s1)
|
|
179
|
+
clearImmediate(s2)
|
|
175
180
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
}
|
|
182
|
+
: (socket, timeout) => {
|
|
183
|
+
if (!timeout) {
|
|
184
|
+
return () => { }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let s1 = null
|
|
188
|
+
const timer = timers.setTimeout(() => {
|
|
189
|
+
// setImmediate is added to make sure that we prioritize socket error events over timeouts
|
|
190
|
+
s1 = setImmediate(() => {
|
|
191
|
+
onConnectTimeout(socket.deref())
|
|
192
|
+
})
|
|
193
|
+
}, timeout)
|
|
194
|
+
return () => {
|
|
195
|
+
timers.clearTimeout(timer)
|
|
196
|
+
clearImmediate(s1)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
184
199
|
|
|
185
200
|
function onConnectTimeout (socket) {
|
|
186
201
|
let message = 'Connect Timeout Error'
|
package/lib/core/errors.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
class UndiciError extends Error {
|
|
4
|
-
constructor (message) {
|
|
5
|
-
super(message)
|
|
4
|
+
constructor (message, options) {
|
|
5
|
+
super(message, options)
|
|
6
6
|
this.name = 'UndiciError'
|
|
7
7
|
this.code = 'UND_ERR'
|
|
8
8
|
}
|
|
@@ -195,9 +195,21 @@ class RequestRetryError extends UndiciError {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
class ResponseError extends UndiciError {
|
|
199
|
+
constructor (message, code, { headers, data }) {
|
|
200
|
+
super(message)
|
|
201
|
+
this.name = 'ResponseError'
|
|
202
|
+
this.message = message || 'Response error'
|
|
203
|
+
this.code = 'UND_ERR_RESPONSE'
|
|
204
|
+
this.statusCode = code
|
|
205
|
+
this.data = data
|
|
206
|
+
this.headers = headers
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
198
210
|
class SecureProxyConnectionError extends UndiciError {
|
|
199
|
-
constructor (cause, message, options) {
|
|
200
|
-
super(message, { cause, ...
|
|
211
|
+
constructor (cause, message, options = {}) {
|
|
212
|
+
super(message, { cause, ...options })
|
|
201
213
|
this.name = 'SecureProxyConnectionError'
|
|
202
214
|
this.message = message || 'Secure Proxy Connection failed'
|
|
203
215
|
this.code = 'UND_ERR_PRX_TLS'
|
|
@@ -227,5 +239,6 @@ module.exports = {
|
|
|
227
239
|
BalancedPoolMissingUpstreamError,
|
|
228
240
|
ResponseExceededMaxSizeError,
|
|
229
241
|
RequestRetryError,
|
|
242
|
+
ResponseError,
|
|
230
243
|
SecureProxyConnectionError
|
|
231
244
|
}
|
package/lib/core/request.js
CHANGED
|
@@ -40,9 +40,9 @@ class Request {
|
|
|
40
40
|
headersTimeout,
|
|
41
41
|
bodyTimeout,
|
|
42
42
|
reset,
|
|
43
|
-
throwOnError,
|
|
44
43
|
expectContinue,
|
|
45
|
-
servername
|
|
44
|
+
servername,
|
|
45
|
+
throwOnError
|
|
46
46
|
}, handler) {
|
|
47
47
|
if (typeof path !== 'string') {
|
|
48
48
|
throw new InvalidArgumentError('path must be a string')
|
|
@@ -82,12 +82,14 @@ class Request {
|
|
|
82
82
|
throw new InvalidArgumentError('invalid expectContinue')
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
if (throwOnError != null) {
|
|
86
|
+
throw new InvalidArgumentError('invalid throwOnError')
|
|
87
|
+
}
|
|
88
|
+
|
|
85
89
|
this.headersTimeout = headersTimeout
|
|
86
90
|
|
|
87
91
|
this.bodyTimeout = bodyTimeout
|
|
88
92
|
|
|
89
|
-
this.throwOnError = throwOnError === true
|
|
90
|
-
|
|
91
93
|
this.method = method
|
|
92
94
|
|
|
93
95
|
this.abort = null
|
|
@@ -183,7 +185,7 @@ class Request {
|
|
|
183
185
|
|
|
184
186
|
validateHandler(handler, method, upgrade)
|
|
185
187
|
|
|
186
|
-
this.servername = servername || getServerName(this.host)
|
|
188
|
+
this.servername = servername || getServerName(this.host) || null
|
|
187
189
|
|
|
188
190
|
this[kHandler] = handler
|
|
189
191
|
|
package/lib/core/symbols.js
CHANGED
|
@@ -52,7 +52,6 @@ module.exports = {
|
|
|
52
52
|
kMaxRequests: Symbol('maxRequestsPerClient'),
|
|
53
53
|
kProxy: Symbol('proxy agent options'),
|
|
54
54
|
kCounter: Symbol('socket request counter'),
|
|
55
|
-
kInterceptors: Symbol('dispatch interceptors'),
|
|
56
55
|
kMaxResponseSize: Symbol('max response size'),
|
|
57
56
|
kHTTP2Session: Symbol('http2Session'),
|
|
58
57
|
kHTTP2SessionState: Symbol('http2Session state'),
|
package/lib/core/tree.js
CHANGED
|
@@ -47,6 +47,9 @@ class TstNode {
|
|
|
47
47
|
throw new TypeError('Unreachable')
|
|
48
48
|
}
|
|
49
49
|
let index = 0
|
|
50
|
+
/**
|
|
51
|
+
* @type {TstNode}
|
|
52
|
+
*/
|
|
50
53
|
let node = this
|
|
51
54
|
while (true) {
|
|
52
55
|
const code = key.charCodeAt(index)
|
|
@@ -87,6 +90,9 @@ class TstNode {
|
|
|
87
90
|
search (key) {
|
|
88
91
|
const keylength = key.length
|
|
89
92
|
let index = 0
|
|
93
|
+
/**
|
|
94
|
+
* @type {TstNode}
|
|
95
|
+
*/
|
|
90
96
|
let node = this
|
|
91
97
|
while (node !== null && index < keylength) {
|
|
92
98
|
let code = key[index]
|
package/lib/core/util.js
CHANGED
|
@@ -443,14 +443,6 @@ function isDisturbed (body) {
|
|
|
443
443
|
return !!(body && (stream.isDisturbed(body) || body[kBodyUsed]))
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
function isErrored (body) {
|
|
447
|
-
return !!(body && stream.isErrored(body))
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function isReadable (body) {
|
|
451
|
-
return !!(body && stream.isReadable(body))
|
|
452
|
-
}
|
|
453
|
-
|
|
454
446
|
function getSocketInfo (socket) {
|
|
455
447
|
return {
|
|
456
448
|
localAddress: socket.localAddress,
|
|
@@ -518,7 +510,7 @@ function addAbortListener (signal, listener) {
|
|
|
518
510
|
signal.addEventListener('abort', listener, { once: true })
|
|
519
511
|
return () => signal.removeEventListener('abort', listener)
|
|
520
512
|
}
|
|
521
|
-
signal.
|
|
513
|
+
signal.once('abort', listener)
|
|
522
514
|
return () => signal.removeListener('abort', listener)
|
|
523
515
|
}
|
|
524
516
|
|
|
@@ -674,8 +666,6 @@ module.exports = {
|
|
|
674
666
|
kEnumerableProperty,
|
|
675
667
|
nop,
|
|
676
668
|
isDisturbed,
|
|
677
|
-
isErrored,
|
|
678
|
-
isReadable,
|
|
679
669
|
toUSVString,
|
|
680
670
|
isUSVString,
|
|
681
671
|
isBlobLike,
|
package/lib/dispatcher/agent.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { InvalidArgumentError } = require('../core/errors')
|
|
4
|
-
const { kClients, kRunning, kClose, kDestroy, kDispatch
|
|
4
|
+
const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('../core/symbols')
|
|
5
5
|
const DispatcherBase = require('./dispatcher-base')
|
|
6
6
|
const Pool = require('./pool')
|
|
7
7
|
const Client = require('./client')
|
|
8
8
|
const util = require('../core/util')
|
|
9
|
-
const createRedirectInterceptor = require('../interceptor/redirect-interceptor')
|
|
10
9
|
|
|
11
10
|
const kOnConnect = Symbol('onConnect')
|
|
12
11
|
const kOnDisconnect = Symbol('onDisconnect')
|
|
13
12
|
const kOnConnectionError = Symbol('onConnectionError')
|
|
14
|
-
const kMaxRedirections = Symbol('maxRedirections')
|
|
15
13
|
const kOnDrain = Symbol('onDrain')
|
|
16
14
|
const kFactory = Symbol('factory')
|
|
17
15
|
const kOptions = Symbol('options')
|
|
@@ -23,9 +21,7 @@ function defaultFactory (origin, opts) {
|
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
class Agent extends DispatcherBase {
|
|
26
|
-
constructor ({ factory = defaultFactory,
|
|
27
|
-
super()
|
|
28
|
-
|
|
24
|
+
constructor ({ factory = defaultFactory, connect, ...options } = {}) {
|
|
29
25
|
if (typeof factory !== 'function') {
|
|
30
26
|
throw new InvalidArgumentError('factory must be a function.')
|
|
31
27
|
}
|
|
@@ -34,23 +30,13 @@ class Agent extends DispatcherBase {
|
|
|
34
30
|
throw new InvalidArgumentError('connect must be a function or an object')
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
39
|
-
}
|
|
33
|
+
super()
|
|
40
34
|
|
|
41
35
|
if (connect && typeof connect !== 'function') {
|
|
42
36
|
connect = { ...connect }
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent)
|
|
46
|
-
? options.interceptors.Agent
|
|
47
|
-
: [createRedirectInterceptor({ maxRedirections })]
|
|
48
|
-
|
|
49
39
|
this[kOptions] = { ...util.deepClone(options), connect }
|
|
50
|
-
this[kOptions].interceptors = options.interceptors
|
|
51
|
-
? { ...options.interceptors }
|
|
52
|
-
: undefined
|
|
53
|
-
this[kMaxRedirections] = maxRedirections
|
|
54
40
|
this[kFactory] = factory
|
|
55
41
|
this[kClients] = new Map()
|
|
56
42
|
|