Skip to content
Open
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
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand Down Expand Up @@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand Down Expand Up @@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All @@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand Down Expand Up @@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All @@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading