deps: update undici to 5.28.0

PR-URL: https://github.com/nodejs/node/pull/50915
Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Node.js GitHub Bot 2023-11-29 15:51:55 +02:00 committed by GitHub
parent 59f57d289d
commit 2fa9503fee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 2366 additions and 410 deletions

View File

@ -33,7 +33,7 @@ Returns: `Client`
* **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.
* **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.
* **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
* **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overriden by a SETTINGS remote frame.
* **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
#### Parameter: `ConnectOptions`

View File

@ -35,8 +35,7 @@ const mockPool = mockAgent.get('http://localhost:3000')
### `MockPool.intercept(options)`
This method defines the interception rules for matching against requests for a MockPool or MockPool. We can intercept multiple times on a single instance, but each intercept is only used once.
For example if you expect to make 2 requests inside a test, you need to call `intercept()` twice. Assuming you use `disableNetConnect()` you will get `MockNotMatchedError` on the second request when you only call `intercept()` once.
This method defines the interception rules for matching against requests for a MockPool or MockPool. We can intercept multiple times on a single instance, but each intercept is only used once. For example if you expect to make 2 requests inside a test, you need to call `intercept()` twice. Assuming you use `disableNetConnect()` you will get `MockNotMatchedError` on the second request when you only call `intercept()` once.
When defining interception rules, all the rules must pass for a request to be intercepted. If a request is not intercepted, a real request will be attempted.
@ -54,11 +53,11 @@ Returns: `MockInterceptor` corresponding to the input options.
### Parameter: `MockPoolInterceptOptions`
* **path** `string | RegExp | (path: string) => boolean` - a matcher for the HTTP request path.
* **path** `string | RegExp | (path: string) => boolean` - a matcher for the HTTP request path. When a `RegExp` or callback is used, it will match against the request path including all query parameters in alphabetical order. When a `string` is provided, the query parameters can be conveniently specified through the `MockPoolInterceptOptions.query` setting.
* **method** `string | RegExp | (method: string) => boolean` - (optional) - a matcher for the HTTP request method. Defaults to `GET`.
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
* **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params.
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params. Only applies when a `string` was provided for `MockPoolInterceptOptions.path`.
### Return: `MockInterceptor`
@ -458,6 +457,41 @@ const result3 = await request('http://localhost:3000/foo')
// Will not match and make attempt a real request
```
#### Example - Mocked request with path callback
```js
import { MockAgent, setGlobalDispatcher, request } from 'undici'
import querystring from 'querystring'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
const matchPath = requestPath => {
const [pathname, search] = requestPath.split('?')
const requestQuery = querystring.parse(search)
if (!pathname.startsWith('/foo')) {
return false
}
if (!Object.keys(requestQuery).includes('foo') || requestQuery.foo !== 'bar') {
return false
}
return true
}
mockPool.intercept({
path: matchPath,
method: 'GET'
}).reply(200, 'foo')
const result = await request('http://localhost:3000/foo?foo=bar')
// Will match and return mocked data
```
### `MockPool.close()`
Closes the mock pool and de-registers from associated MockAgent.

108
deps/undici/src/docs/api/RetryHandler.md vendored Normal file
View File

@ -0,0 +1,108 @@
# Class: RetryHandler
Extends: `undici.DispatcherHandlers`
A handler class that implements the retry logic for a request.
## `new RetryHandler(dispatchOptions, retryHandlers, [retryOptions])`
Arguments:
- **options** `Dispatch.DispatchOptions & RetryOptions` (required) - It is an intersection of `Dispatcher.DispatchOptions` and `RetryOptions`.
- **retryHandlers** `RetryHandlers` (required) - Object containing the `dispatch` to be used on every retry, and `handler` for handling the `dispatch` lifecycle.
Returns: `retryHandler`
### Parameter: `Dispatch.DispatchOptions & RetryOptions`
Extends: [`Dispatch.DispatchOptions`](Dispatcher.md#parameter-dispatchoptions).
#### `RetryOptions`
- **retry** `(err: Error, context: RetryContext, callback: (err?: Error | null) => void) => void` (optional) - Function to be called after every retry. It should pass error if no more retries should be performed.
- **maxRetries** `number` (optional) - Maximum number of retries. Default: `5`
- **maxTimeout** `number` (optional) - Maximum number of milliseconds to wait before retrying. Default: `30000` (30 seconds)
- **minTimeout** `number` (optional) - Minimum number of milliseconds to wait before retrying. Default: `500` (half a second)
- **timeoutFactor** `number` (optional) - Factor to multiply the timeout by for each retry attempt. Default: `2`
- **retryAfter** `boolean` (optional) - It enables automatic retry after the `Retry-After` header is received. Default: `true`
-
- **methods** `string[]` (optional) - Array of HTTP methods to retry. Default: `['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE']`
- **statusCodes** `number[]` (optional) - Array of HTTP status codes to retry. Default: `[429, 500, 502, 503, 504]`
- **errorCodes** `string[]` (optional) - Array of Error codes to retry. Default: `['ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ENETDOWN','ENETUNREACH', 'EHOSTDOWN',
**`RetryContext`**
- `state`: `RetryState` - Current retry state. It can be mutated.
- `opts`: `Dispatch.DispatchOptions & RetryOptions` - Options passed to the retry handler.
### Parameter `RetryHandlers`
- **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every retry.
- **handler** Extends [`Dispatch.DispatchHandlers`](Dispatcher.md#dispatcherdispatchoptions-handler) (required) - Handler function to be called after the request is successful or the retries are exhausted.
Examples:
```js
const client = new Client(`http://localhost:${server.address().port}`);
const chunks = [];
const handler = new RetryHandler(
{
...dispatchOptions,
retryOptions: {
// custom retry function
retry: function (err, state, callback) {
counter++;
if (err.code && err.code === "UND_ERR_DESTROYED") {
callback(err);
return;
}
if (err.statusCode === 206) {
callback(err);
return;
}
setTimeout(() => callback(null), 1000);
},
},
},
{
dispatch: (...args) => {
return client.dispatch(...args);
},
handler: {
onConnect() {},
onBodySent() {},
onHeaders(status, _rawHeaders, resume, _statusMessage) {
// do something with headers
},
onData(chunk) {
chunks.push(chunk);
return true;
},
onComplete() {},
onError() {
// handle error properly
},
},
}
);
```
#### Example - Basic RetryHandler with defaults
```js
const client = new Client(`http://localhost:${server.address().port}`);
const handler = new RetryHandler(dispatchOptions, {
dispatch: client.dispatch.bind(client),
handler: {
onConnect() {},
onBodySent() {},
onHeaders(status, _rawHeaders, resume, _statusMessage) {},
onData(chunk) {},
onComplete() {},
onError(err) {},
},
});
```

View File

@ -15,6 +15,7 @@ const MockAgent = require('./lib/mock/mock-agent')
const MockPool = require('./lib/mock/mock-pool')
const mockErrors = require('./lib/mock/mock-errors')
const ProxyAgent = require('./lib/proxy-agent')
const RetryHandler = require('./lib/handler/RetryHandler')
const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global')
const DecoratorHandler = require('./lib/handler/DecoratorHandler')
const RedirectHandler = require('./lib/handler/RedirectHandler')
@ -36,6 +37,7 @@ module.exports.Pool = Pool
module.exports.BalancedPool = BalancedPool
module.exports.Agent = Agent
module.exports.ProxyAgent = ProxyAgent
module.exports.RetryHandler = RetryHandler
module.exports.DecoratorHandler = DecoratorHandler
module.exports.RedirectHandler = RedirectHandler

View File

@ -16,6 +16,8 @@ const kBody = Symbol('kBody')
const kAbort = Symbol('abort')
const kContentType = Symbol('kContentType')
const noop = () => {}
module.exports = class BodyReadable extends Readable {
constructor ({
resume,
@ -149,37 +151,50 @@ module.exports = class BodyReadable extends Readable {
return this[kBody]
}
async dump (opts) {
dump (opts) {
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
const signal = opts && opts.signal
const abortFn = () => {
this.destroy()
}
let signalListenerCleanup
if (signal) {
if (typeof signal !== 'object' || !('aborted' in signal)) {
throw new InvalidArgumentError('signal must be an AbortSignal')
}
util.throwIfAborted(signal)
signalListenerCleanup = util.addAbortListener(signal, abortFn)
}
try {
for await (const chunk of this) {
util.throwIfAborted(signal)
limit -= Buffer.byteLength(chunk)
if (limit < 0) {
return
try {
if (typeof signal !== 'object' || !('aborted' in signal)) {
throw new InvalidArgumentError('signal must be an AbortSignal')
}
}
} catch {
util.throwIfAborted(signal)
} finally {
if (typeof signalListenerCleanup === 'function') {
signalListenerCleanup()
} else if (signalListenerCleanup) {
signalListenerCleanup[Symbol.dispose]()
util.throwIfAborted(signal)
} catch (err) {
return Promise.reject(err)
}
}
if (this.closed) {
return Promise.resolve(null)
}
return new Promise((resolve, reject) => {
const signalListenerCleanup = signal
? util.addAbortListener(signal, () => {
this.destroy()
})
: noop
this
.on('close', function () {
signalListenerCleanup()
if (signal?.aborted) {
reject(signal.reason || Object.assign(new Error('The operation was aborted'), { name: 'AbortError' }))
} else {
resolve(null)
}
})
.on('error', noop)
.on('data', function (chunk) {
limit -= chunk.length
if (limit <= 0) {
this.destroy()
}
})
.resume()
})
}
}

View File

@ -1183,7 +1183,7 @@ async function connect (client) {
const idx = hostname.indexOf(']')
assert(idx !== -1)
const ip = hostname.substr(1, idx - 1)
const ip = hostname.substring(1, idx)
assert(net.isIP(ip))
hostname = ip
@ -1682,6 +1682,7 @@ function writeH2 (client, session, request) {
return false
}
/** @type {import('node:http2').ClientHttp2Stream} */
let stream
const h2State = client[kHTTP2SessionState]
@ -1777,14 +1778,10 @@ function writeH2 (client, session, request) {
const shouldEndStream = method === 'GET' || method === 'HEAD'
if (expectContinue) {
headers[HTTP2_HEADER_EXPECT] = '100-continue'
/**
* @type {import('node:http2').ClientHttp2Stream}
*/
stream = session.request(headers, { endStream: shouldEndStream, signal })
stream.once('continue', writeBodyH2)
} else {
/** @type {import('node:http2').ClientHttp2Stream} */
stream = session.request(headers, {
endStream: shouldEndStream,
signal
@ -1796,7 +1793,9 @@ function writeH2 (client, session, request) {
++h2State.openStreams
stream.once('response', headers => {
if (request.onHeaders(Number(headers[HTTP2_HEADER_STATUS]), headers, stream.resume.bind(stream), '') === false) {
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
stream.pause()
}
})
@ -1972,7 +1971,11 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
}
}
const onAbort = function () {
onFinished(new RequestAbortedError())
if (finished) {
return
}
const err = new RequestAbortedError()
queueMicrotask(() => onFinished(err))
}
const onFinished = function (err) {
if (finished) {

View File

@ -193,6 +193,19 @@ class ResponseExceededMaxSizeError extends UndiciError {
}
}
class RequestRetryError extends UndiciError {
constructor (message, code, { headers, data }) {
super(message)
Error.captureStackTrace(this, RequestRetryError)
this.name = 'RequestRetryError'
this.message = message || 'Request retry error'
this.code = 'UND_ERR_REQ_RETRY'
this.statusCode = code
this.data = data
this.headers = headers
}
}
module.exports = {
HTTPParserError,
UndiciError,
@ -212,5 +225,6 @@ module.exports = {
NotSupportedError,
ResponseContentLengthMismatchError,
BalancedPoolMissingUpstreamError,
ResponseExceededMaxSizeError
ResponseExceededMaxSizeError,
RequestRetryError
}

View File

@ -229,11 +229,7 @@ class Request {
onBodySent (chunk) {
if (this[kHandler].onBodySent) {
try {
this[kHandler].onBodySent(chunk)
} catch (err) {
this.onError(err)
}
return this[kHandler].onBodySent(chunk)
}
}
@ -243,11 +239,7 @@ class Request {
}
if (this[kHandler].onRequestSent) {
try {
this[kHandler].onRequestSent()
} catch (err) {
this.onError(err)
}
return this[kHandler].onRequestSent()
}
}

View File

@ -57,5 +57,6 @@ module.exports = {
kHTTP2BuildRequest: Symbol('http2 build request'),
kHTTP1BuildRequest: Symbol('http1 build request'),
kHTTP2CopyHeaders: Symbol('http2 copy headers'),
kHTTPConnVersion: Symbol('http connection version')
kHTTPConnVersion: Symbol('http connection version'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry')
}

View File

@ -125,13 +125,13 @@ function getHostname (host) {
const idx = host.indexOf(']')
assert(idx !== -1)
return host.substr(1, idx - 1)
return host.substring(1, idx)
}
const idx = host.indexOf(':')
if (idx === -1) return host
return host.substr(0, idx)
return host.substring(0, idx)
}
// IP addresses are not valid server names per RFC6066
@ -228,7 +228,7 @@ function parseHeaders (headers, obj = {}) {
if (!val) {
if (Array.isArray(headers[i + 1])) {
obj[key] = headers[i + 1]
obj[key] = headers[i + 1].map(x => x.toString('utf8'))
} else {
obj[key] = headers[i + 1].toString('utf8')
}
@ -431,16 +431,7 @@ function throwIfAborted (signal) {
}
}
let events
function addAbortListener (signal, listener) {
if (typeof Symbol.dispose === 'symbol') {
if (!events) {
events = require('events')
}
if (typeof events.addAbortListener === 'function' && 'aborted' in signal) {
return events.addAbortListener(signal, listener)
}
}
if ('addEventListener' in signal) {
signal.addEventListener('abort', listener, { once: true })
return () => signal.removeEventListener('abort', listener)
@ -464,6 +455,21 @@ function toUSVString (val) {
return `${val}`
}
// Parsed accordingly to RFC 9110
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range
function parseRangeHeader (range) {
if (range == null || range === '') return { start: 0, end: null, size: null }
const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null
return m
? {
start: parseInt(m[1]),
end: m[2] ? parseInt(m[2]) : null,
size: m[3] ? parseInt(m[3]) : null
}
: null
}
const kEnumerableProperty = Object.create(null)
kEnumerableProperty.enumerable = true
@ -497,7 +503,9 @@ module.exports = {
buildURL,
throwIfAborted,
addAbortListener,
parseRangeHeader,
nodeMajor,
nodeMinor,
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13)
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13),
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
}

View File

@ -16,6 +16,13 @@ const assert = require('assert')
const kHeadersMap = Symbol('headers map')
const kHeadersSortedMap = Symbol('headers map sorted')
/**
* @param {number} code
*/
function isHTTPWhiteSpaceCharCode (code) {
return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020
}
/**
* @see https://fetch.spec.whatwg.org/#concept-header-value-normalize
* @param {string} potentialValue
@ -24,12 +31,12 @@ function headerValueNormalize (potentialValue) {
// To normalize a byte sequence potentialValue, remove
// any leading and trailing HTTP whitespace bytes from
// potentialValue.
let i = 0; let j = potentialValue.length
// Trimming the end with `.replace()` and a RegExp is typically subject to
// ReDoS. This is safer and faster.
let i = potentialValue.length
while (/[\r\n\t ]/.test(potentialValue.charAt(--i)));
return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '')
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i
return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j)
}
function fill (headers, object) {
@ -38,7 +45,8 @@ function fill (headers, object) {
// 1. If object is a sequence, then for each header in object:
// Note: webidl conversion to array has already been done.
if (Array.isArray(object)) {
for (const header of object) {
for (let i = 0; i < object.length; ++i) {
const header = object[i]
// 1. If header does not contain exactly two items, then throw a TypeError.
if (header.length !== 2) {
throw webidl.errors.exception({
@ -48,15 +56,16 @@ function fill (headers, object) {
}
// 2. Append (headers first item, headers second item) to headers.
headers.append(header[0], header[1])
appendHeader(headers, header[0], header[1])
}
} else if (typeof object === 'object' && object !== null) {
// Note: null should throw
// 2. Otherwise, object is a record, then for each key → value in object,
// append (key, value) to headers
for (const [key, value] of Object.entries(object)) {
headers.append(key, value)
const keys = Object.keys(object)
for (let i = 0; i < keys.length; ++i) {
appendHeader(headers, keys[i], object[keys[i]])
}
} else {
throw webidl.errors.conversionFailed({
@ -67,6 +76,50 @@ function fill (headers, object) {
}
}
/**
* @see https://fetch.spec.whatwg.org/#concept-headers-append
*/
function appendHeader (headers, name, value) {
// 1. Normalize value.
value = headerValueNormalize(value)
// 2. If name is not a header name or value is not a
// header value, then throw a TypeError.
if (!isValidHeaderName(name)) {
throw webidl.errors.invalidArgument({
prefix: 'Headers.append',
value: name,
type: 'header name'
})
} else if (!isValidHeaderValue(value)) {
throw webidl.errors.invalidArgument({
prefix: 'Headers.append',
value,
type: 'header value'
})
}
// 3. If headerss guard is "immutable", then throw a TypeError.
// 4. Otherwise, if headerss guard is "request" and name is a
// forbidden header name, return.
// Note: undici does not implement forbidden header names
if (headers[kGuard] === 'immutable') {
throw new TypeError('immutable')
} else if (headers[kGuard] === 'request-no-cors') {
// 5. Otherwise, if headerss guard is "request-no-cors":
// TODO
}
// 6. Otherwise, if headerss guard is "response" and name is a
// forbidden response-header name, return.
// 7. Append (name, value) to headerss header list.
return headers[kHeadersList].append(name, value)
// 8. If headerss guard is "request-no-cors", then remove
// privileged no-CORS request headers from headers
}
class HeadersList {
/** @type {[string, string][]|null} */
cookies = null
@ -75,7 +128,7 @@ class HeadersList {
if (init instanceof HeadersList) {
this[kHeadersMap] = new Map(init[kHeadersMap])
this[kHeadersSortedMap] = init[kHeadersSortedMap]
this.cookies = init.cookies
this.cookies = init.cookies === null ? null : [...init.cookies]
} else {
this[kHeadersMap] = new Map(init)
this[kHeadersSortedMap] = null
@ -137,7 +190,7 @@ class HeadersList {
// the first such header to value and remove the
// others.
// 2. Otherwise, append header (name, value) to list.
return this[kHeadersMap].set(lowercaseName, { name, value })
this[kHeadersMap].set(lowercaseName, { name, value })
}
// https://fetch.spec.whatwg.org/#concept-header-list-delete
@ -150,20 +203,18 @@ class HeadersList {
this.cookies = null
}
return this[kHeadersMap].delete(name)
this[kHeadersMap].delete(name)
}
// https://fetch.spec.whatwg.org/#concept-header-list-get
get (name) {
// 1. If list does not contain name, then return null.
if (!this.contains(name)) {
return null
}
const value = this[kHeadersMap].get(name.toLowerCase())
// 1. If list does not contain name, then return null.
// 2. Return the values of all headers in list whose name
// is a byte-case-insensitive match for name,
// separated from each other by 0x2C 0x20, in order.
return this[kHeadersMap].get(name.toLowerCase())?.value ?? null
return value === undefined ? null : value.value
}
* [Symbol.iterator] () {
@ -212,43 +263,7 @@ class Headers {
name = webidl.converters.ByteString(name)
value = webidl.converters.ByteString(value)
// 1. Normalize value.
value = headerValueNormalize(value)
// 2. If name is not a header name or value is not a
// header value, then throw a TypeError.
if (!isValidHeaderName(name)) {
throw webidl.errors.invalidArgument({
prefix: 'Headers.append',
value: name,
type: 'header name'
})
} else if (!isValidHeaderValue(value)) {
throw webidl.errors.invalidArgument({
prefix: 'Headers.append',
value,
type: 'header value'
})
}
// 3. If headerss guard is "immutable", then throw a TypeError.
// 4. Otherwise, if headerss guard is "request" and name is a
// forbidden header name, return.
// Note: undici does not implement forbidden header names
if (this[kGuard] === 'immutable') {
throw new TypeError('immutable')
} else if (this[kGuard] === 'request-no-cors') {
// 5. Otherwise, if headerss guard is "request-no-cors":
// TODO
}
// 6. Otherwise, if headerss guard is "response" and name is a
// forbidden response-header name, return.
// 7. Append (name, value) to headerss header list.
// 8. If headerss guard is "request-no-cors", then remove
// privileged no-CORS request headers from headers
return this[kHeadersList].append(name, value)
return appendHeader(this, name, value)
}
// https://fetch.spec.whatwg.org/#dom-headers-delete
@ -293,7 +308,7 @@ class Headers {
// 7. Delete name from thiss header list.
// 8. If thiss guard is "request-no-cors", then remove
// privileged no-CORS request headers from this.
return this[kHeadersList].delete(name)
this[kHeadersList].delete(name)
}
// https://fetch.spec.whatwg.org/#dom-headers-get
@ -386,7 +401,7 @@ class Headers {
// 7. Set (name, value) in thiss header list.
// 8. If thiss guard is "request-no-cors", then remove
// privileged no-CORS request headers from this
return this[kHeadersList].set(name, value)
this[kHeadersList].set(name, value)
}
// https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
@ -422,7 +437,8 @@ class Headers {
const cookies = this[kHeadersList].cookies
// 3. For each name of names:
for (const [name, value] of names) {
for (let i = 0; i < names.length; ++i) {
const [name, value] = names[i]
// 1. If name is `set-cookie`, then:
if (name === 'set-cookie') {
// 1. Let values be a list of all values of headers in list whose name
@ -430,8 +446,8 @@ class Headers {
// 2. For each value of values:
// 1. Append (name, value) to headers.
for (const value of cookies) {
headers.push([name, value])
for (let j = 0; j < cookies.length; ++j) {
headers.push([name, cookies[j]])
}
} else {
// 2. Otherwise:
@ -455,6 +471,12 @@ class Headers {
keys () {
webidl.brandCheck(this, Headers)
if (this[kGuard] === 'immutable') {
const value = this[kHeadersSortedMap]
return makeIterator(() => value, 'Headers',
'key')
}
return makeIterator(
() => [...this[kHeadersSortedMap].values()],
'Headers',
@ -465,6 +487,12 @@ class Headers {
values () {
webidl.brandCheck(this, Headers)
if (this[kGuard] === 'immutable') {
const value = this[kHeadersSortedMap]
return makeIterator(() => value, 'Headers',
'value')
}
return makeIterator(
() => [...this[kHeadersSortedMap].values()],
'Headers',
@ -475,6 +503,12 @@ class Headers {
entries () {
webidl.brandCheck(this, Headers)
if (this[kGuard] === 'immutable') {
const value = this[kHeadersSortedMap]
return makeIterator(() => value, 'Headers',
'key+value')
}
return makeIterator(
() => [...this[kHeadersSortedMap].values()],
'Headers',

View File

@ -1957,7 +1957,7 @@ async function httpNetworkFetch (
path: url.pathname + url.search,
origin: url.origin,
method: request.method,
body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body,
body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
headers: request.headersList.entries,
maxRedirections: 0,
upgrade: request.mode === 'websocket' ? 'websocket' : undefined
@ -2002,7 +2002,7 @@ async function httpNetworkFetch (
location = val
}
headers.append(key, val)
headers[kHeadersList].append(key, val)
}
} else {
const keys = Object.keys(headersList)
@ -2016,7 +2016,7 @@ async function httpNetworkFetch (
location = val
}
headers.append(key, val)
headers[kHeadersList].append(key, val)
}
}
@ -2120,7 +2120,7 @@ async function httpNetworkFetch (
const key = headersList[n + 0].toString('latin1')
const val = headersList[n + 1].toString('latin1')
headers.append(key, val)
headers[kHeadersList].append(key, val)
}
resolve({

View File

@ -316,11 +316,11 @@ class Request {
// 2. If method is not a method or method is a forbidden method, then
// throw a TypeError.
if (!isValidHTTPToken(init.method)) {
throw TypeError(`'${init.method}' is not a valid HTTP method.`)
throw new TypeError(`'${init.method}' is not a valid HTTP method.`)
}
if (forbiddenMethodsSet.has(method.toUpperCase())) {
throw TypeError(`'${init.method}' HTTP method is unsupported.`)
throw new TypeError(`'${init.method}' HTTP method is unsupported.`)
}
// 3. Normalize method.

View File

@ -103,52 +103,57 @@ function isValidReasonPhrase (statusText) {
return true
}
function isTokenChar (c) {
return !(
c >= 0x7f ||
c <= 0x20 ||
c === '(' ||
c === ')' ||
c === '<' ||
c === '>' ||
c === '@' ||
c === ',' ||
c === ';' ||
c === ':' ||
c === '\\' ||
c === '"' ||
c === '/' ||
c === '[' ||
c === ']' ||
c === '?' ||
c === '=' ||
c === '{' ||
c === '}'
)
/**
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
* @param {number} c
*/
function isTokenCharCode (c) {
switch (c) {
case 0x22:
case 0x28:
case 0x29:
case 0x2c:
case 0x2f:
case 0x3a:
case 0x3b:
case 0x3c:
case 0x3d:
case 0x3e:
case 0x3f:
case 0x40:
case 0x5b:
case 0x5c:
case 0x5d:
case 0x7b:
case 0x7d:
// DQUOTE and "(),/:;<=>?@[\]{}"
return false
default:
// VCHAR %x21-7E
return c >= 0x21 && c <= 0x7e
}
}
// See RFC 7230, Section 3.2.6.
// https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321
/**
* @param {string} characters
*/
function isValidHTTPToken (characters) {
if (!characters || typeof characters !== 'string') {
if (characters.length === 0) {
return false
}
for (let i = 0; i < characters.length; ++i) {
const c = characters.charCodeAt(i)
if (c > 0x7f || !isTokenChar(c)) {
if (!isTokenCharCode(characters.charCodeAt(i))) {
return false
}
}
return true
}
// https://fetch.spec.whatwg.org/#header-name
// https://github.com/chromium/chromium/blob/b3d37e6f94f87d59e44662d6078f6a12de845d17/net/http/http_util.cc#L342
/**
* @see https://fetch.spec.whatwg.org/#header-name
* @param {string} potentialValue
*/
function isValidHeaderName (potentialValue) {
if (potentialValue.length === 0) {
return false
}
return isValidHTTPToken(potentialValue)
}

View File

@ -427,12 +427,10 @@ webidl.converters.ByteString = function (V) {
// 2. If the value of any element of x is greater than
// 255, then throw a TypeError.
for (let index = 0; index < x.length; index++) {
const charCode = x.charCodeAt(index)
if (charCode > 255) {
if (x.charCodeAt(index) > 255) {
throw new TypeError(
'Cannot convert argument to a ByteString because the character at ' +
`index ${index} has a value of ${charCode} which is greater than 255.`
`index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
)
}
}

View File

@ -0,0 +1,336 @@
const assert = require('node:assert')
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
const { RequestRetryError } = require('../core/errors')
const { isDisturbed, parseHeaders, parseRangeHeader } = require('../core/util')
function calculateRetryAfterHeader (retryAfter) {
const current = Date.now()
const diff = new Date(retryAfter).getTime() - current
return diff
}
class RetryHandler {
constructor (opts, handlers) {
const { retryOptions, ...dispatchOpts } = opts
const {
// Retry scoped
retry: retryFn,
maxRetries,
maxTimeout,
minTimeout,
timeoutFactor,
// Response scoped
methods,
errorCodes,
retryAfter,
statusCodes
} = retryOptions ?? {}
this.dispatch = handlers.dispatch
this.handler = handlers.handler
this.opts = dispatchOpts
this.abort = null
this.aborted = false
this.retryOpts = {
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
retryAfter: retryAfter ?? true,
maxTimeout: maxTimeout ?? 30 * 1000, // 30s,
timeout: minTimeout ?? 500, // .5s
timeoutFactor: timeoutFactor ?? 2,
maxRetries: maxRetries ?? 5,
// What errors we should retry
methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'],
// Indicates which errors to retry
statusCodes: statusCodes ?? [500, 502, 503, 504, 429],
// List of errors to retry
errorCodes: errorCodes ?? [
'ECONNRESET',
'ECONNREFUSED',
'ENOTFOUND',
'ENETDOWN',
'ENETUNREACH',
'EHOSTDOWN',
'EHOSTUNREACH',
'EPIPE'
]
}
this.retryCount = 0
this.start = 0
this.end = null
this.etag = null
this.resume = null
// Handle possible onConnect duplication
this.handler.onConnect(reason => {
this.aborted = true
if (this.abort) {
this.abort(reason)
} else {
this.reason = reason
}
})
}
onRequestSent () {
if (this.handler.onRequestSent) {
this.handler.onRequestSent()
}
}
onUpgrade (statusCode, headers, socket) {
if (this.handler.onUpgrade) {
this.handler.onUpgrade(statusCode, headers, socket)
}
}
onConnect (abort) {
if (this.aborted) {
abort(this.reason)
} else {
this.abort = abort
}
}
onBodySent (chunk) {
return this.handler.onBodySent(chunk)
}
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
const { statusCode, code, headers } = err
const { method, retryOptions } = opts
const {
maxRetries,
timeout,
maxTimeout,
timeoutFactor,
statusCodes,
errorCodes,
methods
} = retryOptions
let { counter, currentTimeout } = state
currentTimeout =
currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout
// Any code that is not a Undici's originated and allowed to retry
if (
code &&
code !== 'UND_ERR_REQ_RETRY' &&
code !== 'UND_ERR_SOCKET' &&
!errorCodes.includes(code)
) {
cb(err)
return
}
// If a set of method are provided and the current method is not in the list
if (Array.isArray(methods) && !methods.includes(method)) {
cb(err)
return
}
// If a set of status code are provided and the current status code is not in the list
if (
statusCode != null &&
Array.isArray(statusCodes) &&
!statusCodes.includes(statusCode)
) {
cb(err)
return
}
// If we reached the max number of retries
if (counter > maxRetries) {
cb(err)
return
}
let retryAfterHeader = headers != null && headers['retry-after']
if (retryAfterHeader) {
retryAfterHeader = Number(retryAfterHeader)
retryAfterHeader = isNaN(retryAfterHeader)
? calculateRetryAfterHeader(retryAfterHeader)
: retryAfterHeader * 1e3 // Retry-After is in seconds
}
const retryTimeout =
retryAfterHeader > 0
? Math.min(retryAfterHeader, maxTimeout)
: Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout)
state.currentTimeout = retryTimeout
setTimeout(() => cb(null), retryTimeout)
}
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
const headers = parseHeaders(rawHeaders)
this.retryCount += 1
if (statusCode >= 300) {
this.abort(
new RequestRetryError('Request failed', statusCode, {
headers,
count: this.retryCount
})
)
return false
}
// Checkpoint for resume from where we left it
if (this.resume != null) {
this.resume = null
if (statusCode !== 206) {
return true
}
const contentRange = parseRangeHeader(headers['content-range'])
// If no content range
if (!contentRange) {
this.abort(
new RequestRetryError('Content-Range mismatch', statusCode, {
headers,
count: this.retryCount
})
)
return false
}
// Let's start with a weak etag check
if (this.etag != null && this.etag !== headers.etag) {
this.abort(
new RequestRetryError('ETag mismatch', statusCode, {
headers,
count: this.retryCount
})
)
return false
}
const { start, size, end = size } = contentRange
assert(this.start === start, 'content-range mismatch')
assert(this.end == null || this.end === end, 'content-range mismatch')
this.resume = resume
return true
}
if (this.end == null) {
if (statusCode === 206) {
// First time we receive 206
const range = parseRangeHeader(headers['content-range'])
if (range == null) {
return this.handler.onHeaders(
statusCode,
rawHeaders,
resume,
statusMessage
)
}
const { start, size, end = size } = range
assert(
start != null && Number.isFinite(start) && this.start !== start,
'content-range mismatch'
)
assert(Number.isFinite(start))
assert(
end != null && Number.isFinite(end) && this.end !== end,
'invalid content-length'
)
this.start = start
this.end = end
}
// We make our best to checkpoint the body for further range headers
if (this.end == null) {
const contentLength = headers['content-length']
this.end = contentLength != null ? Number(contentLength) : null
}
assert(Number.isFinite(this.start))
assert(
this.end == null || Number.isFinite(this.end),
'invalid content-length'
)
this.resume = resume
this.etag = headers.etag != null ? headers.etag : null
return this.handler.onHeaders(
statusCode,
rawHeaders,
resume,
statusMessage
)
}
const err = new RequestRetryError('Request failed', statusCode, {
headers,
count: this.retryCount
})
this.abort(err)
return false
}
onData (chunk) {
this.start += chunk.length
return this.handler.onData(chunk)
}
onComplete (rawTrailers) {
this.retryCount = 0
return this.handler.onComplete(rawTrailers)
}
onError (err) {
if (this.aborted || isDisturbed(this.opts.body)) {
return this.handler.onError(err)
}
this.retryOpts.retry(
err,
{
state: { counter: this.retryCount++, currentTimeout: this.retryAfter },
opts: { retryOptions: this.retryOpts, ...this.opts }
},
onRetry.bind(this)
)
function onRetry (err) {
if (err != null || this.aborted || isDisturbed(this.opts.body)) {
return this.handler.onError(err)
}
if (this.start !== 0) {
this.opts = {
...this.opts,
headers: {
...this.opts.headers,
range: `bytes=${this.start}-${this.end ?? ''}`
}
}
}
try {
this.dispatch(this.opts, this)
} catch (err) {
this.handler.onError(err)
}
}
}
}
module.exports = RetryHandler

1523
deps/undici/src/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "undici",
"version": "5.27.2",
"version": "5.28.0",
"description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org",
"bugs": {
@ -118,6 +118,7 @@
"jsdom": "^22.1.0",
"jsfuzz": "^1.0.15",
"mocha": "^10.0.0",
"mockttp": "^3.9.2",
"p-timeout": "^3.2.0",
"pre-commit": "^1.2.2",
"proxy": "^1.0.2",

View File

@ -77,7 +77,7 @@ export declare namespace Client {
*/
allowH2?: boolean;
/**
* @description Dictates the maximum number of concurrent streams for a single H2 session. It can be overriden by a SETTINGS remote frame.
* @description Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
* @default 100
*/
maxConcurrentStreams?: number

View File

@ -211,7 +211,7 @@ declare namespace Dispatcher {
/** Invoked when request is upgraded either due to a `Upgrade` header or `CONNECT` method. */
onUpgrade?(statusCode: number, headers: Buffer[] | string[] | null, socket: Duplex): void;
/** Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. */
onHeaders?(statusCode: number, headers: Buffer[] | string[] | null, resume: () => void): boolean;
onHeaders?(statusCode: number, headers: Buffer[] | string[] | null, resume: () => void, statusText: string): boolean;
/** Invoked when response payload data is received. */
onData?(chunk: Buffer): boolean;
/** Invoked when response payload and trailers have been received and the request has completed. */

View File

@ -14,6 +14,7 @@ import MockPool from'./mock-pool'
import MockAgent from'./mock-agent'
import mockErrors from'./mock-errors'
import ProxyAgent from'./proxy-agent'
import RetryHandler from'./retry-handler'
import { request, pipeline, stream, connect, upgrade } from './api'
export * from './cookies'
@ -27,7 +28,7 @@ export * from './content-type'
export * from './cache'
export { Interceptable } from './mock-interceptor'
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, RedirectHandler, DecoratorHandler }
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler }
export default Undici
declare namespace Undici {
@ -35,6 +36,7 @@ declare namespace Undici {
var Pool: typeof import('./pool').default;
var RedirectHandler: typeof import ('./handlers').RedirectHandler
var DecoratorHandler: typeof import ('./handlers').DecoratorHandler
var RetryHandler: typeof import ('./retry-handler').default
var createRedirectInterceptor: typeof import ('./interceptors').createRedirectInterceptor
var BalancedPool: typeof import('./balanced-pool').default;
var Client: typeof import('./client').default;

116
deps/undici/src/types/retry-handler.d.ts vendored Normal file
View File

@ -0,0 +1,116 @@
import Dispatcher from "./dispatcher";
export default RetryHandler;
declare class RetryHandler implements Dispatcher.DispatchHandlers {
constructor(
options: Dispatcher.DispatchOptions & {
retryOptions?: RetryHandler.RetryOptions;
},
retryHandlers: RetryHandler.RetryHandlers
);
}
declare namespace RetryHandler {
export type RetryState = { counter: number; currentTimeout: number };
export type RetryContext = {
state: RetryState;
opts: Dispatcher.DispatchOptions & {
retryOptions?: RetryHandler.RetryOptions;
};
}
export type OnRetryCallback = (result?: Error | null) => void;
export type RetryCallback = (
err: Error,
context: {
state: RetryState;
opts: Dispatcher.DispatchOptions & {
retryOptions?: RetryHandler.RetryOptions;
};
},
callback: OnRetryCallback
) => number | null;
export interface RetryOptions {
/**
* Callback to be invoked on every retry iteration.
* It receives the error, current state of the retry object and the options object
* passed when instantiating the retry handler.
*
* @type {RetryCallback}
* @memberof RetryOptions
*/
retry?: RetryCallback;
/**
* Maximum number of retries to allow.
*
* @type {number}
* @memberof RetryOptions
* @default 5
*/
maxRetries?: number;
/**
* Max number of milliseconds allow between retries
*
* @type {number}
* @memberof RetryOptions
* @default 30000
*/
maxTimeout?: number;
/**
* Initial number of milliseconds to wait before retrying for the first time.
*
* @type {number}
* @memberof RetryOptions
* @default 500
*/
minTimeout?: number;
/**
* Factior to multiply the timeout factor between retries.
*
* @type {number}
* @memberof RetryOptions
* @default 2
*/
timeoutFactor?: number;
/**
* It enables to automatically infer timeout between retries based on the `Retry-After` header.
*
* @type {boolean}
* @memberof RetryOptions
* @default true
*/
retryAfter?: boolean;
/**
* HTTP methods to retry.
*
* @type {Dispatcher.HttpMethod[]}
* @memberof RetryOptions
* @default ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'],
*/
methods?: Dispatcher.HttpMethod[];
/**
* Error codes to be retried. e.g. `ECONNRESET`, `ENOTFOUND`, `ETIMEDOUT`, `ECONNREFUSED`, etc.
*
* @type {string[]}
* @default ['ECONNRESET','ECONNREFUSED','ENOTFOUND','ENETDOWN','ENETUNREACH','EHOSTDOWN','EHOSTUNREACH','EPIPE']
*/
errorCodes?: string[];
/**
* HTTP status codes to be retried.
*
* @type {number[]}
* @memberof RetryOptions
* @default [500, 502, 503, 504, 429],
*/
statusCodes?: number[];
}
export interface RetryHandlers {
dispatch: Dispatcher["dispatch"];
handler: Dispatcher.DispatchHandlers;
}
}

240
deps/undici/undici.js vendored
View File

@ -68,7 +68,8 @@ var require_symbols = __commonJS({
kHTTP2BuildRequest: Symbol("http2 build request"),
kHTTP1BuildRequest: Symbol("http1 build request"),
kHTTP2CopyHeaders: Symbol("http2 copy headers"),
kHTTPConnVersion: Symbol("http connection version")
kHTTPConnVersion: Symbol("http connection version"),
kRetryHandlerDefaultRetry: Symbol("retry agent default retry")
};
}
});
@ -323,6 +324,21 @@ var require_errors = __commonJS({
this.code = "UND_ERR_RES_EXCEEDED_MAX_SIZE";
}
};
var RequestRetryError = class _RequestRetryError extends UndiciError {
static {
__name(this, "RequestRetryError");
}
constructor(message, code, { headers, data }) {
super(message);
Error.captureStackTrace(this, _RequestRetryError);
this.name = "RequestRetryError";
this.message = message || "Request retry error";
this.code = "UND_ERR_REQ_RETRY";
this.statusCode = code;
this.data = data;
this.headers = headers;
}
};
module2.exports = {
HTTPParserError,
UndiciError,
@ -342,7 +358,8 @@ var require_errors = __commonJS({
NotSupportedError,
ResponseContentLengthMismatchError,
BalancedPoolMissingUpstreamError,
ResponseExceededMaxSizeError
ResponseExceededMaxSizeError,
RequestRetryError
};
}
});
@ -439,12 +456,12 @@ var require_util = __commonJS({
if (host[0] === "[") {
const idx2 = host.indexOf("]");
assert(idx2 !== -1);
return host.substr(1, idx2 - 1);
return host.substring(1, idx2);
}
const idx = host.indexOf(":");
if (idx === -1)
return host;
return host.substr(0, idx);
return host.substring(0, idx);
}
__name(getHostname, "getHostname");
function getServerName(host) {
@ -527,7 +544,7 @@ var require_util = __commonJS({
let val = obj[key];
if (!val) {
if (Array.isArray(headers[i + 1])) {
obj[key] = headers[i + 1];
obj[key] = headers[i + 1].map((x) => x.toString("utf8"));
} else {
obj[key] = headers[i + 1].toString("utf8");
}
@ -689,16 +706,7 @@ var require_util = __commonJS({
}
}
__name(throwIfAborted, "throwIfAborted");
var events;
function addAbortListener(signal, listener) {
if (typeof Symbol.dispose === "symbol") {
if (!events) {
events = require("events");
}
if (typeof events.addAbortListener === "function" && "aborted" in signal) {
return events.addAbortListener(signal, listener);
}
}
if ("addEventListener" in signal) {
signal.addEventListener("abort", listener, { once: true });
return () => signal.removeEventListener("abort", listener);
@ -717,6 +725,17 @@ var require_util = __commonJS({
return `${val}`;
}
__name(toUSVString, "toUSVString");
function parseRangeHeader(range) {
if (range == null || range === "")
return { start: 0, end: null, size: null };
const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null;
return m ? {
start: parseInt(m[1]),
end: m[2] ? parseInt(m[2]) : null,
size: m[3] ? parseInt(m[3]) : null
} : null;
}
__name(parseRangeHeader, "parseRangeHeader");
var kEnumerableProperty = /* @__PURE__ */ Object.create(null);
kEnumerableProperty.enumerable = true;
module2.exports = {
@ -749,9 +768,11 @@ var require_util = __commonJS({
buildURL,
throwIfAborted,
addAbortListener,
parseRangeHeader,
nodeMajor,
nodeMinor,
nodeHasAutoSelectFamily: nodeMajor > 18 || nodeMajor === 18 && nodeMinor >= 13
nodeHasAutoSelectFamily: nodeMajor > 18 || nodeMajor === 18 && nodeMinor >= 13,
safeHTTPMethods: ["GET", "HEAD", "OPTIONS", "TRACE"]
};
}
});
@ -1056,17 +1077,37 @@ var require_util2 = __commonJS({
return true;
}
__name(isValidReasonPhrase, "isValidReasonPhrase");
function isTokenChar(c) {
return !(c >= 127 || c <= 32 || c === "(" || c === ")" || c === "<" || c === ">" || c === "@" || c === "," || c === ";" || c === ":" || c === "\\" || c === '"' || c === "/" || c === "[" || c === "]" || c === "?" || c === "=" || c === "{" || c === "}");
function isTokenCharCode(c) {
switch (c) {
case 34:
case 40:
case 41:
case 44:
case 47:
case 58:
case 59:
case 60:
case 61:
case 62:
case 63:
case 64:
case 91:
case 92:
case 93:
case 123:
case 125:
return false;
default:
return c >= 33 && c <= 126;
}
}
__name(isTokenChar, "isTokenChar");
__name(isTokenCharCode, "isTokenCharCode");
function isValidHTTPToken(characters) {
if (!characters || typeof characters !== "string") {
if (characters.length === 0) {
return false;
}
for (let i = 0; i < characters.length; ++i) {
const c = characters.charCodeAt(i);
if (c > 127 || !isTokenChar(c)) {
if (!isTokenCharCode(characters.charCodeAt(i))) {
return false;
}
}
@ -1074,9 +1115,6 @@ var require_util2 = __commonJS({
}
__name(isValidHTTPToken, "isValidHTTPToken");
function isValidHeaderName(potentialValue) {
if (potentialValue.length === 0) {
return false;
}
return isValidHTTPToken(potentialValue);
}
__name(isValidHeaderName, "isValidHeaderName");
@ -1829,10 +1867,9 @@ var require_webidl = __commonJS({
webidl.converters.ByteString = function(V) {
const x = webidl.converters.DOMString(V);
for (let index = 0; index < x.length; index++) {
const charCode = x.charCodeAt(index);
if (charCode > 255) {
if (x.charCodeAt(index) > 255) {
throw new TypeError(
`Cannot convert argument to a ByteString because the character at index ${index} has a value of ${charCode} which is greater than 255.`
`Cannot convert argument to a ByteString because the character at index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
);
}
}
@ -1953,27 +1990,36 @@ var require_headers = __commonJS({
var assert = require("assert");
var kHeadersMap = Symbol("headers map");
var kHeadersSortedMap = Symbol("headers map sorted");
function isHTTPWhiteSpaceCharCode(code) {
return code === 10 || code === 13 || code === 9 || code === 32;
}
__name(isHTTPWhiteSpaceCharCode, "isHTTPWhiteSpaceCharCode");
function headerValueNormalize(potentialValue) {
let i = potentialValue.length;
while (/[\r\n\t ]/.test(potentialValue.charAt(--i)))
;
return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, "");
let i = 0;
let j = potentialValue.length;
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1)))
--j;
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i)))
++i;
return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j);
}
__name(headerValueNormalize, "headerValueNormalize");
function fill(headers, object) {
if (Array.isArray(object)) {
for (const header of object) {
for (let i = 0; i < object.length; ++i) {
const header = object[i];
if (header.length !== 2) {
throw webidl.errors.exception({
header: "Headers constructor",
message: `expected name/value pair to be length 2, found ${header.length}.`
});
}
headers.append(header[0], header[1]);
appendHeader(headers, header[0], header[1]);
}
} else if (typeof object === "object" && object !== null) {
for (const [key, value] of Object.entries(object)) {
headers.append(key, value);
const keys = Object.keys(object);
for (let i = 0; i < keys.length; ++i) {
appendHeader(headers, keys[i], object[keys[i]]);
}
} else {
throw webidl.errors.conversionFailed({
@ -1984,6 +2030,28 @@ var require_headers = __commonJS({
}
}
__name(fill, "fill");
function appendHeader(headers, name, value) {
value = headerValueNormalize(value);
if (!isValidHeaderName(name)) {
throw webidl.errors.invalidArgument({
prefix: "Headers.append",
value: name,
type: "header name"
});
} else if (!isValidHeaderValue(value)) {
throw webidl.errors.invalidArgument({
prefix: "Headers.append",
value,
type: "header value"
});
}
if (headers[kGuard] === "immutable") {
throw new TypeError("immutable");
} else if (headers[kGuard] === "request-no-cors") {
}
return headers[kHeadersList].append(name, value);
}
__name(appendHeader, "appendHeader");
var HeadersList = class _HeadersList {
static {
__name(this, "HeadersList");
@ -1994,7 +2062,7 @@ var require_headers = __commonJS({
if (init instanceof _HeadersList) {
this[kHeadersMap] = new Map(init[kHeadersMap]);
this[kHeadersSortedMap] = init[kHeadersSortedMap];
this.cookies = init.cookies;
this.cookies = init.cookies === null ? null : [...init.cookies];
} else {
this[kHeadersMap] = new Map(init);
this[kHeadersSortedMap] = null;
@ -2036,7 +2104,7 @@ var require_headers = __commonJS({
if (lowercaseName === "set-cookie") {
this.cookies = [value];
}
return this[kHeadersMap].set(lowercaseName, { name, value });
this[kHeadersMap].set(lowercaseName, { name, value });
}
// https://fetch.spec.whatwg.org/#concept-header-list-delete
delete(name) {
@ -2045,14 +2113,12 @@ var require_headers = __commonJS({
if (name === "set-cookie") {
this.cookies = null;
}
return this[kHeadersMap].delete(name);
this[kHeadersMap].delete(name);
}
// https://fetch.spec.whatwg.org/#concept-header-list-get
get(name) {
if (!this.contains(name)) {
return null;
}
return this[kHeadersMap].get(name.toLowerCase())?.value ?? null;
const value = this[kHeadersMap].get(name.toLowerCase());
return value === void 0 ? null : value.value;
}
*[Symbol.iterator]() {
for (const [name, { value }] of this[kHeadersMap]) {
@ -2087,25 +2153,7 @@ var require_headers = __commonJS({
webidl.argumentLengthCheck(arguments, 2, { header: "Headers.append" });
name = webidl.converters.ByteString(name);
value = webidl.converters.ByteString(value);
value = headerValueNormalize(value);
if (!isValidHeaderName(name)) {
throw webidl.errors.invalidArgument({
prefix: "Headers.append",
value: name,
type: "header name"
});
} else if (!isValidHeaderValue(value)) {
throw webidl.errors.invalidArgument({
prefix: "Headers.append",
value,
type: "header value"
});
}
if (this[kGuard] === "immutable") {
throw new TypeError("immutable");
} else if (this[kGuard] === "request-no-cors") {
}
return this[kHeadersList].append(name, value);
return appendHeader(this, name, value);
}
// https://fetch.spec.whatwg.org/#dom-headers-delete
delete(name) {
@ -2126,7 +2174,7 @@ var require_headers = __commonJS({
if (!this[kHeadersList].contains(name)) {
return;
}
return this[kHeadersList].delete(name);
this[kHeadersList].delete(name);
}
// https://fetch.spec.whatwg.org/#dom-headers-get
get(name) {
@ -2180,7 +2228,7 @@ var require_headers = __commonJS({
throw new TypeError("immutable");
} else if (this[kGuard] === "request-no-cors") {
}
return this[kHeadersList].set(name, value);
this[kHeadersList].set(name, value);
}
// https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
getSetCookie() {
@ -2199,10 +2247,11 @@ var require_headers = __commonJS({
const headers = [];
const names = [...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1);
const cookies = this[kHeadersList].cookies;
for (const [name, value] of names) {
for (let i = 0; i < names.length; ++i) {
const [name, value] = names[i];
if (name === "set-cookie") {
for (const value2 of cookies) {
headers.push([name, value2]);
for (let j = 0; j < cookies.length; ++j) {
headers.push([name, cookies[j]]);
}
} else {
assert(value !== null);
@ -2214,6 +2263,14 @@ var require_headers = __commonJS({
}
keys() {
webidl.brandCheck(this, _Headers);
if (this[kGuard] === "immutable") {
const value = this[kHeadersSortedMap];
return makeIterator(
() => value,
"Headers",
"key"
);
}
return makeIterator(
() => [...this[kHeadersSortedMap].values()],
"Headers",
@ -2222,6 +2279,14 @@ var require_headers = __commonJS({
}
values() {
webidl.brandCheck(this, _Headers);
if (this[kGuard] === "immutable") {
const value = this[kHeadersSortedMap];
return makeIterator(
() => value,
"Headers",
"value"
);
}
return makeIterator(
() => [...this[kHeadersSortedMap].values()],
"Headers",
@ -2230,6 +2295,14 @@ var require_headers = __commonJS({
}
entries() {
webidl.brandCheck(this, _Headers);
if (this[kGuard] === "immutable") {
const value = this[kHeadersSortedMap];
return makeIterator(
() => value,
"Headers",
"key+value"
);
}
return makeIterator(
() => [...this[kHeadersSortedMap].values()],
"Headers",
@ -5991,10 +6064,10 @@ var require_request = __commonJS({
if (init.method !== void 0) {
let method = init.method;
if (!isValidHTTPToken(init.method)) {
throw TypeError(`'${init.method}' is not a valid HTTP method.`);
throw new TypeError(`'${init.method}' is not a valid HTTP method.`);
}
if (forbiddenMethodsSet.has(method.toUpperCase())) {
throw TypeError(`'${init.method}' HTTP method is unsupported.`);
throw new TypeError(`'${init.method}' HTTP method is unsupported.`);
}
method = normalizeMethod(init.method);
request.method = method;
@ -7132,11 +7205,7 @@ var require_request2 = __commonJS({
}
onBodySent(chunk) {
if (this[kHandler].onBodySent) {
try {
this[kHandler].onBodySent(chunk);
} catch (err) {
this.onError(err);
}
return this[kHandler].onBodySent(chunk);
}
}
onRequestSent() {
@ -7144,11 +7213,7 @@ var require_request2 = __commonJS({
channels.bodySent.publish({ request: this });
}
if (this[kHandler].onRequestSent) {
try {
this[kHandler].onRequestSent();
} catch (err) {
this.onError(err);
}
return this[kHandler].onRequestSent();
}
}
onConnect(abort) {
@ -8939,7 +9004,7 @@ var require_client = __commonJS({
if (hostname[0] === "[") {
const idx = hostname.indexOf("]");
assert(idx !== -1);
const ip = hostname.substr(1, idx - 1);
const ip = hostname.substring(1, idx);
assert(net.isIP(ip));
hostname = ip;
}
@ -9385,7 +9450,8 @@ upgrade: ${upgrade}\r
}
++h2State.openStreams;
stream.once("response", (headers2) => {
if (request.onHeaders(Number(headers2[HTTP2_HEADER_STATUS]), headers2, stream.resume.bind(stream), "") === false) {
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers2;
if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), "") === false) {
stream.pause();
}
});
@ -9529,7 +9595,11 @@ upgrade: ${upgrade}\r
}
}, "onDrain");
const onAbort = /* @__PURE__ */ __name(function() {
onFinished(new RequestAbortedError());
if (finished) {
return;
}
const err = new RequestAbortedError();
queueMicrotask(() => onFinished(err));
}, "onAbort");
const onFinished = /* @__PURE__ */ __name(function(err) {
if (finished) {
@ -10930,7 +11000,7 @@ var require_fetch = __commonJS({
path: url.pathname + url.search,
origin: url.origin,
method: request.method,
body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body,
body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
headers: request.headersList.entries,
maxRedirections: 0,
upgrade: request.mode === "websocket" ? "websocket" : void 0
@ -10963,7 +11033,7 @@ var require_fetch = __commonJS({
} else if (key.toLowerCase() === "location") {
location = val;
}
headers.append(key, val);
headers[kHeadersList].append(key, val);
}
} else {
const keys = Object.keys(headersList);
@ -10974,7 +11044,7 @@ var require_fetch = __commonJS({
} else if (key.toLowerCase() === "location") {
location = val;
}
headers.append(key, val);
headers[kHeadersList].append(key, val);
}
}
this.body = new Readable({ read: resume });
@ -11042,7 +11112,7 @@ var require_fetch = __commonJS({
for (let n = 0; n < headersList.length; n += 2) {
const key = headersList[n + 0].toString("latin1");
const val = headersList[n + 1].toString("latin1");
headers.append(key, val);
headers[kHeadersList].append(key, val);
}
resolve({
status,

View File

@ -29,7 +29,7 @@ This a list of all the dependencies:
* [postject 1.0.0-alpha.6][]
* [simdjson 3.6.0][]
* [simdutf 4.0.4][]
* [undici 5.27.2][]
* [undici 5.28.0][]
* [uvwasi 0.0.19][]
* [V8 11.8.172.12][]
* [zlib 1.2.13.1-motley-5daffc7][]
@ -297,7 +297,7 @@ a C++ library for fast JSON parsing.
The [simdutf](https://github.com/simdutf/simdutf) dependency is
a C++ library for fast UTF-8 decoding and encoding.
### undici 5.27.2
### undici 5.28.0
The [undici](https://github.com/nodejs/undici) dependency is an HTTP/1.1 client,
written from scratch for Node.js..
@ -352,7 +352,7 @@ performance improvements not currently available in standard zlib.
[postject 1.0.0-alpha.6]: #postject-100-alpha6
[simdjson 3.6.0]: #simdutf-360
[simdutf 4.0.4]: #simdutf-404
[undici 5.27.2]: #undici-5272
[undici 5.28.0]: #undici-5280
[update-openssl-action]: ../../../.github/workflows/update-openssl.yml
[uvwasi 0.0.19]: #uvwasi-0019
[v8 11.8.172.12]: #v8-11817212

View File

@ -2,5 +2,5 @@
// Refer to tools/dep_updaters/update-undici.sh
#ifndef SRC_UNDICI_VERSION_H_
#define SRC_UNDICI_VERSION_H_
#define UNDICI_VERSION "5.27.2"
#define UNDICI_VERSION "5.28.0"
#endif // SRC_UNDICI_VERSION_H_