undici 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/api/Dispatcher.md +1 -0
- package/docs/api/Util.md +25 -0
- package/index.js +4 -0
- package/lib/agent.js +2 -0
- package/lib/api/readable.js +87 -70
- package/lib/client.js +24 -13
- package/lib/core/connect.js +1 -1
- package/lib/core/constants.js +2 -0
- package/lib/core/errors.js +10 -20
- package/lib/core/request.js +23 -49
- package/lib/core/tree.js +134 -0
- package/lib/core/util.js +86 -33
- package/lib/fetch/body.js +1 -1
- package/lib/fetch/dataURL.js +49 -44
- package/lib/fetch/headers.js +45 -27
- package/lib/fetch/index.js +96 -82
- package/lib/fetch/response.js +3 -3
- package/lib/fetch/util.js +51 -62
- package/package.json +2 -1
- package/types/dispatcher.d.ts +2 -0
- package/types/index.d.ts +1 -0
- package/types/util.d.ts +31 -0
package/docs/api/Dispatcher.md
CHANGED
|
@@ -209,6 +209,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
|
|
|
209
209
|
* **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
|
|
210
210
|
* **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
|
|
211
211
|
* **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
|
|
212
|
+
* **onResponseStarted** `() => void` (optional) - Invoked when response is received, before headers have been read.
|
|
212
213
|
* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
|
|
213
214
|
* **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests.
|
|
214
215
|
* **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
|
package/docs/api/Util.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Util
|
|
2
|
+
|
|
3
|
+
Utility API for third-party implementations of the dispatcher API.
|
|
4
|
+
|
|
5
|
+
## `parseHeaders(headers, [obj])`
|
|
6
|
+
|
|
7
|
+
Receives a header object and returns the parsed value.
|
|
8
|
+
|
|
9
|
+
Arguments:
|
|
10
|
+
|
|
11
|
+
- **headers** `Record<string, string | string[]> | (Buffer | string | (Buffer | string)[])[]` (required) - Header object.
|
|
12
|
+
|
|
13
|
+
- **obj** `Record<string, string | string[]>` (optional) - Object to specify a proxy object. The parsed value is assigned to this object. But, if **headers** is an object, it is not used.
|
|
14
|
+
|
|
15
|
+
Returns: `Record<string, string | string[]>` If **headers** is an object, it is **headers**. Otherwise, if **obj** is specified, it is equivalent to **obj**.
|
|
16
|
+
|
|
17
|
+
## `headerNameToString(value)`
|
|
18
|
+
|
|
19
|
+
Retrieves a header name and returns its lowercase value.
|
|
20
|
+
|
|
21
|
+
Arguments:
|
|
22
|
+
|
|
23
|
+
- **value** `string | Buffer` (required) - Header name.
|
|
24
|
+
|
|
25
|
+
Returns: `string`
|
package/index.js
CHANGED
|
@@ -45,6 +45,10 @@ module.exports.createRedirectInterceptor = createRedirectInterceptor
|
|
|
45
45
|
|
|
46
46
|
module.exports.buildConnector = buildConnector
|
|
47
47
|
module.exports.errors = errors
|
|
48
|
+
module.exports.util = {
|
|
49
|
+
parseHeaders: util.parseHeaders,
|
|
50
|
+
headerNameToString: util.headerNameToString
|
|
51
|
+
}
|
|
48
52
|
|
|
49
53
|
function makeDispatcher (fn) {
|
|
50
54
|
return (url, opts, handler) => {
|
package/lib/agent.js
CHANGED
|
@@ -124,6 +124,7 @@ class Agent extends DispatcherBase {
|
|
|
124
124
|
const client = ref.deref()
|
|
125
125
|
/* istanbul ignore else: gc is undeterministic */
|
|
126
126
|
if (client) {
|
|
127
|
+
this[kFinalizer].unregister(client)
|
|
127
128
|
closePromises.push(client.close())
|
|
128
129
|
}
|
|
129
130
|
}
|
|
@@ -137,6 +138,7 @@ class Agent extends DispatcherBase {
|
|
|
137
138
|
const client = ref.deref()
|
|
138
139
|
/* istanbul ignore else: gc is undeterministic */
|
|
139
140
|
if (client) {
|
|
141
|
+
this[kFinalizer].unregister(client)
|
|
140
142
|
destroyPromises.push(client.destroy(err))
|
|
141
143
|
}
|
|
142
144
|
}
|
package/lib/api/readable.js
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
const assert = require('assert')
|
|
6
6
|
const { Readable } = require('stream')
|
|
7
|
-
const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = require('../core/errors')
|
|
7
|
+
const { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require('../core/errors')
|
|
8
8
|
const util = require('../core/util')
|
|
9
|
-
const { ReadableStreamFrom
|
|
10
|
-
|
|
11
|
-
let Blob
|
|
9
|
+
const { ReadableStreamFrom } = require('../core/util')
|
|
12
10
|
|
|
13
11
|
const kConsume = Symbol('kConsume')
|
|
14
12
|
const kReading = Symbol('kReading')
|
|
@@ -46,11 +44,6 @@ module.exports = class BodyReadable extends Readable {
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
destroy (err) {
|
|
49
|
-
if (this.destroyed) {
|
|
50
|
-
// Node < 16
|
|
51
|
-
return this
|
|
52
|
-
}
|
|
53
|
-
|
|
54
47
|
if (!err && !this._readableState.endEmitted) {
|
|
55
48
|
err = new RequestAbortedError()
|
|
56
49
|
}
|
|
@@ -62,15 +55,16 @@ module.exports = class BodyReadable extends Readable {
|
|
|
62
55
|
return super.destroy(err)
|
|
63
56
|
}
|
|
64
57
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
_destroy (err, callback) {
|
|
59
|
+
// Workaround for Node "bug". If the stream is destroyed in same
|
|
60
|
+
// tick as it is created, then a user who is waiting for a
|
|
61
|
+
// promise (i.e micro tick) for installing a 'error' listener will
|
|
62
|
+
// never get a chance and will always encounter an unhandled exception.
|
|
63
|
+
// - tick => process.nextTick(fn)
|
|
64
|
+
// - micro tick => queueMicrotask(fn)
|
|
65
|
+
queueMicrotask(() => {
|
|
66
|
+
callback(err)
|
|
67
|
+
})
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
on (ev, ...args) {
|
|
@@ -151,37 +145,31 @@ module.exports = class BodyReadable extends Readable {
|
|
|
151
145
|
return this[kBody]
|
|
152
146
|
}
|
|
153
147
|
|
|
154
|
-
dump (opts) {
|
|
155
|
-
let limit =
|
|
156
|
-
const signal = opts
|
|
157
|
-
|
|
158
|
-
if (signal) {
|
|
159
|
-
|
|
160
|
-
if (typeof signal !== 'object' || !('aborted' in signal)) {
|
|
161
|
-
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
162
|
-
}
|
|
163
|
-
util.throwIfAborted(signal)
|
|
164
|
-
} catch (err) {
|
|
165
|
-
return Promise.reject(err)
|
|
166
|
-
}
|
|
148
|
+
async dump (opts) {
|
|
149
|
+
let limit = Number.isFinite(opts?.limit) ? opts.limit : 262144
|
|
150
|
+
const signal = opts?.signal
|
|
151
|
+
|
|
152
|
+
if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) {
|
|
153
|
+
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
167
154
|
}
|
|
168
155
|
|
|
169
|
-
|
|
170
|
-
|
|
156
|
+
signal?.throwIfAborted()
|
|
157
|
+
|
|
158
|
+
if (this._readableState.closeEmitted) {
|
|
159
|
+
return null
|
|
171
160
|
}
|
|
172
161
|
|
|
173
|
-
return new Promise((resolve, reject) => {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
: noop
|
|
162
|
+
return await new Promise((resolve, reject) => {
|
|
163
|
+
const onAbort = () => {
|
|
164
|
+
this.destroy(signal.reason ?? new AbortError())
|
|
165
|
+
}
|
|
166
|
+
signal?.addEventListener('abort', onAbort)
|
|
179
167
|
|
|
180
168
|
this
|
|
181
169
|
.on('close', function () {
|
|
182
|
-
|
|
183
|
-
if (signal
|
|
184
|
-
reject(signal.reason
|
|
170
|
+
signal?.removeEventListener('abort', onAbort)
|
|
171
|
+
if (signal?.aborted) {
|
|
172
|
+
reject(signal.reason ?? new AbortError())
|
|
185
173
|
} else {
|
|
186
174
|
resolve(null)
|
|
187
175
|
}
|
|
@@ -210,33 +198,44 @@ function isUnusable (self) {
|
|
|
210
198
|
}
|
|
211
199
|
|
|
212
200
|
async function consume (stream, type) {
|
|
213
|
-
if (isUnusable(stream)) {
|
|
214
|
-
throw new TypeError('unusable')
|
|
215
|
-
}
|
|
216
|
-
|
|
217
201
|
assert(!stream[kConsume])
|
|
218
202
|
|
|
219
203
|
return new Promise((resolve, reject) => {
|
|
220
|
-
stream
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
204
|
+
if (isUnusable(stream)) {
|
|
205
|
+
const rState = stream._readableState
|
|
206
|
+
if (rState.destroyed && rState.closeEmitted === false) {
|
|
207
|
+
stream
|
|
208
|
+
.on('error', err => {
|
|
209
|
+
reject(err)
|
|
210
|
+
})
|
|
211
|
+
.on('close', () => {
|
|
212
|
+
reject(new TypeError('unusable'))
|
|
213
|
+
})
|
|
214
|
+
} else {
|
|
215
|
+
reject(rState.errored ?? new TypeError('unusable'))
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
stream[kConsume] = {
|
|
219
|
+
type,
|
|
220
|
+
stream,
|
|
221
|
+
resolve,
|
|
222
|
+
reject,
|
|
223
|
+
length: 0,
|
|
224
|
+
body: []
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
stream
|
|
228
|
+
.on('error', function (err) {
|
|
229
|
+
consumeFinish(this[kConsume], err)
|
|
230
|
+
})
|
|
231
|
+
.on('close', function () {
|
|
232
|
+
if (this[kConsume].body !== null) {
|
|
233
|
+
consumeFinish(this[kConsume], new RequestAbortedError())
|
|
234
|
+
}
|
|
235
|
+
})
|
|
228
236
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
consumeFinish(this[kConsume], err)
|
|
232
|
-
})
|
|
233
|
-
.on('close', function () {
|
|
234
|
-
if (this[kConsume].body !== null) {
|
|
235
|
-
consumeFinish(this[kConsume], new RequestAbortedError())
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
process.nextTick(consumeStart, stream[kConsume])
|
|
237
|
+
queueMicrotask(() => consumeStart(stream[kConsume]))
|
|
238
|
+
}
|
|
240
239
|
})
|
|
241
240
|
}
|
|
242
241
|
|
|
@@ -266,14 +265,35 @@ function consumeStart (consume) {
|
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
267
|
|
|
268
|
+
/**
|
|
269
|
+
* @param {Buffer[]} chunks
|
|
270
|
+
* @param {number} length
|
|
271
|
+
*/
|
|
272
|
+
function chunksDecode (chunks, length) {
|
|
273
|
+
if (chunks.length === 0 || length === 0) {
|
|
274
|
+
return ''
|
|
275
|
+
}
|
|
276
|
+
const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length)
|
|
277
|
+
|
|
278
|
+
const start =
|
|
279
|
+
buffer.length >= 3 &&
|
|
280
|
+
// Skip BOM.
|
|
281
|
+
buffer[0] === 0xef &&
|
|
282
|
+
buffer[1] === 0xbb &&
|
|
283
|
+
buffer[2] === 0xbf
|
|
284
|
+
? 3
|
|
285
|
+
: 0
|
|
286
|
+
return buffer.utf8Slice(start, buffer.length - start)
|
|
287
|
+
}
|
|
288
|
+
|
|
269
289
|
function consumeEnd (consume) {
|
|
270
290
|
const { type, body, resolve, stream, length } = consume
|
|
271
291
|
|
|
272
292
|
try {
|
|
273
293
|
if (type === 'text') {
|
|
274
|
-
resolve(
|
|
294
|
+
resolve(chunksDecode(body, length))
|
|
275
295
|
} else if (type === 'json') {
|
|
276
|
-
resolve(JSON.parse(
|
|
296
|
+
resolve(JSON.parse(chunksDecode(body, length)))
|
|
277
297
|
} else if (type === 'arrayBuffer') {
|
|
278
298
|
const dst = new Uint8Array(length)
|
|
279
299
|
|
|
@@ -285,9 +305,6 @@ function consumeEnd (consume) {
|
|
|
285
305
|
|
|
286
306
|
resolve(dst.buffer)
|
|
287
307
|
} else if (type === 'blob') {
|
|
288
|
-
if (!Blob) {
|
|
289
|
-
Blob = require('buffer').Blob
|
|
290
|
-
}
|
|
291
308
|
resolve(new Blob(body, { type: stream[kContentType] }))
|
|
292
309
|
}
|
|
293
310
|
|
package/lib/client.js
CHANGED
|
@@ -740,6 +740,7 @@ class Parser {
|
|
|
740
740
|
if (!request) {
|
|
741
741
|
return -1
|
|
742
742
|
}
|
|
743
|
+
request.onResponseStarted()
|
|
743
744
|
}
|
|
744
745
|
|
|
745
746
|
onHeaderField (buf) {
|
|
@@ -765,11 +766,14 @@ class Parser {
|
|
|
765
766
|
}
|
|
766
767
|
|
|
767
768
|
const key = this.headers[len - 2]
|
|
768
|
-
if (key.length === 10
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
769
|
+
if (key.length === 10) {
|
|
770
|
+
const headerName = util.bufferToLowerCasedHeaderName(key)
|
|
771
|
+
if (headerName === 'keep-alive') {
|
|
772
|
+
this.keepAlive += buf.toString()
|
|
773
|
+
} else if (headerName === 'connection') {
|
|
774
|
+
this.connection += buf.toString()
|
|
775
|
+
}
|
|
776
|
+
} else if (key.length === 14 && util.bufferToLowerCasedHeaderName(key) === 'content-length') {
|
|
773
777
|
this.contentLength += buf.toString()
|
|
774
778
|
}
|
|
775
779
|
|
|
@@ -1783,6 +1787,7 @@ function writeH2 (client, session, request) {
|
|
|
1783
1787
|
|
|
1784
1788
|
stream.once('response', headers => {
|
|
1785
1789
|
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
|
|
1790
|
+
request.onResponseStarted()
|
|
1786
1791
|
|
|
1787
1792
|
if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
|
|
1788
1793
|
stream.pause()
|
|
@@ -1963,12 +1968,19 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
|
|
|
1963
1968
|
body.resume()
|
|
1964
1969
|
}
|
|
1965
1970
|
}
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1971
|
+
const onClose = function () {
|
|
1972
|
+
// 'close' might be emitted *before* 'error' for
|
|
1973
|
+
// broken streams. Wait a tick to avoid this case.
|
|
1974
|
+
queueMicrotask(() => {
|
|
1975
|
+
// It's only safe to remove 'error' listener after
|
|
1976
|
+
// 'close'.
|
|
1977
|
+
body.removeListener('error', onFinished)
|
|
1978
|
+
})
|
|
1979
|
+
|
|
1980
|
+
if (!finished) {
|
|
1981
|
+
const err = new RequestAbortedError()
|
|
1982
|
+
queueMicrotask(() => onFinished(err))
|
|
1969
1983
|
}
|
|
1970
|
-
const err = new RequestAbortedError()
|
|
1971
|
-
queueMicrotask(() => onFinished(err))
|
|
1972
1984
|
}
|
|
1973
1985
|
const onFinished = function (err) {
|
|
1974
1986
|
if (finished) {
|
|
@@ -1986,8 +1998,7 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
|
|
|
1986
1998
|
body
|
|
1987
1999
|
.removeListener('data', onData)
|
|
1988
2000
|
.removeListener('end', onFinished)
|
|
1989
|
-
.removeListener('
|
|
1990
|
-
.removeListener('close', onAbort)
|
|
2001
|
+
.removeListener('close', onClose)
|
|
1991
2002
|
|
|
1992
2003
|
if (!err) {
|
|
1993
2004
|
try {
|
|
@@ -2010,7 +2021,7 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
|
|
|
2010
2021
|
.on('data', onData)
|
|
2011
2022
|
.on('end', onFinished)
|
|
2012
2023
|
.on('error', onFinished)
|
|
2013
|
-
.on('close',
|
|
2024
|
+
.on('close', onClose)
|
|
2014
2025
|
|
|
2015
2026
|
if (body.resume) {
|
|
2016
2027
|
body.resume()
|
package/lib/core/connect.js
CHANGED
|
@@ -15,7 +15,7 @@ let tls // include tls conditionally since it is not always available
|
|
|
15
15
|
let SessionCache
|
|
16
16
|
// FIXME: remove workaround when the Node bug is fixed
|
|
17
17
|
// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308
|
|
18
|
-
if (global.FinalizationRegistry && !process.env.NODE_V8_COVERAGE) {
|
|
18
|
+
if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env.UNDICI_NO_FG)) {
|
|
19
19
|
SessionCache = class WeakSessionCache {
|
|
20
20
|
constructor (maxCachedSessions) {
|
|
21
21
|
this._maxCachedSessions = maxCachedSessions
|
package/lib/core/constants.js
CHANGED
package/lib/core/errors.js
CHANGED
|
@@ -11,7 +11,6 @@ class UndiciError extends Error {
|
|
|
11
11
|
class ConnectTimeoutError extends UndiciError {
|
|
12
12
|
constructor (message) {
|
|
13
13
|
super(message)
|
|
14
|
-
Error.captureStackTrace(this, ConnectTimeoutError)
|
|
15
14
|
this.name = 'ConnectTimeoutError'
|
|
16
15
|
this.message = message || 'Connect Timeout Error'
|
|
17
16
|
this.code = 'UND_ERR_CONNECT_TIMEOUT'
|
|
@@ -21,7 +20,6 @@ class ConnectTimeoutError extends UndiciError {
|
|
|
21
20
|
class HeadersTimeoutError extends UndiciError {
|
|
22
21
|
constructor (message) {
|
|
23
22
|
super(message)
|
|
24
|
-
Error.captureStackTrace(this, HeadersTimeoutError)
|
|
25
23
|
this.name = 'HeadersTimeoutError'
|
|
26
24
|
this.message = message || 'Headers Timeout Error'
|
|
27
25
|
this.code = 'UND_ERR_HEADERS_TIMEOUT'
|
|
@@ -31,7 +29,6 @@ class HeadersTimeoutError extends UndiciError {
|
|
|
31
29
|
class HeadersOverflowError extends UndiciError {
|
|
32
30
|
constructor (message) {
|
|
33
31
|
super(message)
|
|
34
|
-
Error.captureStackTrace(this, HeadersOverflowError)
|
|
35
32
|
this.name = 'HeadersOverflowError'
|
|
36
33
|
this.message = message || 'Headers Overflow Error'
|
|
37
34
|
this.code = 'UND_ERR_HEADERS_OVERFLOW'
|
|
@@ -41,7 +38,6 @@ class HeadersOverflowError extends UndiciError {
|
|
|
41
38
|
class BodyTimeoutError extends UndiciError {
|
|
42
39
|
constructor (message) {
|
|
43
40
|
super(message)
|
|
44
|
-
Error.captureStackTrace(this, BodyTimeoutError)
|
|
45
41
|
this.name = 'BodyTimeoutError'
|
|
46
42
|
this.message = message || 'Body Timeout Error'
|
|
47
43
|
this.code = 'UND_ERR_BODY_TIMEOUT'
|
|
@@ -51,7 +47,6 @@ class BodyTimeoutError extends UndiciError {
|
|
|
51
47
|
class ResponseStatusCodeError extends UndiciError {
|
|
52
48
|
constructor (message, statusCode, headers, body) {
|
|
53
49
|
super(message)
|
|
54
|
-
Error.captureStackTrace(this, ResponseStatusCodeError)
|
|
55
50
|
this.name = 'ResponseStatusCodeError'
|
|
56
51
|
this.message = message || 'Response Status Code Error'
|
|
57
52
|
this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
|
|
@@ -65,7 +60,6 @@ class ResponseStatusCodeError extends UndiciError {
|
|
|
65
60
|
class InvalidArgumentError extends UndiciError {
|
|
66
61
|
constructor (message) {
|
|
67
62
|
super(message)
|
|
68
|
-
Error.captureStackTrace(this, InvalidArgumentError)
|
|
69
63
|
this.name = 'InvalidArgumentError'
|
|
70
64
|
this.message = message || 'Invalid Argument Error'
|
|
71
65
|
this.code = 'UND_ERR_INVALID_ARG'
|
|
@@ -75,17 +69,23 @@ class InvalidArgumentError extends UndiciError {
|
|
|
75
69
|
class InvalidReturnValueError extends UndiciError {
|
|
76
70
|
constructor (message) {
|
|
77
71
|
super(message)
|
|
78
|
-
Error.captureStackTrace(this, InvalidReturnValueError)
|
|
79
72
|
this.name = 'InvalidReturnValueError'
|
|
80
73
|
this.message = message || 'Invalid Return Value Error'
|
|
81
74
|
this.code = 'UND_ERR_INVALID_RETURN_VALUE'
|
|
82
75
|
}
|
|
83
76
|
}
|
|
84
77
|
|
|
85
|
-
class
|
|
78
|
+
class AbortError extends UndiciError {
|
|
79
|
+
constructor (message) {
|
|
80
|
+
super(message)
|
|
81
|
+
this.name = 'AbortError'
|
|
82
|
+
this.message = message || 'The operation was aborted'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class RequestAbortedError extends AbortError {
|
|
86
87
|
constructor (message) {
|
|
87
88
|
super(message)
|
|
88
|
-
Error.captureStackTrace(this, RequestAbortedError)
|
|
89
89
|
this.name = 'AbortError'
|
|
90
90
|
this.message = message || 'Request aborted'
|
|
91
91
|
this.code = 'UND_ERR_ABORTED'
|
|
@@ -95,7 +95,6 @@ class RequestAbortedError extends UndiciError {
|
|
|
95
95
|
class InformationalError extends UndiciError {
|
|
96
96
|
constructor (message) {
|
|
97
97
|
super(message)
|
|
98
|
-
Error.captureStackTrace(this, InformationalError)
|
|
99
98
|
this.name = 'InformationalError'
|
|
100
99
|
this.message = message || 'Request information'
|
|
101
100
|
this.code = 'UND_ERR_INFO'
|
|
@@ -105,7 +104,6 @@ class InformationalError extends UndiciError {
|
|
|
105
104
|
class RequestContentLengthMismatchError extends UndiciError {
|
|
106
105
|
constructor (message) {
|
|
107
106
|
super(message)
|
|
108
|
-
Error.captureStackTrace(this, RequestContentLengthMismatchError)
|
|
109
107
|
this.name = 'RequestContentLengthMismatchError'
|
|
110
108
|
this.message = message || 'Request body length does not match content-length header'
|
|
111
109
|
this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
|
|
@@ -115,7 +113,6 @@ class RequestContentLengthMismatchError extends UndiciError {
|
|
|
115
113
|
class ResponseContentLengthMismatchError extends UndiciError {
|
|
116
114
|
constructor (message) {
|
|
117
115
|
super(message)
|
|
118
|
-
Error.captureStackTrace(this, ResponseContentLengthMismatchError)
|
|
119
116
|
this.name = 'ResponseContentLengthMismatchError'
|
|
120
117
|
this.message = message || 'Response body length does not match content-length header'
|
|
121
118
|
this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
|
|
@@ -125,7 +122,6 @@ class ResponseContentLengthMismatchError extends UndiciError {
|
|
|
125
122
|
class ClientDestroyedError extends UndiciError {
|
|
126
123
|
constructor (message) {
|
|
127
124
|
super(message)
|
|
128
|
-
Error.captureStackTrace(this, ClientDestroyedError)
|
|
129
125
|
this.name = 'ClientDestroyedError'
|
|
130
126
|
this.message = message || 'The client is destroyed'
|
|
131
127
|
this.code = 'UND_ERR_DESTROYED'
|
|
@@ -135,7 +131,6 @@ class ClientDestroyedError extends UndiciError {
|
|
|
135
131
|
class ClientClosedError extends UndiciError {
|
|
136
132
|
constructor (message) {
|
|
137
133
|
super(message)
|
|
138
|
-
Error.captureStackTrace(this, ClientClosedError)
|
|
139
134
|
this.name = 'ClientClosedError'
|
|
140
135
|
this.message = message || 'The client is closed'
|
|
141
136
|
this.code = 'UND_ERR_CLOSED'
|
|
@@ -145,7 +140,6 @@ class ClientClosedError extends UndiciError {
|
|
|
145
140
|
class SocketError extends UndiciError {
|
|
146
141
|
constructor (message, socket) {
|
|
147
142
|
super(message)
|
|
148
|
-
Error.captureStackTrace(this, SocketError)
|
|
149
143
|
this.name = 'SocketError'
|
|
150
144
|
this.message = message || 'Socket error'
|
|
151
145
|
this.code = 'UND_ERR_SOCKET'
|
|
@@ -156,7 +150,6 @@ class SocketError extends UndiciError {
|
|
|
156
150
|
class NotSupportedError extends UndiciError {
|
|
157
151
|
constructor (message) {
|
|
158
152
|
super(message)
|
|
159
|
-
Error.captureStackTrace(this, NotSupportedError)
|
|
160
153
|
this.name = 'NotSupportedError'
|
|
161
154
|
this.message = message || 'Not supported error'
|
|
162
155
|
this.code = 'UND_ERR_NOT_SUPPORTED'
|
|
@@ -166,7 +159,6 @@ class NotSupportedError extends UndiciError {
|
|
|
166
159
|
class BalancedPoolMissingUpstreamError extends UndiciError {
|
|
167
160
|
constructor (message) {
|
|
168
161
|
super(message)
|
|
169
|
-
Error.captureStackTrace(this, NotSupportedError)
|
|
170
162
|
this.name = 'MissingUpstreamError'
|
|
171
163
|
this.message = message || 'No upstream has been added to the BalancedPool'
|
|
172
164
|
this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
|
|
@@ -176,7 +168,6 @@ class BalancedPoolMissingUpstreamError extends UndiciError {
|
|
|
176
168
|
class HTTPParserError extends Error {
|
|
177
169
|
constructor (message, code, data) {
|
|
178
170
|
super(message)
|
|
179
|
-
Error.captureStackTrace(this, HTTPParserError)
|
|
180
171
|
this.name = 'HTTPParserError'
|
|
181
172
|
this.code = code ? `HPE_${code}` : undefined
|
|
182
173
|
this.data = data ? data.toString() : undefined
|
|
@@ -186,7 +177,6 @@ class HTTPParserError extends Error {
|
|
|
186
177
|
class ResponseExceededMaxSizeError extends UndiciError {
|
|
187
178
|
constructor (message) {
|
|
188
179
|
super(message)
|
|
189
|
-
Error.captureStackTrace(this, ResponseExceededMaxSizeError)
|
|
190
180
|
this.name = 'ResponseExceededMaxSizeError'
|
|
191
181
|
this.message = message || 'Response content exceeded max size'
|
|
192
182
|
this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
|
|
@@ -196,7 +186,6 @@ class ResponseExceededMaxSizeError extends UndiciError {
|
|
|
196
186
|
class RequestRetryError extends UndiciError {
|
|
197
187
|
constructor (message, code, { headers, data }) {
|
|
198
188
|
super(message)
|
|
199
|
-
Error.captureStackTrace(this, RequestRetryError)
|
|
200
189
|
this.name = 'RequestRetryError'
|
|
201
190
|
this.message = message || 'Request retry error'
|
|
202
191
|
this.code = 'UND_ERR_REQ_RETRY'
|
|
@@ -207,6 +196,7 @@ class RequestRetryError extends UndiciError {
|
|
|
207
196
|
}
|
|
208
197
|
|
|
209
198
|
module.exports = {
|
|
199
|
+
AbortError,
|
|
210
200
|
HTTPParserError,
|
|
211
201
|
UndiciError,
|
|
212
202
|
HeadersTimeoutError,
|
package/lib/core/request.js
CHANGED
|
@@ -7,17 +7,11 @@ const {
|
|
|
7
7
|
const assert = require('assert')
|
|
8
8
|
const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = require('./symbols')
|
|
9
9
|
const util = require('./util')
|
|
10
|
+
const { headerNameLowerCasedRecord } = require('./constants')
|
|
10
11
|
|
|
11
|
-
//
|
|
12
|
+
// headerCharRegex have been lifted from
|
|
12
13
|
// https://github.com/nodejs/node/blob/main/lib/_http_common.js
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
* Verifies that the given val is a valid HTTP token
|
|
16
|
-
* per the rules defined in RFC 7230
|
|
17
|
-
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
18
|
-
*/
|
|
19
|
-
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
|
|
20
|
-
|
|
21
15
|
/**
|
|
22
16
|
* Matches if val contains an invalid field-vchar
|
|
23
17
|
* field-value = *( field-content / obs-fold )
|
|
@@ -80,7 +74,7 @@ class Request {
|
|
|
80
74
|
|
|
81
75
|
if (typeof method !== 'string') {
|
|
82
76
|
throw new InvalidArgumentError('method must be a string')
|
|
83
|
-
} else if (
|
|
77
|
+
} else if (!util.isValidHTTPToken(method)) {
|
|
84
78
|
throw new InvalidArgumentError('invalid request method')
|
|
85
79
|
}
|
|
86
80
|
|
|
@@ -259,6 +253,10 @@ class Request {
|
|
|
259
253
|
}
|
|
260
254
|
}
|
|
261
255
|
|
|
256
|
+
onResponseStarted () {
|
|
257
|
+
return this[kHandler].onResponseStarted?.()
|
|
258
|
+
}
|
|
259
|
+
|
|
262
260
|
onHeaders (statusCode, headers, resume, statusText) {
|
|
263
261
|
assert(!this.aborted)
|
|
264
262
|
assert(!this.completed)
|
|
@@ -416,65 +414,41 @@ function processHeader (request, key, val, skipAppend = false) {
|
|
|
416
414
|
return
|
|
417
415
|
}
|
|
418
416
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
key.toLowerCase()
|
|
423
|
-
|
|
417
|
+
let headerName = headerNameLowerCasedRecord[key]
|
|
418
|
+
|
|
419
|
+
if (headerName === undefined) {
|
|
420
|
+
headerName = key.toLowerCase()
|
|
421
|
+
if (headerNameLowerCasedRecord[headerName] === undefined && !util.isValidHTTPToken(headerName)) {
|
|
422
|
+
throw new InvalidArgumentError('invalid header key')
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (request.host === null && headerName === 'host') {
|
|
424
427
|
if (headerCharRegex.exec(val) !== null) {
|
|
425
428
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
426
429
|
}
|
|
427
430
|
// Consumed by Client
|
|
428
431
|
request.host = val
|
|
429
|
-
} else if (
|
|
430
|
-
request.contentLength === null &&
|
|
431
|
-
key.length === 14 &&
|
|
432
|
-
key.toLowerCase() === 'content-length'
|
|
433
|
-
) {
|
|
432
|
+
} else if (request.contentLength === null && headerName === 'content-length') {
|
|
434
433
|
request.contentLength = parseInt(val, 10)
|
|
435
434
|
if (!Number.isFinite(request.contentLength)) {
|
|
436
435
|
throw new InvalidArgumentError('invalid content-length header')
|
|
437
436
|
}
|
|
438
|
-
} else if (
|
|
439
|
-
request.contentType === null &&
|
|
440
|
-
key.length === 12 &&
|
|
441
|
-
key.toLowerCase() === 'content-type'
|
|
442
|
-
) {
|
|
437
|
+
} else if (request.contentType === null && headerName === 'content-type') {
|
|
443
438
|
request.contentType = val
|
|
444
439
|
if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend)
|
|
445
440
|
else request.headers += processHeaderValue(key, val)
|
|
446
|
-
} else if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
) {
|
|
450
|
-
throw new InvalidArgumentError('invalid transfer-encoding header')
|
|
451
|
-
} else if (
|
|
452
|
-
key.length === 10 &&
|
|
453
|
-
key.toLowerCase() === 'connection'
|
|
454
|
-
) {
|
|
441
|
+
} else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
|
|
442
|
+
throw new InvalidArgumentError(`invalid ${headerName} header`)
|
|
443
|
+
} else if (headerName === 'connection') {
|
|
455
444
|
const value = typeof val === 'string' ? val.toLowerCase() : null
|
|
456
445
|
if (value !== 'close' && value !== 'keep-alive') {
|
|
457
446
|
throw new InvalidArgumentError('invalid connection header')
|
|
458
447
|
} else if (value === 'close') {
|
|
459
448
|
request.reset = true
|
|
460
449
|
}
|
|
461
|
-
} else if (
|
|
462
|
-
key.length === 10 &&
|
|
463
|
-
key.toLowerCase() === 'keep-alive'
|
|
464
|
-
) {
|
|
465
|
-
throw new InvalidArgumentError('invalid keep-alive header')
|
|
466
|
-
} else if (
|
|
467
|
-
key.length === 7 &&
|
|
468
|
-
key.toLowerCase() === 'upgrade'
|
|
469
|
-
) {
|
|
470
|
-
throw new InvalidArgumentError('invalid upgrade header')
|
|
471
|
-
} else if (
|
|
472
|
-
key.length === 6 &&
|
|
473
|
-
key.toLowerCase() === 'expect'
|
|
474
|
-
) {
|
|
450
|
+
} else if (headerName === 'expect') {
|
|
475
451
|
throw new NotSupportedError('expect header not supported')
|
|
476
|
-
} else if (tokenRegExp.exec(key) === null) {
|
|
477
|
-
throw new InvalidArgumentError('invalid header key')
|
|
478
452
|
} else {
|
|
479
453
|
if (Array.isArray(val)) {
|
|
480
454
|
for (let i = 0; i < val.length; i++) {
|