undici 6.28.1 → 6.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -40,7 +40,8 @@ diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) =>
|
|
|
40
40
|
|
|
41
41
|
## `undici:request:headers`
|
|
42
42
|
|
|
43
|
-
This message is published after the response headers have been received
|
|
43
|
+
This message is published after the response headers have been received. This includes a successful CONNECT or
|
|
44
|
+
protocol upgrade response.
|
|
44
45
|
|
|
45
46
|
```js
|
|
46
47
|
import diagnosticsChannel from 'diagnostics_channel'
|
|
@@ -57,6 +58,7 @@ diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, respo
|
|
|
57
58
|
## `undici:request:trailers`
|
|
58
59
|
|
|
59
60
|
This message is published after the response body and trailers have been received, i.e. the response has been completed.
|
|
61
|
+
After an upgraded socket is passed to the request handler, this message is published with an empty `trailers` array.
|
|
60
62
|
|
|
61
63
|
```js
|
|
62
64
|
import diagnosticsChannel from 'diagnostics_channel'
|
package/lib/core/request.js
CHANGED
|
@@ -263,11 +263,77 @@ class Request {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
/**
|
|
267
|
+
* @param {number|null} statusCode
|
|
268
|
+
* @param {Buffer[]|null} headers
|
|
269
|
+
* @param {import('node:stream').Duplex} socket
|
|
270
|
+
* @param {string} [statusText]
|
|
271
|
+
*/
|
|
272
|
+
onUpgrade (statusCode, headers, socket, statusText = '') {
|
|
273
|
+
this.onFinally()
|
|
274
|
+
|
|
267
275
|
assert(!this.aborted)
|
|
268
276
|
assert(!this.completed)
|
|
269
277
|
|
|
270
|
-
|
|
278
|
+
if (statusCode !== null) {
|
|
279
|
+
this.#publishUpgradeHeaders(statusCode, headers, statusText)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const result = this[kHandler].onUpgrade(statusCode, headers, socket)
|
|
283
|
+
|
|
284
|
+
if (!this.aborted) {
|
|
285
|
+
this.completed = true
|
|
286
|
+
if (statusCode !== null) {
|
|
287
|
+
this.#publishUpgradeTrailers()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return result
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @param {number} statusCode
|
|
296
|
+
* @param {import('node:http2').IncomingHttpHeaders} headers
|
|
297
|
+
* @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders
|
|
298
|
+
* @param {string} [statusText]
|
|
299
|
+
*/
|
|
300
|
+
onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') {
|
|
301
|
+
assert(!this.aborted)
|
|
302
|
+
assert(this.completed)
|
|
303
|
+
|
|
304
|
+
if (channels.headers.hasSubscribers) {
|
|
305
|
+
this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText)
|
|
306
|
+
}
|
|
307
|
+
this.#publishUpgradeTrailers()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @param {Error} error
|
|
312
|
+
*/
|
|
313
|
+
onUpgradeError (error) {
|
|
314
|
+
assert(!this.aborted)
|
|
315
|
+
assert(this.completed)
|
|
316
|
+
|
|
317
|
+
if (channels.error.hasSubscribers) {
|
|
318
|
+
channels.error.publish({ request: this, error })
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @param {number} statusCode
|
|
324
|
+
* @param {Buffer[]} headers
|
|
325
|
+
* @param {string} statusText
|
|
326
|
+
*/
|
|
327
|
+
#publishUpgradeHeaders (statusCode, headers, statusText) {
|
|
328
|
+
if (channels.headers.hasSubscribers) {
|
|
329
|
+
channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#publishUpgradeTrailers () {
|
|
334
|
+
if (channels.trailers.hasSubscribers) {
|
|
335
|
+
channels.trailers.publish({ request: this, trailers: [] })
|
|
336
|
+
}
|
|
271
337
|
}
|
|
272
338
|
|
|
273
339
|
onComplete (trailers) {
|
|
@@ -432,7 +432,7 @@ class Parser {
|
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
onUpgrade (head) {
|
|
435
|
-
const { upgrade, client, socket, headers, statusCode } = this
|
|
435
|
+
const { upgrade, client, socket, headers, statusCode, statusText } = this
|
|
436
436
|
|
|
437
437
|
assert(upgrade)
|
|
438
438
|
assert(client[kSocket] === socket)
|
|
@@ -467,9 +467,10 @@ class Parser {
|
|
|
467
467
|
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
|
|
468
468
|
|
|
469
469
|
try {
|
|
470
|
-
request.onUpgrade(statusCode, headers, socket)
|
|
471
|
-
} catch (
|
|
472
|
-
util.
|
|
470
|
+
request.onUpgrade(statusCode, headers, socket, statusText)
|
|
471
|
+
} catch (error) {
|
|
472
|
+
util.errorRequest(client, request, error)
|
|
473
|
+
util.destroy(socket, error)
|
|
473
474
|
}
|
|
474
475
|
|
|
475
476
|
client[kResume]()
|
|
@@ -1050,12 +1051,22 @@ function writeH1 (client, request) {
|
|
|
1050
1051
|
const socket = client[kSocket]
|
|
1051
1052
|
clearIdleSocketValidation(socket)
|
|
1052
1053
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1054
|
+
/**
|
|
1055
|
+
* @param {Error} [error]
|
|
1056
|
+
*/
|
|
1057
|
+
const abort = (error) => {
|
|
1058
|
+
if (request.aborted) {
|
|
1059
|
+
return
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (request.completed) {
|
|
1063
|
+
if (request.upgrade || request.method === 'CONNECT') {
|
|
1064
|
+
util.destroy(socket, new InformationalError('aborted'))
|
|
1065
|
+
}
|
|
1055
1066
|
return
|
|
1056
1067
|
}
|
|
1057
1068
|
|
|
1058
|
-
util.errorRequest(client, request,
|
|
1069
|
+
util.errorRequest(client, request, error || new RequestAbortedError())
|
|
1059
1070
|
|
|
1060
1071
|
util.destroy(body)
|
|
1061
1072
|
util.destroy(socket, new InformationalError('aborted'))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
+
const { errorMonitor } = require('node:events')
|
|
4
5
|
const { pipeline } = require('node:stream')
|
|
5
6
|
const util = require('../core/util.js')
|
|
6
7
|
const {
|
|
@@ -77,6 +78,15 @@ function parseH2Headers (headers) {
|
|
|
77
78
|
return result
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @param {import('node:http2').IncomingHttpHeaders} headers
|
|
83
|
+
* @returns {Buffer[]}
|
|
84
|
+
*/
|
|
85
|
+
function parseH2ResponseHeaders (headers) {
|
|
86
|
+
const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers
|
|
87
|
+
return parseH2Headers(realHeaders)
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
async function connectH2 (client, socket) {
|
|
81
91
|
client[kSocket] = socket
|
|
82
92
|
|
|
@@ -297,22 +307,32 @@ function writeH2 (client, request) {
|
|
|
297
307
|
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
|
298
308
|
headers[HTTP2_HEADER_METHOD] = method
|
|
299
309
|
|
|
300
|
-
|
|
301
|
-
|
|
310
|
+
/**
|
|
311
|
+
* @param {Error} [error]
|
|
312
|
+
*/
|
|
313
|
+
const abort = (error) => {
|
|
314
|
+
if (request.aborted) {
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (request.completed) {
|
|
319
|
+
if (method === 'CONNECT' && stream != null) {
|
|
320
|
+
util.destroy(stream, error || new RequestAbortedError())
|
|
321
|
+
}
|
|
302
322
|
return
|
|
303
323
|
}
|
|
304
324
|
|
|
305
|
-
|
|
325
|
+
error = error || new RequestAbortedError()
|
|
306
326
|
|
|
307
|
-
util.errorRequest(client, request,
|
|
327
|
+
util.errorRequest(client, request, error)
|
|
308
328
|
|
|
309
329
|
if (stream != null) {
|
|
310
|
-
util.destroy(stream,
|
|
330
|
+
util.destroy(stream, error)
|
|
311
331
|
}
|
|
312
332
|
|
|
313
333
|
// We do not destroy the socket as we can continue using the session
|
|
314
334
|
// the stream get's destroyed and the session remains to create new streams
|
|
315
|
-
util.destroy(body,
|
|
335
|
+
util.destroy(body, error)
|
|
316
336
|
client[kQueue][client[kRunningIdx]++] = null
|
|
317
337
|
client[kResume]()
|
|
318
338
|
}
|
|
@@ -331,25 +351,57 @@ function writeH2 (client, request) {
|
|
|
331
351
|
|
|
332
352
|
if (method === 'CONNECT') {
|
|
333
353
|
session.ref()
|
|
334
|
-
// We are already connected, streams are pending, first request
|
|
335
|
-
// will create a new stream. We trigger a request to create the stream and wait until
|
|
336
|
-
// `ready` event is triggered
|
|
337
354
|
// We disabled endStream to allow the user to write to the stream
|
|
338
355
|
stream = session.request(headers, { endStream: false, signal })
|
|
356
|
+
let upgradeResponseFinished = false
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @param {import('node:http2').IncomingHttpHeaders} headers
|
|
360
|
+
*/
|
|
361
|
+
const onResponse = (headers) => {
|
|
362
|
+
upgradeResponseFinished = true
|
|
363
|
+
stream.off(errorMonitor, onUpgradeError)
|
|
364
|
+
request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders)
|
|
365
|
+
}
|
|
339
366
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
stream.
|
|
367
|
+
/**
|
|
368
|
+
* @param {Error} error
|
|
369
|
+
*/
|
|
370
|
+
const onUpgradeError = (error) => {
|
|
371
|
+
upgradeResponseFinished = true
|
|
372
|
+
stream.off('response', onResponse)
|
|
373
|
+
request.onUpgradeError(error)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const onReady = () => {
|
|
377
|
+
try {
|
|
346
378
|
request.onUpgrade(null, null, stream)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
379
|
+
} catch (error) {
|
|
380
|
+
stream.off('response', onResponse)
|
|
381
|
+
abort(error)
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (request.aborted) {
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
stream.off('error', abort)
|
|
390
|
+
stream.once(errorMonitor, onUpgradeError)
|
|
391
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
350
392
|
}
|
|
351
393
|
|
|
394
|
+
stream.once('response', onResponse)
|
|
395
|
+
stream.once('error', abort)
|
|
396
|
+
++session[kOpenStreams]
|
|
397
|
+
onReady()
|
|
398
|
+
|
|
352
399
|
stream.once('close', () => {
|
|
400
|
+
if (!upgradeResponseFinished && request.completed) {
|
|
401
|
+
stream.off('response', onResponse)
|
|
402
|
+
stream.off(errorMonitor, onUpgradeError)
|
|
403
|
+
request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
|
|
404
|
+
}
|
|
353
405
|
session[kOpenStreams] -= 1
|
|
354
406
|
if (session[kOpenStreams] === 0) session.unref()
|
|
355
407
|
})
|
|
@@ -205,7 +205,10 @@ class RetryHandler {
|
|
|
205
205
|
this.retryCount += 1
|
|
206
206
|
|
|
207
207
|
if (statusCode >= 300) {
|
|
208
|
-
if
|
|
208
|
+
// Only expose a response if no earlier attempt has reached the caller.
|
|
209
|
+
// Otherwise abort this attempt so the error settles the existing body
|
|
210
|
+
// instead of replacing it with a new response.
|
|
211
|
+
if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
209
212
|
this.headersSent = true
|
|
210
213
|
this.checkpointResponseEnd(headers, resume)
|
|
211
214
|
return this.handler.onHeaders(
|