Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deps: update undici to 5.21.0 #47063

Merged
merged 1 commit into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deps/undici/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling

## Workarounds

### Network address family autoselection.
### Network address family autoselection.

If you experience problem when connecting to a remote server that is resolved by your DNS servers to a IPv6 (AAAA record)
first, there are chances that your local router or ISP might have problem connecting to IPv6 networks. In that case
Expand Down
4 changes: 3 additions & 1 deletion deps/undici/src/docs/api/ProxyAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
* **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.
* **token** `string` (optional) - It can be passed by a string of token for authentication.
* **auth** `string` (**deprecated**) - Use token.
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`

Examples:

Expand Down Expand Up @@ -83,7 +84,8 @@ import { setGlobalDispatcher, request, ProxyAgent } from 'undici';

const proxyAgent = new ProxyAgent({
uri: 'my.proxy.server',
token: 'Bearer xxxx'
// token: 'Bearer xxxx'
token: `Basic ${Buffer.from('username:password').toString('base64')}`
});
setGlobalDispatcher(proxyAgent);

Expand Down
38 changes: 33 additions & 5 deletions deps/undici/src/lib/api/api-stream.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict'

const { finished } = require('stream')
const { finished, PassThrough } = require('stream')
const {
InvalidArgumentError,
InvalidReturnValueError,
RequestAbortedError
RequestAbortedError,
ResponseStatusCodeError
} = require('../core/errors')
const util = require('../core/util')
const { AsyncResource } = require('async_hooks')
Expand All @@ -16,7 +17,7 @@ class StreamHandler extends AsyncResource {
throw new InvalidArgumentError('invalid opts')
}

const { signal, method, opaque, body, onInfo, responseHeaders } = opts
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts

try {
if (typeof callback !== 'function') {
Expand Down Expand Up @@ -57,6 +58,7 @@ class StreamHandler extends AsyncResource {
this.trailers = null
this.body = body
this.onInfo = onInfo || null
this.throwOnError = throwOnError || false

if (util.isStream(body)) {
body.on('error', (err) => {
Expand All @@ -76,8 +78,8 @@ class StreamHandler extends AsyncResource {
this.context = context
}

onHeaders (statusCode, rawHeaders, resume) {
const { factory, opaque, context } = this
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
const { factory, opaque, context, callback } = this

if (statusCode < 200) {
if (this.onInfo) {
Expand All @@ -96,6 +98,32 @@ class StreamHandler extends AsyncResource {
context
})

if (this.throwOnError && statusCode >= 400) {
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
const chunks = []
const pt = new PassThrough()
pt
.on('data', (chunk) => chunks.push(chunk))
.on('end', () => {
const payload = Buffer.concat(chunks).toString('utf8')
this.runInAsyncScope(
callback,
null,
new ResponseStatusCodeError(
`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`,
statusCode,
headers,
payload
)
)
})
.on('error', (err) => {
this.onError(err)
})
this.res = pt
return
}

if (
!res ||
typeof res.write !== 'function' ||
Expand Down
20 changes: 18 additions & 2 deletions deps/undici/src/lib/api/readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

const assert = require('assert')
const { Readable } = require('stream')
const { RequestAbortedError, NotSupportedError } = require('../core/errors')
const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = require('../core/errors')
const util = require('../core/util')
const { ReadableStreamFrom, toUSVString } = require('../core/util')

Expand Down Expand Up @@ -146,15 +146,31 @@ module.exports = class BodyReadable extends Readable {

async dump (opts) {
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
const signal = opts && opts.signal
const abortFn = () => {
this.destroy()
}
if (signal) {
if (typeof signal !== 'object' || !('aborted' in signal)) {
throw new InvalidArgumentError('signal must be an AbortSignal')
}
util.throwIfAborted(signal)
signal.addEventListener('abort', abortFn, { once: true })
}
try {
for await (const chunk of this) {
util.throwIfAborted(signal)
limit -= Buffer.byteLength(chunk)
if (limit < 0) {
return
}
}
} catch {
// Do nothing...
util.throwIfAborted(signal)
} finally {
if (signal) {
signal.removeEventListener('abort', abortFn)
}
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions deps/undici/src/lib/client.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-check

'use strict'

/* global WebAssembly */
Expand Down Expand Up @@ -85,7 +87,15 @@ try {
channels.connected = { hasSubscribers: false }
}

/**
* @type {import('../types/client').default}
*/
class Client extends DispatcherBase {
/**
*
* @param {string|URL} url
* @param {import('../types/client').Client.Options} options
*/
constructor (url, {
interceptors,
maxHeaderSize,
Expand Down Expand Up @@ -1658,6 +1668,8 @@ class AsyncWriter {
process.emitWarning(new RequestContentLengthMismatchError())
}

socket.cork()

if (bytesWritten === 0) {
if (!expectsPayload) {
socket[kReset] = true
Expand All @@ -1678,6 +1690,8 @@ class AsyncWriter {

const ret = socket.write(chunk)

socket.uncork()

request.onBodySent(chunk)

if (!ret) {
Expand Down
48 changes: 33 additions & 15 deletions deps/undici/src/lib/core/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(
function nop () {}

function isStream (obj) {
return obj && typeof obj.pipe === 'function'
return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
}

// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
Expand Down Expand Up @@ -46,6 +46,12 @@ function buildURL (url, queryParams) {
function parseURL (url) {
if (typeof url === 'string') {
url = new URL(url)

if (!/^https?:/.test(url.origin || url.protocol)) {
throw new InvalidArgumentError('invalid protocol')
}

return url
}

if (!url || typeof url !== 'object') {
Expand Down Expand Up @@ -375,23 +381,34 @@ function ReadableStreamFrom (iterable) {

// The chunk should be a FormData instance and contains
// all the required methods.
function isFormDataLike (chunk) {
return (chunk &&
chunk.constructor && chunk.constructor.name === 'FormData' &&
typeof chunk === 'object' &&
(typeof chunk.append === 'function' &&
typeof chunk.delete === 'function' &&
typeof chunk.get === 'function' &&
typeof chunk.getAll === 'function' &&
typeof chunk.has === 'function' &&
typeof chunk.set === 'function' &&
typeof chunk.entries === 'function' &&
typeof chunk.keys === 'function' &&
typeof chunk.values === 'function' &&
typeof chunk.forEach === 'function')
function isFormDataLike (object) {
return (
object &&
typeof object === 'object' &&
typeof object.append === 'function' &&
typeof object.delete === 'function' &&
typeof object.get === 'function' &&
typeof object.getAll === 'function' &&
typeof object.has === 'function' &&
typeof object.set === 'function' &&
object[Symbol.toStringTag] === 'FormData'
)
}

function throwIfAborted (signal) {
if (!signal) { return }
if (typeof signal.throwIfAborted === 'function') {
signal.throwIfAborted()
} else {
if (signal.aborted) {
// DOMException not available < v17.0.0
const err = new Error('The operation was aborted')
err.name = 'AbortError'
throw err
}
}
}

const kEnumerableProperty = Object.create(null)
kEnumerableProperty.enumerable = true

Expand Down Expand Up @@ -423,6 +440,7 @@ module.exports = {
getSocketInfo,
isFormDataLike,
buildURL,
throwIfAborted,
nodeMajor,
nodeMinor,
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13)
Expand Down
13 changes: 11 additions & 2 deletions deps/undici/src/lib/fetch/dataURL.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const assert = require('assert')
const { atob } = require('buffer')
const { format } = require('url')
const { isValidHTTPToken, isomorphicDecode } = require('./util')

const encoder = new TextEncoder()
Expand Down Expand Up @@ -118,7 +117,17 @@ function dataURLProcessor (dataURL) {
* @param {boolean} excludeFragment
*/
function URLSerializer (url, excludeFragment = false) {
return format(url, { fragment: !excludeFragment })
const href = url.href

if (!excludeFragment) {
return href
}

const hash = href.lastIndexOf('#')
if (hash === -1) {
return href
}
return href.slice(0, hash)
}

// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
Expand Down
2 changes: 1 addition & 1 deletion deps/undici/src/lib/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
// capability.
// TODO: given global’s relevant settings object’s cross-origin isolated
// capability?
response.timingInfo.endTime = coarsenedSharedCurrentTime()
timingInfo.endTime = coarsenedSharedCurrentTime()

// 10. Set response’s timing info to timingInfo.
response.timingInfo = timingInfo
Expand Down
24 changes: 16 additions & 8 deletions deps/undici/src/lib/fetch/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const util = require('../core/util')
const {
isValidHTTPToken,
sameOrigin,
normalizeMethod
normalizeMethod,
makePolicyContainer
} = require('./util')
const {
forbiddenMethods,
Expand Down Expand Up @@ -51,10 +52,14 @@ class Request {
input = webidl.converters.RequestInfo(input)
init = webidl.converters.RequestInit(init)

// TODO
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
this[kRealm] = {
settingsObject: {
baseUrl: getGlobalOrigin()
baseUrl: getGlobalOrigin(),
get origin () {
return this.baseUrl?.origin
},
policyContainer: makePolicyContainer()
}
}

Expand Down Expand Up @@ -349,14 +354,17 @@ class Request {
if (signal.aborted) {
ac.abort(signal.reason)
} else {
const acRef = new WeakRef(ac)
const abort = function () {
acRef.deref()?.abort(this.reason)
ac.abort(this.reason)
}

if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
setMaxListeners(100, signal)
}
// Third-party AbortControllers may not work with these.
// See https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619
try {
if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
setMaxListeners(100, signal)
}
} catch {}

signal.addEventListener('abort', abort, { once: true })
requestFinalizer.register(this, { signal, abort })
Expand Down
Loading