undici 6.18.2 → 6.19.1

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.
@@ -19,7 +19,7 @@ Returns: `Client`
19
19
 
20
20
  > ⚠️ Warning: The `H2` support is experimental.
21
21
 
22
- * **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
22
+ * **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds. Please note the `timeout` will be reset if you keep writing data to the scoket everytime.
23
23
  * **headersTimeout** `number | null` (optional) - Default: `300e3` - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
24
24
  * **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout`, in milliseconds, when overridden by *keep-alive* hints from the server. Defaults to 10 minutes.
25
25
  * **keepAliveTimeout** `number | null` (optional) - Default: `4e3` - The timeout, in milliseconds, after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overridden by *keep-alive* hints from the server. See [MDN: HTTP - Headers - Keep-Alive directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive#directives) for more details. Defaults to 4 seconds.
@@ -73,7 +73,7 @@ if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env
73
73
  }
74
74
  }
75
75
 
76
- function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) {
76
+ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) {
77
77
  if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
78
78
  throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
79
79
  }
@@ -91,7 +91,7 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
91
91
  servername = servername || options.servername || util.getServerName(host) || null
92
92
 
93
93
  const sessionKey = servername || hostname
94
- const session = sessionCache.get(sessionKey) || null
94
+ const session = customSession || sessionCache.get(sessionKey) || null
95
95
 
96
96
  assert(sessionKey)
97
97
 
@@ -16,7 +16,8 @@ const {
16
16
  isBlobLike,
17
17
  buildURL,
18
18
  validateHandler,
19
- getServerName
19
+ getServerName,
20
+ normalizedMethodRecords
20
21
  } = require('./util')
21
22
  const { channels } = require('./diagnostics.js')
22
23
  const { headerNameLowerCasedRecord } = require('./constants')
@@ -51,13 +52,13 @@ class Request {
51
52
  method !== 'CONNECT'
52
53
  ) {
53
54
  throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
54
- } else if (invalidPathRegex.exec(path) !== null) {
55
+ } else if (invalidPathRegex.test(path)) {
55
56
  throw new InvalidArgumentError('invalid request path')
56
57
  }
57
58
 
58
59
  if (typeof method !== 'string') {
59
60
  throw new InvalidArgumentError('method must be a string')
60
- } else if (!isValidHTTPToken(method)) {
61
+ } else if (normalizedMethodRecords[method] === undefined && !isValidHTTPToken(method)) {
61
62
  throw new InvalidArgumentError('invalid request method')
62
63
  }
63
64
 
package/lib/core/util.js CHANGED
@@ -645,6 +645,31 @@ function errorRequest (client, request, err) {
645
645
  const kEnumerableProperty = Object.create(null)
646
646
  kEnumerableProperty.enumerable = true
647
647
 
648
+ const normalizedMethodRecordsBase = {
649
+ delete: 'DELETE',
650
+ DELETE: 'DELETE',
651
+ get: 'GET',
652
+ GET: 'GET',
653
+ head: 'HEAD',
654
+ HEAD: 'HEAD',
655
+ options: 'OPTIONS',
656
+ OPTIONS: 'OPTIONS',
657
+ post: 'POST',
658
+ POST: 'POST',
659
+ put: 'PUT',
660
+ PUT: 'PUT'
661
+ }
662
+
663
+ const normalizedMethodRecords = {
664
+ ...normalizedMethodRecordsBase,
665
+ patch: 'patch',
666
+ PATCH: 'PATCH'
667
+ }
668
+
669
+ // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
670
+ Object.setPrototypeOf(normalizedMethodRecordsBase, null)
671
+ Object.setPrototypeOf(normalizedMethodRecords, null)
672
+
648
673
  module.exports = {
649
674
  kEnumerableProperty,
650
675
  nop,
@@ -683,6 +708,8 @@ module.exports = {
683
708
  isValidHeaderValue,
684
709
  isTokenCharCode,
685
710
  parseRangeHeader,
711
+ normalizedMethodRecordsBase,
712
+ normalizedMethodRecords,
686
713
  isValidPort,
687
714
  isHttpOrHttpsPrefixed,
688
715
  nodeMajor,
@@ -978,19 +978,19 @@ function writeH1 (client, request) {
978
978
 
979
979
  /* istanbul ignore else: assertion */
980
980
  if (!body || bodyLength === 0) {
981
- writeBuffer({ abort, body: null, client, request, socket, contentLength, header, expectsPayload })
981
+ writeBuffer(abort, null, client, request, socket, contentLength, header, expectsPayload)
982
982
  } else if (util.isBuffer(body)) {
983
- writeBuffer({ abort, body, client, request, socket, contentLength, header, expectsPayload })
983
+ writeBuffer(abort, body, client, request, socket, contentLength, header, expectsPayload)
984
984
  } else if (util.isBlobLike(body)) {
985
985
  if (typeof body.stream === 'function') {
986
- writeIterable({ abort, body: body.stream(), client, request, socket, contentLength, header, expectsPayload })
986
+ writeIterable(abort, body.stream(), client, request, socket, contentLength, header, expectsPayload)
987
987
  } else {
988
- writeBlob({ abort, body, client, request, socket, contentLength, header, expectsPayload })
988
+ writeBlob(abort, body, client, request, socket, contentLength, header, expectsPayload)
989
989
  }
990
990
  } else if (util.isStream(body)) {
991
- writeStream({ abort, body, client, request, socket, contentLength, header, expectsPayload })
991
+ writeStream(abort, body, client, request, socket, contentLength, header, expectsPayload)
992
992
  } else if (util.isIterable(body)) {
993
- writeIterable({ abort, body, client, request, socket, contentLength, header, expectsPayload })
993
+ writeIterable(abort, body, client, request, socket, contentLength, header, expectsPayload)
994
994
  } else {
995
995
  assert(false)
996
996
  }
@@ -998,7 +998,7 @@ function writeH1 (client, request) {
998
998
  return true
999
999
  }
1000
1000
 
1001
- function writeStream ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1001
+ function writeStream (abort, body, client, request, socket, contentLength, header, expectsPayload) {
1002
1002
  assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
1003
1003
 
1004
1004
  let finished = false
@@ -1101,7 +1101,7 @@ function writeStream ({ abort, body, client, request, socket, contentLength, hea
1101
1101
  }
1102
1102
  }
1103
1103
 
1104
- function writeBuffer ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1104
+ function writeBuffer (abort, body, client, request, socket, contentLength, header, expectsPayload) {
1105
1105
  try {
1106
1106
  if (!body) {
1107
1107
  if (contentLength === 0) {
@@ -1131,7 +1131,7 @@ function writeBuffer ({ abort, body, client, request, socket, contentLength, hea
1131
1131
  }
1132
1132
  }
1133
1133
 
1134
- async function writeBlob ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1134
+ async function writeBlob (abort, body, client, request, socket, contentLength, header, expectsPayload) {
1135
1135
  assert(contentLength === body.size, 'blob body must have content length')
1136
1136
 
1137
1137
  try {
@@ -1159,7 +1159,7 @@ async function writeBlob ({ abort, body, client, request, socket, contentLength,
1159
1159
  }
1160
1160
  }
1161
1161
 
1162
- async function writeIterable ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1162
+ async function writeIterable (abort, body, client, request, socket, contentLength, header, expectsPayload) {
1163
1163
  assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
1164
1164
 
1165
1165
  let callback = null
@@ -477,82 +477,80 @@ function writeH2 (client, request) {
477
477
  function writeBodyH2 () {
478
478
  /* istanbul ignore else: assertion */
479
479
  if (!body || contentLength === 0) {
480
- writeBuffer({
480
+ writeBuffer(
481
481
  abort,
482
+ stream,
483
+ null,
482
484
  client,
483
485
  request,
486
+ client[kSocket],
484
487
  contentLength,
485
- expectsPayload,
486
- h2stream: stream,
487
- body: null,
488
- socket: client[kSocket]
489
- })
488
+ expectsPayload
489
+ )
490
490
  } else if (util.isBuffer(body)) {
491
- writeBuffer({
491
+ writeBuffer(
492
492
  abort,
493
+ stream,
494
+ body,
493
495
  client,
494
496
  request,
497
+ client[kSocket],
495
498
  contentLength,
496
- body,
497
- expectsPayload,
498
- h2stream: stream,
499
- socket: client[kSocket]
500
- })
499
+ expectsPayload
500
+ )
501
501
  } else if (util.isBlobLike(body)) {
502
502
  if (typeof body.stream === 'function') {
503
- writeIterable({
503
+ writeIterable(
504
504
  abort,
505
+ stream,
506
+ body.stream(),
505
507
  client,
506
508
  request,
509
+ client[kSocket],
507
510
  contentLength,
508
- expectsPayload,
509
- h2stream: stream,
510
- body: body.stream(),
511
- socket: client[kSocket]
512
- })
511
+ expectsPayload
512
+ )
513
513
  } else {
514
- writeBlob({
514
+ writeBlob(
515
515
  abort,
516
+ stream,
516
517
  body,
517
518
  client,
518
519
  request,
520
+ client[kSocket],
519
521
  contentLength,
520
- expectsPayload,
521
- h2stream: stream,
522
- socket: client[kSocket]
523
- })
522
+ expectsPayload
523
+ )
524
524
  }
525
525
  } else if (util.isStream(body)) {
526
- writeStream({
526
+ writeStream(
527
527
  abort,
528
+ client[kSocket],
529
+ expectsPayload,
530
+ stream,
528
531
  body,
529
532
  client,
530
533
  request,
531
- contentLength,
532
- expectsPayload,
533
- socket: client[kSocket],
534
- h2stream: stream,
535
- header: ''
536
- })
534
+ contentLength
535
+ )
537
536
  } else if (util.isIterable(body)) {
538
- writeIterable({
537
+ writeIterable(
539
538
  abort,
539
+ stream,
540
540
  body,
541
541
  client,
542
542
  request,
543
+ client[kSocket],
543
544
  contentLength,
544
- expectsPayload,
545
- header: '',
546
- h2stream: stream,
547
- socket: client[kSocket]
548
- })
545
+ expectsPayload
546
+ )
549
547
  } else {
550
548
  assert(false)
551
549
  }
552
550
  }
553
551
  }
554
552
 
555
- function writeBuffer ({ abort, h2stream, body, client, request, socket, contentLength, expectsPayload }) {
553
+ function writeBuffer (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
556
554
  try {
557
555
  if (body != null && util.isBuffer(body)) {
558
556
  assert(contentLength === body.byteLength, 'buffer body must have content length')
@@ -575,7 +573,7 @@ function writeBuffer ({ abort, h2stream, body, client, request, socket, contentL
575
573
  }
576
574
  }
577
575
 
578
- function writeStream ({ abort, socket, expectsPayload, h2stream, body, client, request, contentLength }) {
576
+ function writeStream (abort, socket, expectsPayload, h2stream, body, client, request, contentLength) {
579
577
  assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
580
578
 
581
579
  // For HTTP/2, is enough to pipe the stream
@@ -606,7 +604,7 @@ function writeStream ({ abort, socket, expectsPayload, h2stream, body, client, r
606
604
  }
607
605
  }
608
606
 
609
- async function writeBlob ({ abort, h2stream, body, client, request, socket, contentLength, expectsPayload }) {
607
+ async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
610
608
  assert(contentLength === body.size, 'blob body must have content length')
611
609
 
612
610
  try {
@@ -634,7 +632,7 @@ async function writeBlob ({ abort, h2stream, body, client, request, socket, cont
634
632
  }
635
633
  }
636
634
 
637
- async function writeIterable ({ abort, h2stream, body, client, request, socket, contentLength, expectsPayload }) {
635
+ async function writeIterable (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
638
636
  assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
639
637
 
640
638
  let callback = null
@@ -202,7 +202,7 @@ class RetryHandler {
202
202
  this.abort(
203
203
  new RequestRetryError('Content-Range mismatch', statusCode, {
204
204
  headers,
205
- count: this.retryCount
205
+ data: { count: this.retryCount }
206
206
  })
207
207
  )
208
208
  return false
@@ -213,7 +213,7 @@ class RetryHandler {
213
213
  this.abort(
214
214
  new RequestRetryError('ETag mismatch', statusCode, {
215
215
  headers,
216
- count: this.retryCount
216
+ data: { count: this.retryCount }
217
217
  })
218
218
  )
219
219
  return false
@@ -10,9 +10,7 @@ const nodeUtil = require('node:util')
10
10
  const {
11
11
  isValidHTTPToken,
12
12
  sameOrigin,
13
- normalizeMethod,
14
- environmentSettingsObject,
15
- normalizeMethodRecord
13
+ environmentSettingsObject
16
14
  } = require('./util')
17
15
  const {
18
16
  forbiddenMethodsSet,
@@ -24,7 +22,7 @@ const {
24
22
  requestCache,
25
23
  requestDuplex
26
24
  } = require('./constants')
27
- const { kEnumerableProperty } = util
25
+ const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util
28
26
  const { kHeaders, kSignal, kState, kDispatcher } = require('./symbols')
29
27
  const { webidl } = require('./webidl')
30
28
  const { URLSerializer } = require('./data-url')
@@ -349,7 +347,7 @@ class Request {
349
347
  // 1. Let method be init["method"].
350
348
  let method = init.method
351
349
 
352
- const mayBeNormalized = normalizeMethodRecord[method]
350
+ const mayBeNormalized = normalizedMethodRecords[method]
353
351
 
354
352
  if (mayBeNormalized !== undefined) {
355
353
  // Note: Bypass validation DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH and these lowercase ones
@@ -361,12 +359,16 @@ class Request {
361
359
  throw new TypeError(`'${method}' is not a valid HTTP method.`)
362
360
  }
363
361
 
364
- if (forbiddenMethodsSet.has(method.toUpperCase())) {
362
+ const upperCase = method.toUpperCase()
363
+
364
+ if (forbiddenMethodsSet.has(upperCase)) {
365
365
  throw new TypeError(`'${method}' HTTP method is unsupported.`)
366
366
  }
367
367
 
368
368
  // 3. Normalize method.
369
- method = normalizeMethod(method)
369
+ // https://fetch.spec.whatwg.org/#concept-method-normalize
370
+ // Note: must be in uppercase
371
+ method = normalizedMethodRecordsBase[upperCase] ?? method
370
372
 
371
373
  // 4. Set request’s method to method.
372
374
  request.method = method
@@ -6,7 +6,7 @@ const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet
6
6
  const { getGlobalOrigin } = require('./global')
7
7
  const { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require('./data-url')
8
8
  const { performance } = require('node:perf_hooks')
9
- const { isBlobLike, ReadableStreamFrom, isValidHTTPToken } = require('../../core/util')
9
+ const { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
10
10
  const assert = require('node:assert')
11
11
  const { isUint8Array } = require('node:util/types')
12
12
  const { webidl } = require('./webidl')
@@ -260,10 +260,13 @@ function appendRequestOriginHeader (request) {
260
260
  // TODO: implement "byte-serializing a request origin"
261
261
  let serializedOrigin = request.origin
262
262
 
263
- // "'client' is changed to an origin during fetching."
264
- // This doesn't happen in undici (in most cases) because undici, by default,
265
- // has no concept of origin.
266
- if (serializedOrigin === 'client') {
263
+ // - "'client' is changed to an origin during fetching."
264
+ // This doesn't happen in undici (in most cases) because undici, by default,
265
+ // has no concept of origin.
266
+ // - request.origin can also be set to request.client.origin (client being
267
+ // an environment settings object), which is undefined without using
268
+ // setGlobalOrigin.
269
+ if (serializedOrigin === 'client' || serializedOrigin === undefined) {
267
270
  return
268
271
  }
269
272
 
@@ -791,37 +794,12 @@ function isCancelled (fetchParams) {
791
794
  fetchParams.controller.state === 'terminated'
792
795
  }
793
796
 
794
- const normalizeMethodRecordBase = {
795
- delete: 'DELETE',
796
- DELETE: 'DELETE',
797
- get: 'GET',
798
- GET: 'GET',
799
- head: 'HEAD',
800
- HEAD: 'HEAD',
801
- options: 'OPTIONS',
802
- OPTIONS: 'OPTIONS',
803
- post: 'POST',
804
- POST: 'POST',
805
- put: 'PUT',
806
- PUT: 'PUT'
807
- }
808
-
809
- const normalizeMethodRecord = {
810
- ...normalizeMethodRecordBase,
811
- patch: 'patch',
812
- PATCH: 'PATCH'
813
- }
814
-
815
- // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
816
- Object.setPrototypeOf(normalizeMethodRecordBase, null)
817
- Object.setPrototypeOf(normalizeMethodRecord, null)
818
-
819
797
  /**
820
798
  * @see https://fetch.spec.whatwg.org/#concept-method-normalize
821
799
  * @param {string} method
822
800
  */
823
801
  function normalizeMethod (method) {
824
- return normalizeMethodRecordBase[method.toLowerCase()] ?? method
802
+ return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
825
803
  }
826
804
 
827
805
  // https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
@@ -1639,7 +1617,6 @@ module.exports = {
1639
1617
  urlHasHttpsScheme,
1640
1618
  urlIsHttpHttpsScheme,
1641
1619
  readAllBytes,
1642
- normalizeMethodRecord,
1643
1620
  simpleRangeHeaderValue,
1644
1621
  buildContentRange,
1645
1622
  parseMetadata,
@@ -28,8 +28,6 @@ const { types } = require('node:util')
28
28
  const { ErrorEvent, CloseEvent } = require('./events')
29
29
  const { SendQueue } = require('./sender')
30
30
 
31
- let experimentalWarned = false
32
-
33
31
  // https://websockets.spec.whatwg.org/#interface-definition
34
32
  class WebSocket extends EventTarget {
35
33
  #events = {
@@ -56,13 +54,6 @@ class WebSocket extends EventTarget {
56
54
  const prefix = 'WebSocket constructor'
57
55
  webidl.argumentLengthCheck(arguments, 1, prefix)
58
56
 
59
- if (!experimentalWarned) {
60
- experimentalWarned = true
61
- process.emitWarning('WebSockets are experimental, expect them to change at any time.', {
62
- code: 'UNDICI-WS'
63
- })
64
- }
65
-
66
57
  const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
67
58
 
68
59
  url = webidl.converters.USVString(url, prefix, 'url')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.18.2",
3
+ "version": "6.19.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -107,8 +107,8 @@
107
107
  "@sinonjs/fake-timers": "^11.1.0",
108
108
  "@types/node": "^18.0.3",
109
109
  "abort-controller": "^3.0.0",
110
- "borp": "^0.14.0",
111
- "c8": "^9.1.0",
110
+ "borp": "^0.15.0",
111
+ "c8": "^10.0.0",
112
112
  "cross-env": "^7.0.3",
113
113
  "dns-packet": "^5.4.0",
114
114
  "fast-check": "^3.17.1",
package/types/errors.d.ts CHANGED
@@ -125,4 +125,25 @@ declare namespace Errors {
125
125
  name: 'ResponseExceededMaxSizeError';
126
126
  code: 'UND_ERR_RES_EXCEEDED_MAX_SIZE';
127
127
  }
128
+
129
+ export class RequestRetryError extends UndiciError {
130
+ constructor (
131
+ message: string,
132
+ statusCode: number,
133
+ headers?: IncomingHttpHeaders | string[] | null,
134
+ body?: null | Record<string, any> | string
135
+ );
136
+ name: 'RequestRetryError';
137
+ code: 'UND_ERR_REQ_RETRY';
138
+ statusCode: number;
139
+ data: {
140
+ count: number;
141
+ };
142
+ headers: Record<string, string | string[]>;
143
+ }
144
+
145
+ export class SecureProxyConnectionError extends UndiciError {
146
+ name: 'SecureProxyConnectionError';
147
+ code: 'UND_ERR_PRX_TLS';
148
+ }
128
149
  }
package/types/index.d.ts CHANGED
@@ -18,6 +18,7 @@ import EnvHttpProxyAgent from './env-http-proxy-agent'
18
18
  import RetryHandler from'./retry-handler'
19
19
  import RetryAgent from'./retry-agent'
20
20
  import { request, pipeline, stream, connect, upgrade } from './api'
21
+ import interceptors from './interceptors'
21
22
 
22
23
  export * from './util'
23
24
  export * from './cookies'
@@ -32,7 +33,7 @@ export * from './content-type'
32
33
  export * from './cache'
33
34
  export { Interceptable } from './mock-interceptor'
34
35
 
35
- export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent }
36
+ export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, interceptors, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent }
36
37
  export default Undici
37
38
 
38
39
  declare namespace Undici {