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.14.0 #45812

Merged
merged 1 commit into from
Dec 13, 2022
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
4 changes: 3 additions & 1 deletion deps/undici/src/docs/api/Connector.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ Once you call `buildConnector`, it will return a connector function, which takes
* **hostname** `string` (required)
* **host** `string` (optional)
* **protocol** `string` (required)
* **port** `number` (required)
* **port** `string` (required)
* **servername** `string` (optional)
* **localAddress** `string | null` (optional) Local address the socket should connect from.
* **httpSocket** `Socket` (optional) Establish secure connection on a given socket rather than creating a new socket. It can only be sent on TLS update.

### Basic example

Expand Down
81 changes: 62 additions & 19 deletions deps/undici/src/lib/core/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,81 @@ const net = require('net')
const assert = require('assert')
const util = require('./util')
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')

let tls // include tls conditionally since it is not always available

// TODO: session re-use does not wait for the first
// connection to resolve the session and might therefore
// resolve the same servername multiple times even when
// re-use is enabled.

let SessionCache
if (global.FinalizationRegistry) {
SessionCache = class WeakSessionCache {
constructor (maxCachedSessions) {
this._maxCachedSessions = maxCachedSessions
this._sessionCache = new Map()
this._sessionRegistry = new global.FinalizationRegistry((key) => {
if (this._sessionCache.size < this._maxCachedSessions) {
return
}

const ref = this._sessionCache.get(key)
if (ref !== undefined && ref.deref() === undefined) {
this._sessionCache.delete(key)
}
})
}

get (sessionKey) {
const ref = this._sessionCache.get(sessionKey)
return ref ? ref.deref() : null
}

set (sessionKey, session) {
if (this._maxCachedSessions === 0) {
return
}

this._sessionCache.set(sessionKey, new WeakRef(session))
this._sessionRegistry.register(session, sessionKey)
}
}
} else {
SessionCache = class SimpleSessionCache {
constructor (maxCachedSessions) {
this._maxCachedSessions = maxCachedSessions
this._sessionCache = new Map()
}

get (sessionKey) {
return this._sessionCache.get(sessionKey)
}

set (sessionKey, session) {
if (this._maxCachedSessions === 0) {
return
}

if (this._sessionCache.size >= this._maxCachedSessions) {
// remove the oldest session
const { value: oldestKey } = this._sessionCache.keys().next()
this._sessionCache.delete(oldestKey)
}

this._sessionCache.set(sessionKey, session)
}
}
}

function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
}

const options = { path: socketPath, ...opts }
const sessionCache = new Map()
const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions)
timeout = timeout == null ? 10e3 : timeout
maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions

return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
let socket
Expand Down Expand Up @@ -47,25 +106,9 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {

socket
.on('session', function (session) {
// cache is disabled
if (maxCachedSessions === 0) {
return
}

if (sessionCache.size >= maxCachedSessions) {
// remove the oldest session
const { value: oldestKey } = sessionCache.keys().next()
sessionCache.delete(oldestKey)
}

// TODO (fix): Can a session become invalid once established? Don't think so?
sessionCache.set(sessionKey, session)
})
.on('error', function (err) {
if (sessionKey && err.code !== 'UND_ERR_INFO') {
// TODO (fix): Only delete for session related errors.
sessionCache.delete(sessionKey)
}
})
} else {
assert(!httpSocket, 'httpSocket can only be sent on TLS update')
socket = net.connect({
Expand Down
45 changes: 38 additions & 7 deletions deps/undici/src/lib/fetch/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ const { FormData } = require('./formdata')
const { kState } = require('./symbols')
const { webidl } = require('./webidl')
const { DOMException, structuredClone } = require('./constants')
const { Blob } = require('buffer')
const { Blob, File: NativeFile } = require('buffer')
const { kBodyUsed } = require('../core/symbols')
const assert = require('assert')
const { isErrored } = require('../core/util')
const { isUint8Array, isArrayBuffer } = require('util/types')
const { File } = require('./file')
const { File: UndiciFile } = require('./file')
const { StringDecoder } = require('string_decoder')
const { parseMIMEType, serializeAMimeType } = require('./dataURL')

/** @type {globalThis['ReadableStream']} */
let ReadableStream
let ReadableStream = globalThis.ReadableStream

/** @type {globalThis['File']} */
const File = NativeFile ?? UndiciFile

// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
function extractBody (object, keepalive = false) {
Expand Down Expand Up @@ -142,7 +144,33 @@ function extractBody (object, keepalive = false) {
source = object

// Set length to unclear, see html/6424 for improving this.
// TODO
length = (() => {
const prefixLength = prefix.length
const boundaryLength = boundary.length
let bodyLength = 0

for (const [name, value] of object) {
if (typeof value === 'string') {
bodyLength +=
prefixLength +
Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
} else {
bodyLength +=
prefixLength +
Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : '')) +
2 + // \r\n
`Content-Type: ${
value.type || 'application/octet-stream'
}\r\n\r\n`.length

// value is a Blob or File, and \r\n
bodyLength += value.size + 2
}
}

bodyLength += boundaryLength + 4 // --boundary--
return bodyLength
})()

// Set type to `multipart/form-data; boundary=`,
// followed by the multipart/form-data boundary string generated
Expand Down Expand Up @@ -348,7 +376,10 @@ function bodyMixinMethods (instance) {
let busboy

try {
busboy = Busboy({ headers })
busboy = Busboy({
headers,
defParamCharset: 'utf8'
})
} catch (err) {
// Error due to headers:
throw Object.assign(new TypeError(), { cause: err })
Expand All @@ -361,7 +392,7 @@ function bodyMixinMethods (instance) {
const { filename, encoding, mimeType } = info
const chunks = []

if (encoding.toLowerCase() === 'base64') {
if (encoding === 'base64' || encoding.toLowerCase() === 'base64') {
let base64chunk = ''

value.on('data', (chunk) => {
Expand Down
15 changes: 9 additions & 6 deletions deps/undici/src/lib/fetch/file.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const { Blob } = require('buffer')
const { Blob, File: NativeFile } = require('buffer')
const { types } = require('util')
const { kState } = require('./symbols')
const { isBlobLike } = require('./util')
Expand Down Expand Up @@ -329,11 +329,14 @@ function convertLineEndingsNative (s) {
// rollup) will warn about circular dependencies. See:
// https://github.com/nodejs/undici/issues/1629
function isFileLike (object) {
return object instanceof File || (
object &&
(typeof object.stream === 'function' ||
typeof object.arrayBuffer === 'function') &&
object[Symbol.toStringTag] === 'File'
return (
(NativeFile && object instanceof NativeFile) ||
object instanceof File || (
object &&
(typeof object.stream === 'function' ||
typeof object.arrayBuffer === 'function') &&
object[Symbol.toStringTag] === 'File'
)
)
}

Expand Down
7 changes: 5 additions & 2 deletions deps/undici/src/lib/fetch/formdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

const { isBlobLike, toUSVString, makeIterator } = require('./util')
const { kState } = require('./symbols')
const { File, FileLike, isFileLike } = require('./file')
const { File: UndiciFile, FileLike, isFileLike } = require('./file')
const { webidl } = require('./webidl')
const { Blob } = require('buffer')
const { Blob, File: NativeFile } = require('buffer')

/** @type {globalThis['File']} */
const File = NativeFile ?? UndiciFile

// https://xhr.spec.whatwg.org/#formdata
class FormData {
Expand Down
32 changes: 22 additions & 10 deletions deps/undici/src/lib/fetch/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'use strict'

const { kHeadersList } = require('../core/symbols')
const { kGuard } = require('./symbols')
const { kGuard, kHeadersCaseInsensitive } = require('./symbols')
const { kEnumerableProperty } = require('../core/util')
const {
makeIterator,
Expand Down Expand Up @@ -96,27 +96,27 @@ class HeadersList {

// 1. If list contains name, then set name to the first such
// header’s name.
name = name.toLowerCase()
const exists = this[kHeadersMap].get(name)
const lowercaseName = name.toLowerCase()
const exists = this[kHeadersMap].get(lowercaseName)

// 2. Append (name, value) to list.
if (exists) {
this[kHeadersMap].set(name, `${exists}, ${value}`)
this[kHeadersMap].set(lowercaseName, { name: exists.name, value: `${exists.value}, ${value}` })
} else {
this[kHeadersMap].set(name, `${value}`)
this[kHeadersMap].set(lowercaseName, { name, value })
}
}

// https://fetch.spec.whatwg.org/#concept-header-list-set
set (name, value) {
this[kHeadersSortedMap] = null
name = name.toLowerCase()
const lowercaseName = name.toLowerCase()

// 1. If list contains name, then set the value of
// the first such header to value and remove the
// others.
// 2. Otherwise, append header (name, value) to list.
return this[kHeadersMap].set(name, value)
return this[kHeadersMap].set(lowercaseName, { name, value })
}

// https://fetch.spec.whatwg.org/#concept-header-list-delete
Expand All @@ -137,14 +137,26 @@ class HeadersList {
// 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()) ?? null
return this[kHeadersMap].get(name.toLowerCase())?.value ?? null
}

* [Symbol.iterator] () {
for (const pair of this[kHeadersMap]) {
yield pair
// use the lowercased name
for (const [name, { value }] of this[kHeadersMap]) {
yield [name, value]
}
}

get [kHeadersCaseInsensitive] () {
/** @type {string[]} */
const flatList = []

for (const { name, value } of this[kHeadersMap].values()) {
flatList.push(name, value)
}

return flatList
}
}

// https://fetch.spec.whatwg.org/#headers-class
Expand Down
Loading