undici 5.19.1 → 5.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/docs/api/Client.md +4 -2
- package/docs/api/Dispatcher.md +7 -7
- package/docs/api/ProxyAgent.md +3 -1
- package/index.js +3 -7
- package/lib/api/api-stream.js +33 -5
- package/lib/api/readable.js +18 -2
- package/lib/client.js +27 -3
- package/lib/cookies/parse.js +8 -8
- package/lib/core/request.js +1 -5
- package/lib/core/util.js +39 -16
- package/lib/fetch/dataURL.js +12 -2
- package/lib/fetch/headers.js +1 -0
- package/lib/fetch/index.js +2 -6
- package/lib/fetch/request.js +16 -8
- package/lib/fetch/util.js +98 -93
- package/lib/fileapi/encoding.js +5 -1
- package/lib/pool.js +3 -0
- package/lib/proxy-agent.js +12 -2
- package/lib/timers.js +21 -13
- package/lib/websocket/connection.js +12 -61
- package/lib/websocket/symbols.js +0 -3
- package/lib/websocket/websocket.js +55 -9
- package/package.json +5 -5
- package/types/balanced-pool.d.ts +3 -3
- package/types/client.d.ts +52 -26
- package/types/dispatcher.d.ts +2 -2
- package/types/header.d.ts +1 -1
- package/types/proxy-agent.d.ts +3 -0
- package/types/websocket.d.ts +1 -0
package/README.md
CHANGED
|
@@ -405,6 +405,18 @@ implementations in Deno and Cloudflare Workers.
|
|
|
405
405
|
|
|
406
406
|
Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
|
|
407
407
|
|
|
408
|
+
## Workarounds
|
|
409
|
+
|
|
410
|
+
### Network address family autoselection.
|
|
411
|
+
|
|
412
|
+
If you experience problem when connecting to a remote server that is resolved by your DNS servers to a IPv6 (AAAA record)
|
|
413
|
+
first, there are chances that your local router or ISP might have problem connecting to IPv6 networks. In that case
|
|
414
|
+
undici will throw an error with code `UND_ERR_CONNECT_TIMEOUT`.
|
|
415
|
+
|
|
416
|
+
If the target server resolves to both a IPv6 and IPv4 (A records) address and you are using a compatible Node version
|
|
417
|
+
(18.3.0 and above), you can fix the problem by providing the `autoSelectFamily` option (support by both `undici.request`
|
|
418
|
+
and `undici.Agent`) which will enable the family autoselection algorithm when establishing the connection.
|
|
419
|
+
|
|
408
420
|
## Collaborators
|
|
409
421
|
|
|
410
422
|
* [__Daniele Belardi__](https://github.com/dnlup), <https://www.npmjs.com/~dnlup>
|
package/docs/api/Client.md
CHANGED
|
@@ -17,8 +17,8 @@ Returns: `Client`
|
|
|
17
17
|
|
|
18
18
|
### Parameter: `ClientOptions`
|
|
19
19
|
|
|
20
|
-
* **bodyTimeout** `number | null` (optional) - Default: `
|
|
21
|
-
* **headersTimeout** `number | null` (optional) - Default: `
|
|
20
|
+
* **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
|
|
21
|
+
* **headersTimeout** `number | null` (optional) - Default: `300e3` - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
|
|
22
22
|
* **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout` when overridden by *keep-alive* hints from the server. Defaults to 10 minutes.
|
|
23
23
|
* **keepAliveTimeout** `number | null` (optional) - Default: `4e3` - The timeout after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overridden by *keep-alive* hints from the server. See [MDN: HTTP - Headers - Keep-Alive directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive#directives) for more details. Defaults to 4 seconds.
|
|
24
24
|
* **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `1e3` - A number subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 1 second.
|
|
@@ -28,6 +28,8 @@ Returns: `Client`
|
|
|
28
28
|
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
|
|
29
29
|
* **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body.
|
|
30
30
|
* **interceptors** `{ Client: DispatchInterceptor[] }` - Default: `[RedirectInterceptor]` - A list of interceptors that are applied to the dispatch method. Additional logic can be applied (such as, but not limited to: 302 status code handling, authentication, cookies, compression and caching). Note that the behavior of interceptors is Experimental and might change at any given time.
|
|
31
|
+
* **autoSelectFamily**: `boolean` (optional) - Default: depends on local Node version, on Node 18.13.0 and above is `false`. Enables a family autodetection algorithm that loosely implements section 5 of [RFC 8305](https://tools.ietf.org/html/rfc8305#section-5). See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. This option is ignored if not supported by the current Node version.
|
|
32
|
+
* **autoSelectFamilyAttemptTimeout**: `number` - Default: depends on local Node version, on Node 18.13.0 and above is `250`. The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details.
|
|
31
33
|
|
|
32
34
|
#### Parameter: `ConnectOptions`
|
|
33
35
|
|
package/docs/api/Dispatcher.md
CHANGED
|
@@ -74,7 +74,7 @@ Returns: `void | Promise<ConnectData>` - Only returns a `Promise` if no `callbac
|
|
|
74
74
|
#### Parameter: `ConnectData`
|
|
75
75
|
|
|
76
76
|
* **statusCode** `number`
|
|
77
|
-
* **headers** `Record<string, string | string[]>`
|
|
77
|
+
* **headers** `Record<string, string | string[] | undefined>`
|
|
78
78
|
* **socket** `stream.Duplex`
|
|
79
79
|
* **opaque** `unknown`
|
|
80
80
|
|
|
@@ -199,8 +199,8 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
|
|
|
199
199
|
* **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
|
|
200
200
|
* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
|
|
201
201
|
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
|
|
202
|
-
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to
|
|
203
|
-
* **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to
|
|
202
|
+
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
|
|
203
|
+
* **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
|
|
204
204
|
* **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server.
|
|
205
205
|
|
|
206
206
|
#### Parameter: `DispatchHandler`
|
|
@@ -383,7 +383,7 @@ Extends: [`RequestOptions`](#parameter-requestoptions)
|
|
|
383
383
|
#### Parameter: PipelineHandlerData
|
|
384
384
|
|
|
385
385
|
* **statusCode** `number`
|
|
386
|
-
* **headers** `Record<string, string | string[]>`
|
|
386
|
+
* **headers** `Record<string, string | string[] | undefined>`
|
|
387
387
|
* **opaque** `unknown`
|
|
388
388
|
* **body** `stream.Readable`
|
|
389
389
|
* **context** `object`
|
|
@@ -644,7 +644,7 @@ Returns: `void | Promise<StreamData>` - Only returns a `Promise` if no `callback
|
|
|
644
644
|
#### Parameter: `StreamFactoryData`
|
|
645
645
|
|
|
646
646
|
* **statusCode** `number`
|
|
647
|
-
* **headers** `Record<string, string | string[]>`
|
|
647
|
+
* **headers** `Record<string, string | string[] | undefined>`
|
|
648
648
|
* **opaque** `unknown`
|
|
649
649
|
* **onInfo** `({statusCode: number, headers: Record<string, string | string[]>}) => void | null` (optional) - Default: `null` - Callback collecting all the info headers (HTTP 100-199) received.
|
|
650
650
|
|
|
@@ -853,9 +853,9 @@ Emitted when dispatcher is no longer busy.
|
|
|
853
853
|
|
|
854
854
|
## Parameter: `UndiciHeaders`
|
|
855
855
|
|
|
856
|
-
* `Record<string, string | string[]> | string[] | null`
|
|
856
|
+
* `Record<string, string | string[] | undefined> | string[] | null`
|
|
857
857
|
|
|
858
|
-
Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in two forms; either as an object specified by the `Record<string, string | string[]>` (`IncomingHttpHeaders`) type, or an array of strings. An array representation of a header list must have an even length or an `InvalidArgumentError` will be thrown.
|
|
858
|
+
Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in two forms; either as an object specified by the `Record<string, string | string[] | undefined>` (`IncomingHttpHeaders`) type, or an array of strings. An array representation of a header list must have an even length or an `InvalidArgumentError` will be thrown.
|
|
859
859
|
|
|
860
860
|
Keys are lowercase and values are not modified.
|
|
861
861
|
|
package/docs/api/ProxyAgent.md
CHANGED
|
@@ -19,6 +19,7 @@ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
|
|
|
19
19
|
* **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.
|
|
20
20
|
* **token** `string` (optional) - It can be passed by a string of token for authentication.
|
|
21
21
|
* **auth** `string` (**deprecated**) - Use token.
|
|
22
|
+
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
22
23
|
|
|
23
24
|
Examples:
|
|
24
25
|
|
|
@@ -83,7 +84,8 @@ import { setGlobalDispatcher, request, ProxyAgent } from 'undici';
|
|
|
83
84
|
|
|
84
85
|
const proxyAgent = new ProxyAgent({
|
|
85
86
|
uri: 'my.proxy.server',
|
|
86
|
-
token: 'Bearer xxxx'
|
|
87
|
+
// token: 'Bearer xxxx'
|
|
88
|
+
token: `Basic ${Buffer.from('username:password').toString('base64')}`
|
|
87
89
|
});
|
|
88
90
|
setGlobalDispatcher(proxyAgent);
|
|
89
91
|
|
package/index.js
CHANGED
|
@@ -20,10 +20,6 @@ const DecoratorHandler = require('./lib/handler/DecoratorHandler')
|
|
|
20
20
|
const RedirectHandler = require('./lib/handler/RedirectHandler')
|
|
21
21
|
const createRedirectInterceptor = require('./lib/interceptor/redirectInterceptor')
|
|
22
22
|
|
|
23
|
-
const nodeVersion = process.versions.node.split('.')
|
|
24
|
-
const nodeMajor = Number(nodeVersion[0])
|
|
25
|
-
const nodeMinor = Number(nodeVersion[1])
|
|
26
|
-
|
|
27
23
|
let hasCrypto
|
|
28
24
|
try {
|
|
29
25
|
require('crypto')
|
|
@@ -100,7 +96,7 @@ function makeDispatcher (fn) {
|
|
|
100
96
|
module.exports.setGlobalDispatcher = setGlobalDispatcher
|
|
101
97
|
module.exports.getGlobalDispatcher = getGlobalDispatcher
|
|
102
98
|
|
|
103
|
-
if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 8)) {
|
|
99
|
+
if (util.nodeMajor > 16 || (util.nodeMajor === 16 && util.nodeMinor >= 8)) {
|
|
104
100
|
let fetchImpl = null
|
|
105
101
|
module.exports.fetch = async function fetch (resource) {
|
|
106
102
|
if (!fetchImpl) {
|
|
@@ -127,7 +123,7 @@ if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 8)) {
|
|
|
127
123
|
module.exports.getGlobalOrigin = getGlobalOrigin
|
|
128
124
|
}
|
|
129
125
|
|
|
130
|
-
if (nodeMajor >= 16) {
|
|
126
|
+
if (util.nodeMajor >= 16) {
|
|
131
127
|
const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/cookies')
|
|
132
128
|
|
|
133
129
|
module.exports.deleteCookie = deleteCookie
|
|
@@ -141,7 +137,7 @@ if (nodeMajor >= 16) {
|
|
|
141
137
|
module.exports.serializeAMimeType = serializeAMimeType
|
|
142
138
|
}
|
|
143
139
|
|
|
144
|
-
if (nodeMajor >= 18 && hasCrypto) {
|
|
140
|
+
if (util.nodeMajor >= 18 && hasCrypto) {
|
|
145
141
|
const { WebSocket } = require('./lib/websocket/websocket')
|
|
146
142
|
|
|
147
143
|
module.exports.WebSocket = WebSocket
|
package/lib/api/api-stream.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { finished } = require('stream')
|
|
3
|
+
const { finished, PassThrough } = require('stream')
|
|
4
4
|
const {
|
|
5
5
|
InvalidArgumentError,
|
|
6
6
|
InvalidReturnValueError,
|
|
7
|
-
RequestAbortedError
|
|
7
|
+
RequestAbortedError,
|
|
8
|
+
ResponseStatusCodeError
|
|
8
9
|
} = require('../core/errors')
|
|
9
10
|
const util = require('../core/util')
|
|
10
11
|
const { AsyncResource } = require('async_hooks')
|
|
@@ -16,7 +17,7 @@ class StreamHandler extends AsyncResource {
|
|
|
16
17
|
throw new InvalidArgumentError('invalid opts')
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
20
|
+
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
23
|
if (typeof callback !== 'function') {
|
|
@@ -57,6 +58,7 @@ class StreamHandler extends AsyncResource {
|
|
|
57
58
|
this.trailers = null
|
|
58
59
|
this.body = body
|
|
59
60
|
this.onInfo = onInfo || null
|
|
61
|
+
this.throwOnError = throwOnError || false
|
|
60
62
|
|
|
61
63
|
if (util.isStream(body)) {
|
|
62
64
|
body.on('error', (err) => {
|
|
@@ -76,8 +78,8 @@ class StreamHandler extends AsyncResource {
|
|
|
76
78
|
this.context = context
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
onHeaders (statusCode, rawHeaders, resume) {
|
|
80
|
-
const { factory, opaque, context } = this
|
|
81
|
+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
82
|
+
const { factory, opaque, context, callback } = this
|
|
81
83
|
|
|
82
84
|
if (statusCode < 200) {
|
|
83
85
|
if (this.onInfo) {
|
|
@@ -96,6 +98,32 @@ class StreamHandler extends AsyncResource {
|
|
|
96
98
|
context
|
|
97
99
|
})
|
|
98
100
|
|
|
101
|
+
if (this.throwOnError && statusCode >= 400) {
|
|
102
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
103
|
+
const chunks = []
|
|
104
|
+
const pt = new PassThrough()
|
|
105
|
+
pt
|
|
106
|
+
.on('data', (chunk) => chunks.push(chunk))
|
|
107
|
+
.on('end', () => {
|
|
108
|
+
const payload = Buffer.concat(chunks).toString('utf8')
|
|
109
|
+
this.runInAsyncScope(
|
|
110
|
+
callback,
|
|
111
|
+
null,
|
|
112
|
+
new ResponseStatusCodeError(
|
|
113
|
+
`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`,
|
|
114
|
+
statusCode,
|
|
115
|
+
headers,
|
|
116
|
+
payload
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
.on('error', (err) => {
|
|
121
|
+
this.onError(err)
|
|
122
|
+
})
|
|
123
|
+
this.res = pt
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
99
127
|
if (
|
|
100
128
|
!res ||
|
|
101
129
|
typeof res.write !== 'function' ||
|
package/lib/api/readable.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const assert = require('assert')
|
|
6
6
|
const { Readable } = require('stream')
|
|
7
|
-
const { RequestAbortedError, NotSupportedError } = require('../core/errors')
|
|
7
|
+
const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = require('../core/errors')
|
|
8
8
|
const util = require('../core/util')
|
|
9
9
|
const { ReadableStreamFrom, toUSVString } = require('../core/util')
|
|
10
10
|
|
|
@@ -146,15 +146,31 @@ module.exports = class BodyReadable extends Readable {
|
|
|
146
146
|
|
|
147
147
|
async dump (opts) {
|
|
148
148
|
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
|
|
149
|
+
const signal = opts && opts.signal
|
|
150
|
+
const abortFn = () => {
|
|
151
|
+
this.destroy()
|
|
152
|
+
}
|
|
153
|
+
if (signal) {
|
|
154
|
+
if (typeof signal !== 'object' || !('aborted' in signal)) {
|
|
155
|
+
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
156
|
+
}
|
|
157
|
+
util.throwIfAborted(signal)
|
|
158
|
+
signal.addEventListener('abort', abortFn, { once: true })
|
|
159
|
+
}
|
|
149
160
|
try {
|
|
150
161
|
for await (const chunk of this) {
|
|
162
|
+
util.throwIfAborted(signal)
|
|
151
163
|
limit -= Buffer.byteLength(chunk)
|
|
152
164
|
if (limit < 0) {
|
|
153
165
|
return
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
} catch {
|
|
157
|
-
|
|
169
|
+
util.throwIfAborted(signal)
|
|
170
|
+
} finally {
|
|
171
|
+
if (signal) {
|
|
172
|
+
signal.removeEventListener('abort', abortFn)
|
|
173
|
+
}
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
176
|
}
|
package/lib/client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict'
|
|
2
4
|
|
|
3
5
|
/* global WebAssembly */
|
|
@@ -85,7 +87,15 @@ try {
|
|
|
85
87
|
channels.connected = { hasSubscribers: false }
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @type {import('../types/client').default}
|
|
92
|
+
*/
|
|
88
93
|
class Client extends DispatcherBase {
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param {string|URL} url
|
|
97
|
+
* @param {import('../types/client').Client.Options} options
|
|
98
|
+
*/
|
|
89
99
|
constructor (url, {
|
|
90
100
|
interceptors,
|
|
91
101
|
maxHeaderSize,
|
|
@@ -109,7 +119,9 @@ class Client extends DispatcherBase {
|
|
|
109
119
|
connect,
|
|
110
120
|
maxRequestsPerClient,
|
|
111
121
|
localAddress,
|
|
112
|
-
maxResponseSize
|
|
122
|
+
maxResponseSize,
|
|
123
|
+
autoSelectFamily,
|
|
124
|
+
autoSelectFamilyAttemptTimeout
|
|
113
125
|
} = {}) {
|
|
114
126
|
super()
|
|
115
127
|
|
|
@@ -185,12 +197,20 @@ class Client extends DispatcherBase {
|
|
|
185
197
|
throw new InvalidArgumentError('maxResponseSize must be a positive number')
|
|
186
198
|
}
|
|
187
199
|
|
|
200
|
+
if (
|
|
201
|
+
autoSelectFamilyAttemptTimeout != null &&
|
|
202
|
+
(!Number.isInteger(autoSelectFamilyAttemptTimeout) || autoSelectFamilyAttemptTimeout < -1)
|
|
203
|
+
) {
|
|
204
|
+
throw new InvalidArgumentError('autoSelectFamilyAttemptTimeout must be a positive number')
|
|
205
|
+
}
|
|
206
|
+
|
|
188
207
|
if (typeof connect !== 'function') {
|
|
189
208
|
connect = buildConnector({
|
|
190
209
|
...tls,
|
|
191
210
|
maxCachedSessions,
|
|
192
211
|
socketPath,
|
|
193
212
|
timeout: connectTimeout,
|
|
213
|
+
...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
|
|
194
214
|
...connect
|
|
195
215
|
})
|
|
196
216
|
}
|
|
@@ -212,8 +232,8 @@ class Client extends DispatcherBase {
|
|
|
212
232
|
this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming
|
|
213
233
|
this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming
|
|
214
234
|
this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n`
|
|
215
|
-
this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout :
|
|
216
|
-
this[kHeadersTimeout] = headersTimeout != null ? headersTimeout :
|
|
235
|
+
this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3
|
|
236
|
+
this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3
|
|
217
237
|
this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
|
|
218
238
|
this[kMaxRedirections] = maxRedirections
|
|
219
239
|
this[kMaxRequests] = maxRequestsPerClient
|
|
@@ -1648,6 +1668,8 @@ class AsyncWriter {
|
|
|
1648
1668
|
process.emitWarning(new RequestContentLengthMismatchError())
|
|
1649
1669
|
}
|
|
1650
1670
|
|
|
1671
|
+
socket.cork()
|
|
1672
|
+
|
|
1651
1673
|
if (bytesWritten === 0) {
|
|
1652
1674
|
if (!expectsPayload) {
|
|
1653
1675
|
socket[kReset] = true
|
|
@@ -1668,6 +1690,8 @@ class AsyncWriter {
|
|
|
1668
1690
|
|
|
1669
1691
|
const ret = socket.write(chunk)
|
|
1670
1692
|
|
|
1693
|
+
socket.uncork()
|
|
1694
|
+
|
|
1671
1695
|
request.onBodySent(chunk)
|
|
1672
1696
|
|
|
1673
1697
|
if (!ret) {
|
package/lib/cookies/parse.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
|
|
4
4
|
const { isCTLExcludingHtab } = require('./util')
|
|
5
|
-
const {
|
|
5
|
+
const { collectASequenceOfCodePointsFast } = require('../fetch/dataURL')
|
|
6
6
|
const assert = require('assert')
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -32,7 +32,7 @@ function parseSetCookie (header) {
|
|
|
32
32
|
// (including the %x3B (";") in question).
|
|
33
33
|
const position = { position: 0 }
|
|
34
34
|
|
|
35
|
-
nameValuePair =
|
|
35
|
+
nameValuePair = collectASequenceOfCodePointsFast(';', header, position)
|
|
36
36
|
unparsedAttributes = header.slice(position.position)
|
|
37
37
|
} else {
|
|
38
38
|
// Otherwise:
|
|
@@ -54,8 +54,8 @@ function parseSetCookie (header) {
|
|
|
54
54
|
// empty) value string consists of the characters after the first
|
|
55
55
|
// %x3D ("=") character.
|
|
56
56
|
const position = { position: 0 }
|
|
57
|
-
name =
|
|
58
|
-
|
|
57
|
+
name = collectASequenceOfCodePointsFast(
|
|
58
|
+
'=',
|
|
59
59
|
nameValuePair,
|
|
60
60
|
position
|
|
61
61
|
)
|
|
@@ -106,8 +106,8 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
|
|
106
106
|
if (unparsedAttributes.includes(';')) {
|
|
107
107
|
// 1. Consume the characters of the unparsed-attributes up to, but
|
|
108
108
|
// not including, the first %x3B (";") character.
|
|
109
|
-
cookieAv =
|
|
110
|
-
|
|
109
|
+
cookieAv = collectASequenceOfCodePointsFast(
|
|
110
|
+
';',
|
|
111
111
|
unparsedAttributes,
|
|
112
112
|
{ position: 0 }
|
|
113
113
|
)
|
|
@@ -134,8 +134,8 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
|
|
134
134
|
// character.
|
|
135
135
|
const position = { position: 0 }
|
|
136
136
|
|
|
137
|
-
attributeName =
|
|
138
|
-
|
|
137
|
+
attributeName = collectASequenceOfCodePointsFast(
|
|
138
|
+
'=',
|
|
139
139
|
cookieAv,
|
|
140
140
|
position
|
|
141
141
|
)
|
package/lib/core/request.js
CHANGED
|
@@ -34,10 +34,6 @@ const channels = {}
|
|
|
34
34
|
|
|
35
35
|
let extractBody
|
|
36
36
|
|
|
37
|
-
const nodeVersion = process.versions.node.split('.')
|
|
38
|
-
const nodeMajor = Number(nodeVersion[0])
|
|
39
|
-
const nodeMinor = Number(nodeVersion[1])
|
|
40
|
-
|
|
41
37
|
try {
|
|
42
38
|
const diagnosticsChannel = require('diagnostics_channel')
|
|
43
39
|
channels.create = diagnosticsChannel.channel('undici:request:create')
|
|
@@ -172,7 +168,7 @@ class Request {
|
|
|
172
168
|
}
|
|
173
169
|
|
|
174
170
|
if (util.isFormDataLike(this.body)) {
|
|
175
|
-
if (nodeMajor < 16 || (nodeMajor === 16 && nodeMinor < 8)) {
|
|
171
|
+
if (util.nodeMajor < 16 || (util.nodeMajor === 16 && util.nodeMinor < 8)) {
|
|
176
172
|
throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.')
|
|
177
173
|
}
|
|
178
174
|
|
package/lib/core/util.js
CHANGED
|
@@ -10,10 +10,12 @@ const { Blob } = require('buffer')
|
|
|
10
10
|
const nodeUtil = require('util')
|
|
11
11
|
const { stringify } = require('querystring')
|
|
12
12
|
|
|
13
|
+
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
|
|
14
|
+
|
|
13
15
|
function nop () {}
|
|
14
16
|
|
|
15
17
|
function isStream (obj) {
|
|
16
|
-
return obj && typeof obj.pipe === 'function'
|
|
18
|
+
return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
|
|
@@ -44,6 +46,12 @@ function buildURL (url, queryParams) {
|
|
|
44
46
|
function parseURL (url) {
|
|
45
47
|
if (typeof url === 'string') {
|
|
46
48
|
url = new URL(url)
|
|
49
|
+
|
|
50
|
+
if (!/^https?:/.test(url.origin || url.protocol)) {
|
|
51
|
+
throw new InvalidArgumentError('invalid protocol')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return url
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
if (!url || typeof url !== 'object') {
|
|
@@ -373,23 +381,34 @@ function ReadableStreamFrom (iterable) {
|
|
|
373
381
|
|
|
374
382
|
// The chunk should be a FormData instance and contains
|
|
375
383
|
// all the required methods.
|
|
376
|
-
function isFormDataLike (
|
|
377
|
-
return (
|
|
378
|
-
|
|
379
|
-
typeof
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
typeof chunk.keys === 'function' &&
|
|
388
|
-
typeof chunk.values === 'function' &&
|
|
389
|
-
typeof chunk.forEach === 'function')
|
|
384
|
+
function isFormDataLike (object) {
|
|
385
|
+
return (
|
|
386
|
+
object &&
|
|
387
|
+
typeof object === 'object' &&
|
|
388
|
+
typeof object.append === 'function' &&
|
|
389
|
+
typeof object.delete === 'function' &&
|
|
390
|
+
typeof object.get === 'function' &&
|
|
391
|
+
typeof object.getAll === 'function' &&
|
|
392
|
+
typeof object.has === 'function' &&
|
|
393
|
+
typeof object.set === 'function' &&
|
|
394
|
+
object[Symbol.toStringTag] === 'FormData'
|
|
390
395
|
)
|
|
391
396
|
}
|
|
392
397
|
|
|
398
|
+
function throwIfAborted (signal) {
|
|
399
|
+
if (!signal) { return }
|
|
400
|
+
if (typeof signal.throwIfAborted === 'function') {
|
|
401
|
+
signal.throwIfAborted()
|
|
402
|
+
} else {
|
|
403
|
+
if (signal.aborted) {
|
|
404
|
+
// DOMException not available < v17.0.0
|
|
405
|
+
const err = new Error('The operation was aborted')
|
|
406
|
+
err.name = 'AbortError'
|
|
407
|
+
throw err
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
393
412
|
const kEnumerableProperty = Object.create(null)
|
|
394
413
|
kEnumerableProperty.enumerable = true
|
|
395
414
|
|
|
@@ -420,5 +439,9 @@ module.exports = {
|
|
|
420
439
|
validateHandler,
|
|
421
440
|
getSocketInfo,
|
|
422
441
|
isFormDataLike,
|
|
423
|
-
buildURL
|
|
442
|
+
buildURL,
|
|
443
|
+
throwIfAborted,
|
|
444
|
+
nodeMajor,
|
|
445
|
+
nodeMinor,
|
|
446
|
+
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13)
|
|
424
447
|
}
|
package/lib/fetch/dataURL.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const { atob } = require('buffer')
|
|
3
|
-
const { format } = require('url')
|
|
4
3
|
const { isValidHTTPToken, isomorphicDecode } = require('./util')
|
|
5
4
|
|
|
6
5
|
const encoder = new TextEncoder()
|
|
@@ -118,7 +117,17 @@ function dataURLProcessor (dataURL) {
|
|
|
118
117
|
* @param {boolean} excludeFragment
|
|
119
118
|
*/
|
|
120
119
|
function URLSerializer (url, excludeFragment = false) {
|
|
121
|
-
|
|
120
|
+
const href = url.href
|
|
121
|
+
|
|
122
|
+
if (!excludeFragment) {
|
|
123
|
+
return href
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const hash = href.lastIndexOf('#')
|
|
127
|
+
if (hash === -1) {
|
|
128
|
+
return href
|
|
129
|
+
}
|
|
130
|
+
return href.slice(0, hash)
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
|
@@ -556,6 +565,7 @@ module.exports = {
|
|
|
556
565
|
dataURLProcessor,
|
|
557
566
|
URLSerializer,
|
|
558
567
|
collectASequenceOfCodePoints,
|
|
568
|
+
collectASequenceOfCodePointsFast,
|
|
559
569
|
stringPercentDecode,
|
|
560
570
|
parseMIMEType,
|
|
561
571
|
collectAnHTTPQuotedString,
|
package/lib/fetch/headers.js
CHANGED
|
@@ -75,6 +75,7 @@ class HeadersList {
|
|
|
75
75
|
if (init instanceof HeadersList) {
|
|
76
76
|
this[kHeadersMap] = new Map(init[kHeadersMap])
|
|
77
77
|
this[kHeadersSortedMap] = init[kHeadersSortedMap]
|
|
78
|
+
this.cookies = init.cookies
|
|
78
79
|
} else {
|
|
79
80
|
this[kHeadersMap] = new Map(init)
|
|
80
81
|
this[kHeadersSortedMap] = null
|
package/lib/fetch/index.js
CHANGED
|
@@ -53,7 +53,7 @@ const {
|
|
|
53
53
|
const { kHeadersList } = require('../core/symbols')
|
|
54
54
|
const EE = require('events')
|
|
55
55
|
const { Readable, pipeline } = require('stream')
|
|
56
|
-
const { isErrored, isReadable } = require('../core/util')
|
|
56
|
+
const { isErrored, isReadable, nodeMajor, nodeMinor } = require('../core/util')
|
|
57
57
|
const { dataURLProcessor, serializeAMimeType } = require('./dataURL')
|
|
58
58
|
const { TransformStream } = require('stream/web')
|
|
59
59
|
const { getGlobalDispatcher } = require('../global')
|
|
@@ -64,10 +64,6 @@ const { STATUS_CODES } = require('http')
|
|
|
64
64
|
let resolveObjectURL
|
|
65
65
|
let ReadableStream = globalThis.ReadableStream
|
|
66
66
|
|
|
67
|
-
const nodeVersion = process.versions.node.split('.')
|
|
68
|
-
const nodeMajor = Number(nodeVersion[0])
|
|
69
|
-
const nodeMinor = Number(nodeVersion[1])
|
|
70
|
-
|
|
71
67
|
class Fetch extends EE {
|
|
72
68
|
constructor (dispatcher) {
|
|
73
69
|
super()
|
|
@@ -301,7 +297,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
|
|
|
301
297
|
// capability.
|
|
302
298
|
// TODO: given global’s relevant settings object’s cross-origin isolated
|
|
303
299
|
// capability?
|
|
304
|
-
|
|
300
|
+
timingInfo.endTime = coarsenedSharedCurrentTime()
|
|
305
301
|
|
|
306
302
|
// 10. Set response’s timing info to timingInfo.
|
|
307
303
|
response.timingInfo = timingInfo
|
package/lib/fetch/request.js
CHANGED
|
@@ -9,7 +9,8 @@ const util = require('../core/util')
|
|
|
9
9
|
const {
|
|
10
10
|
isValidHTTPToken,
|
|
11
11
|
sameOrigin,
|
|
12
|
-
normalizeMethod
|
|
12
|
+
normalizeMethod,
|
|
13
|
+
makePolicyContainer
|
|
13
14
|
} = require('./util')
|
|
14
15
|
const {
|
|
15
16
|
forbiddenMethods,
|
|
@@ -51,10 +52,14 @@ class Request {
|
|
|
51
52
|
input = webidl.converters.RequestInfo(input)
|
|
52
53
|
init = webidl.converters.RequestInit(init)
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
+
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
|
|
55
56
|
this[kRealm] = {
|
|
56
57
|
settingsObject: {
|
|
57
|
-
baseUrl: getGlobalOrigin()
|
|
58
|
+
baseUrl: getGlobalOrigin(),
|
|
59
|
+
get origin () {
|
|
60
|
+
return this.baseUrl?.origin
|
|
61
|
+
},
|
|
62
|
+
policyContainer: makePolicyContainer()
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -349,14 +354,17 @@ class Request {
|
|
|
349
354
|
if (signal.aborted) {
|
|
350
355
|
ac.abort(signal.reason)
|
|
351
356
|
} else {
|
|
352
|
-
const acRef = new WeakRef(ac)
|
|
353
357
|
const abort = function () {
|
|
354
|
-
|
|
358
|
+
ac.abort(this.reason)
|
|
355
359
|
}
|
|
356
360
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
361
|
+
// Third-party AbortControllers may not work with these.
|
|
362
|
+
// See https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619
|
|
363
|
+
try {
|
|
364
|
+
if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
|
|
365
|
+
setMaxListeners(100, signal)
|
|
366
|
+
}
|
|
367
|
+
} catch {}
|
|
360
368
|
|
|
361
369
|
signal.addEventListener('abort', abort, { once: true })
|
|
362
370
|
requestFinalizer.register(this, { signal, abort })
|