Skip to content

Commit ca825a8

Browse files
committed
deps: update undici to 6.27.0
PR-URL: #63711 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Richard Lau <richard.lau@ibm.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent a1a5bb9 commit ca825a8

18 files changed

Lines changed: 586 additions & 198 deletions

File tree

deps/undici/src/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,9 @@ undici-fetch.js
8787

8888
# File generated by /test/request-timeout.js
8989
test/request-timeout.10mb.bin
90+
91+
# Local agent configuration
92+
CLAUDE.md
93+
AGENTS.md
94+
.pi/
95+
.claude/

deps/undici/src/docs/docs/api/Client.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ Returns: `Client`
2626
* **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `2e3` - A number of milliseconds subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 2 seconds.
2727
* **maxHeaderSize** `number | null` (optional) - Default: `--max-http-header-size` or `16384` - The maximum length of request headers in bytes. Defaults to Node.js' --max-http-header-size or 16KiB.
2828
* **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable.
29+
* **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options.
30+
* **maxFragments** `number` (optional) - Default: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit.
31+
* **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit.
2932
* **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections.
3033
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
3134
* **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.

deps/undici/src/lib/dispatcher/agent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ function defaultFactory (origin, opts) {
2424

2525
class Agent extends DispatcherBase {
2626
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
27-
super()
28-
2927
if (typeof factory !== 'function') {
3028
throw new InvalidArgumentError('factory must be a function.')
3129
}
@@ -38,6 +36,8 @@ class Agent extends DispatcherBase {
3836
throw new InvalidArgumentError('maxRedirections must be a positive number')
3937
}
4038

39+
super(options)
40+
4141
if (connect && typeof connect !== 'function') {
4242
connect = { ...connect }
4343
}

deps/undici/src/lib/dispatcher/client-h1.js

Lines changed: 140 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ const EMPTY_BUF = Buffer.alloc(0)
5757
const FastBuffer = Buffer[Symbol.species]
5858
const addListener = util.addListener
5959
const removeAllListeners = util.removeAllListeners
60+
const kIdleSocketValidation = Symbol('kIdleSocketValidation')
61+
const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
62+
const kSocketUsed = Symbol('kSocketUsed')
6063

6164
let extractBody
6265

@@ -279,29 +282,71 @@ class Parser {
279282

280283
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
281284

282-
if (ret === constants.ERROR.PAUSED_UPGRADE) {
283-
this.onUpgrade(data.slice(offset))
284-
} else if (ret === constants.ERROR.PAUSED) {
285-
this.paused = true
286-
socket.unshift(data.slice(offset))
287-
} else if (ret !== constants.ERROR.OK) {
288-
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
289-
let message = ''
290-
/* istanbul ignore else: difficult to make a test case for */
291-
if (ptr) {
292-
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
293-
message =
294-
'Response does not match the HTTP/1.1 protocol (' +
295-
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
296-
')'
285+
if (ret !== constants.ERROR.OK) {
286+
const body = data.subarray(offset)
287+
288+
if (ret === constants.ERROR.PAUSED_UPGRADE) {
289+
this.onUpgrade(body)
290+
} else if (ret === constants.ERROR.PAUSED) {
291+
this.paused = true
292+
socket.unshift(body)
293+
} else {
294+
throw this.createError(ret, body)
297295
}
298-
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
299296
}
300297
} catch (err) {
301298
util.destroy(socket, err)
302299
}
303300
}
304301

302+
finish () {
303+
assert(currentParser === null)
304+
assert(this.ptr != null)
305+
assert(!this.paused)
306+
307+
const { llhttp } = this
308+
309+
let ret
310+
311+
try {
312+
currentParser = this
313+
ret = llhttp.llhttp_finish(this.ptr)
314+
} finally {
315+
currentParser = null
316+
}
317+
318+
if (ret === constants.ERROR.OK) {
319+
return null
320+
}
321+
322+
if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
323+
this.paused = true
324+
return null
325+
}
326+
327+
return this.createError(ret, EMPTY_BUF)
328+
}
329+
330+
createError (ret, data) {
331+
const { llhttp, contentLength, bytesRead } = this
332+
333+
if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
334+
return new ResponseContentLengthMismatchError()
335+
}
336+
337+
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
338+
let message = ''
339+
if (ptr) {
340+
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
341+
message =
342+
'Response does not match the HTTP/1.1 protocol (' +
343+
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
344+
')'
345+
}
346+
347+
return new HTTPParserError(message, constants.ERROR[ret], data)
348+
}
349+
305350
destroy () {
306351
assert(this.ptr != null)
307352
assert(currentParser == null)
@@ -329,6 +374,11 @@ class Parser {
329374
return -1
330375
}
331376

377+
if (client[kRunning] === 0) {
378+
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
379+
return -1
380+
}
381+
332382
const request = client[kQueue][client[kRunningIdx]]
333383
if (!request) {
334384
return -1
@@ -432,6 +482,11 @@ class Parser {
432482
return -1
433483
}
434484

485+
if (client[kRunning] === 0) {
486+
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
487+
return -1
488+
}
489+
435490
const request = client[kQueue][client[kRunningIdx]]
436491

437492
/* istanbul ignore next: difficult to make a test case for */
@@ -605,6 +660,7 @@ class Parser {
605660
request.onComplete(headers)
606661

607662
client[kQueue][client[kRunningIdx]++] = null
663+
socket[kSocketUsed] = true
608664

609665
if (socket[kWriting]) {
610666
assert(client[kRunning] === 0)
@@ -663,6 +719,9 @@ async function connectH1 (client, socket) {
663719
socket[kWriting] = false
664720
socket[kReset] = false
665721
socket[kBlocking] = false
722+
socket[kIdleSocketValidation] = 0
723+
socket[kIdleSocketValidationTimeout] = null
724+
socket[kSocketUsed] = false
666725
socket[kParser] = new Parser(client, socket, llhttpInstance)
667726

668727
addListener(socket, 'error', function (err) {
@@ -673,8 +732,11 @@ async function connectH1 (client, socket) {
673732
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
674733
// to the user.
675734
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
676-
// We treat all incoming data so for as a valid response.
677-
parser.onMessageComplete()
735+
const parserErr = parser.finish()
736+
if (parserErr) {
737+
this[kError] = parserErr
738+
this[kClient][kOnError](parserErr)
739+
}
678740
return
679741
}
680742

@@ -693,8 +755,10 @@ async function connectH1 (client, socket) {
693755
const parser = this[kParser]
694756

695757
if (parser.statusCode && !parser.shouldKeepAlive) {
696-
// We treat all incoming data so far as a valid response.
697-
parser.onMessageComplete()
758+
const parserErr = parser.finish()
759+
if (parserErr) {
760+
util.destroy(this, parserErr)
761+
}
698762
return
699763
}
700764

@@ -704,10 +768,11 @@ async function connectH1 (client, socket) {
704768
const client = this[kClient]
705769
const parser = this[kParser]
706770

771+
clearIdleSocketValidation(this)
772+
707773
if (parser) {
708774
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
709-
// We treat all incoming data so far as a valid response.
710-
parser.onMessageComplete()
775+
this[kError] = parser.finish() || this[kError]
711776
}
712777

713778
this[kParser].destroy()
@@ -770,7 +835,7 @@ async function connectH1 (client, socket) {
770835
return socket.destroyed
771836
},
772837
busy (request) {
773-
if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
838+
if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
774839
return true
775840
}
776841

@@ -808,6 +873,31 @@ async function connectH1 (client, socket) {
808873
}
809874
}
810875

876+
function clearIdleSocketValidation (socket) {
877+
if (socket[kIdleSocketValidationTimeout]) {
878+
clearTimeout(socket[kIdleSocketValidationTimeout])
879+
socket[kIdleSocketValidationTimeout] = null
880+
}
881+
882+
socket[kIdleSocketValidation] = 0
883+
}
884+
885+
function scheduleIdleSocketValidation (client, socket) {
886+
socket[kIdleSocketValidation] = 1
887+
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
888+
socket[kIdleSocketValidationTimeout] = null
889+
socket[kIdleSocketValidation] = 2
890+
891+
if (client[kSocket] === socket && !socket.destroyed) {
892+
client[kResume]()
893+
}
894+
}, 0)
895+
socket[kIdleSocketValidationTimeout].unref?.()
896+
}
897+
898+
/**
899+
* @param {import('./client.js')} client
900+
*/
811901
function resumeH1 (client) {
812902
const socket = client[kSocket]
813903

@@ -822,6 +912,32 @@ function resumeH1 (client) {
822912
socket[kNoRef] = false
823913
}
824914

915+
if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
916+
if (socket[kIdleSocketValidation] === 0) {
917+
scheduleIdleSocketValidation(client, socket)
918+
socket[kParser].readMore()
919+
if (socket.destroyed) {
920+
return
921+
}
922+
return
923+
}
924+
925+
if (socket[kIdleSocketValidation] === 1) {
926+
socket[kParser].readMore()
927+
if (socket.destroyed) {
928+
return
929+
}
930+
return
931+
}
932+
}
933+
934+
if (client[kRunning] === 0) {
935+
socket[kParser].readMore()
936+
if (socket.destroyed) {
937+
return
938+
}
939+
}
940+
825941
if (client[kSize] === 0) {
826942
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
827943
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
@@ -915,6 +1031,7 @@ function writeH1 (client, request) {
9151031
}
9161032

9171033
const socket = client[kSocket]
1034+
clearIdleSocketValidation(socket)
9181035

9191036
const abort = (err) => {
9201037
if (request.aborted || request.completed) {

deps/undici/src/lib/dispatcher/client.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,10 @@ class Client extends DispatcherBase {
106106
autoSelectFamilyAttemptTimeout,
107107
// h2
108108
maxConcurrentStreams,
109-
allowH2
109+
allowH2,
110+
webSocket
110111
} = {}) {
111-
super()
112+
super({ webSocket })
112113

113114
if (keepAlive !== undefined) {
114115
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')

deps/undici/src/lib/dispatcher/dispatcher-base.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = requ
1111
const kOnDestroyed = Symbol('onDestroyed')
1212
const kOnClosed = Symbol('onClosed')
1313
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
14+
const kWebSocketOptions = Symbol('webSocketOptions')
1415

1516
class DispatcherBase extends Dispatcher {
16-
constructor () {
17+
constructor (opts) {
1718
super()
1819

1920
this[kDestroyed] = false
2021
this[kOnDestroyed] = null
2122
this[kClosed] = false
2223
this[kOnClosed] = []
24+
this[kWebSocketOptions] = opts?.webSocket ?? {}
25+
}
26+
27+
get webSocketOptions () {
28+
return {
29+
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
30+
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
31+
}
2332
}
2433

2534
get destroyed () {

deps/undici/src/lib/dispatcher/pool-base.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const kRemoveClient = Symbol('remove client')
1919
const kStats = Symbol('stats')
2020

2121
class PoolBase extends DispatcherBase {
22-
constructor () {
23-
super()
22+
constructor (opts) {
23+
super(opts)
2424

2525
this[kQueue] = new FixedQueue()
2626
this[kClients] = []

deps/undici/src/lib/dispatcher/pool.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ class Pool extends PoolBase {
3737
allowH2,
3838
...options
3939
} = {}) {
40-
super()
41-
4240
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
4341
throw new InvalidArgumentError('invalid connections')
4442
}
@@ -63,6 +61,8 @@ class Pool extends PoolBase {
6361
})
6462
}
6563

64+
super(options)
65+
6666
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
6767
? options.interceptors.Pool
6868
: []

0 commit comments

Comments
 (0)