undici 6.18.1 → 6.19.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.
@@ -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.
@@ -46,6 +46,9 @@ It represents the retry state for a given request.
46
46
  - **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every retry.
47
47
  - **handler** Extends [`Dispatch.DispatchHandlers`](Dispatcher.md#dispatcherdispatchoptions-handler) (required) - Handler function to be called after the request is successful or the retries are exhausted.
48
48
 
49
+ >__Note__: The `RetryHandler` does not retry over stateful bodies (e.g. streams, AsyncIterable) as those, once consumed, are left in an state that cannot be reutilized. For these situations the `RetryHandler` will identify
50
+ >the body as stateful and will not retry the request rejecting with the error `UND_ERR_REQ_RETRY`.
51
+
49
52
  Examples:
50
53
 
51
54
  ```js
@@ -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
 
@@ -20,6 +20,7 @@ module.exports = {
20
20
  kHost: Symbol('host'),
21
21
  kNoRef: Symbol('no ref'),
22
22
  kBodyUsed: Symbol('used'),
23
+ kBody: Symbol('abstracted request body'),
23
24
  kRunning: Symbol('running'),
24
25
  kBlocking: Symbol('blocking'),
25
26
  kPending: Symbol('pending'),
package/lib/core/util.js CHANGED
@@ -1,19 +1,72 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
- const { kDestroyed, kBodyUsed, kListeners } = require('./symbols')
4
+ const { kDestroyed, kBodyUsed, kListeners, kBody } = require('./symbols')
5
5
  const { IncomingMessage } = require('node:http')
6
6
  const stream = require('node:stream')
7
7
  const net = require('node:net')
8
- const { InvalidArgumentError } = require('./errors')
9
8
  const { Blob } = require('node:buffer')
10
9
  const nodeUtil = require('node:util')
11
10
  const { stringify } = require('node:querystring')
11
+ const { EventEmitter: EE } = require('node:events')
12
+ const { InvalidArgumentError } = require('./errors')
12
13
  const { headerNameLowerCasedRecord } = require('./constants')
13
14
  const { tree } = require('./tree')
14
15
 
15
16
  const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
16
17
 
18
+ class BodyAsyncIterable {
19
+ constructor (body) {
20
+ this[kBody] = body
21
+ this[kBodyUsed] = false
22
+ }
23
+
24
+ async * [Symbol.asyncIterator] () {
25
+ assert(!this[kBodyUsed], 'disturbed')
26
+ this[kBodyUsed] = true
27
+ yield * this[kBody]
28
+ }
29
+ }
30
+
31
+ function wrapRequestBody (body) {
32
+ if (isStream(body)) {
33
+ // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
34
+ // so that it can be dispatched again?
35
+ // TODO (fix): Do we need 100-expect support to provide a way to do this properly?
36
+ if (bodyLength(body) === 0) {
37
+ body
38
+ .on('data', function () {
39
+ assert(false)
40
+ })
41
+ }
42
+
43
+ if (typeof body.readableDidRead !== 'boolean') {
44
+ body[kBodyUsed] = false
45
+ EE.prototype.on.call(body, 'data', function () {
46
+ this[kBodyUsed] = true
47
+ })
48
+ }
49
+
50
+ return body
51
+ } else if (body && typeof body.pipeTo === 'function') {
52
+ // TODO (fix): We can't access ReadableStream internal state
53
+ // to determine whether or not it has been disturbed. This is just
54
+ // a workaround.
55
+ return new BodyAsyncIterable(body)
56
+ } else if (
57
+ body &&
58
+ typeof body !== 'string' &&
59
+ !ArrayBuffer.isView(body) &&
60
+ isIterable(body)
61
+ ) {
62
+ // TODO: Should we allow re-using iterable if !this.opts.idempotent
63
+ // or through some other flag?
64
+ return new BodyAsyncIterable(body)
65
+ } else {
66
+ return body
67
+ }
68
+ }
69
+
17
70
  function nop () {}
18
71
 
19
72
  function isStream (obj) {
@@ -592,6 +645,31 @@ function errorRequest (client, request, err) {
592
645
  const kEnumerableProperty = Object.create(null)
593
646
  kEnumerableProperty.enumerable = true
594
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
+
595
673
  module.exports = {
596
674
  kEnumerableProperty,
597
675
  nop,
@@ -630,9 +708,12 @@ module.exports = {
630
708
  isValidHeaderValue,
631
709
  isTokenCharCode,
632
710
  parseRangeHeader,
711
+ normalizedMethodRecordsBase,
712
+ normalizedMethodRecords,
633
713
  isValidPort,
634
714
  isHttpOrHttpsPrefixed,
635
715
  nodeMajor,
636
716
  nodeMinor,
637
- safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
717
+ safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'],
718
+ wrapRequestBody
638
719
  }
@@ -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
@@ -3,7 +3,12 @@ const assert = require('node:assert')
3
3
 
4
4
  const { kRetryHandlerDefaultRetry } = require('../core/symbols')
5
5
  const { RequestRetryError } = require('../core/errors')
6
- const { isDisturbed, parseHeaders, parseRangeHeader } = require('../core/util')
6
+ const {
7
+ isDisturbed,
8
+ parseHeaders,
9
+ parseRangeHeader,
10
+ wrapRequestBody
11
+ } = require('../core/util')
7
12
 
8
13
  function calculateRetryAfterHeader (retryAfter) {
9
14
  const current = Date.now()
@@ -29,7 +34,7 @@ class RetryHandler {
29
34
 
30
35
  this.dispatch = handlers.dispatch
31
36
  this.handler = handlers.handler
32
- this.opts = dispatchOpts
37
+ this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
33
38
  this.abort = null
34
39
  this.aborted = false
35
40
  this.retryOpts = {
@@ -174,7 +179,9 @@ class RetryHandler {
174
179
  this.abort(
175
180
  new RequestRetryError('Request failed', statusCode, {
176
181
  headers,
177
- count: this.retryCount
182
+ data: {
183
+ count: this.retryCount
184
+ }
178
185
  })
179
186
  )
180
187
  return false
@@ -195,7 +202,7 @@ class RetryHandler {
195
202
  this.abort(
196
203
  new RequestRetryError('Content-Range mismatch', statusCode, {
197
204
  headers,
198
- count: this.retryCount
205
+ data: { count: this.retryCount }
199
206
  })
200
207
  )
201
208
  return false
@@ -206,7 +213,7 @@ class RetryHandler {
206
213
  this.abort(
207
214
  new RequestRetryError('ETag mismatch', statusCode, {
208
215
  headers,
209
- count: this.retryCount
216
+ data: { count: this.retryCount }
210
217
  })
211
218
  )
212
219
  return false
@@ -278,7 +285,7 @@ class RetryHandler {
278
285
 
279
286
  const err = new RequestRetryError('Request failed', statusCode, {
280
287
  headers,
281
- count: this.retryCount
288
+ data: { count: this.retryCount }
282
289
  })
283
290
 
284
291
  this.abort(err)
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { parseSetCookie } = require('./parse')
4
- const { stringify, getHeadersList } = require('./util')
4
+ const { stringify } = require('./util')
5
5
  const { webidl } = require('../fetch/webidl')
6
6
  const { Headers } = require('../fetch/headers')
7
7
 
@@ -78,14 +78,13 @@ function getSetCookies (headers) {
78
78
 
79
79
  webidl.brandCheck(headers, Headers, { strict: false })
80
80
 
81
- const cookies = getHeadersList(headers).cookies
81
+ const cookies = headers.getSetCookie()
82
82
 
83
83
  if (!cookies) {
84
84
  return []
85
85
  }
86
86
 
87
- // In older versions of undici, cookies is a list of name:value.
88
- return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair))
87
+ return cookies.map((pair) => parseSetCookie(pair))
89
88
  }
90
89
 
91
90
  /**
@@ -1,8 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const assert = require('node:assert')
4
- const { getHeadersList: internalGetHeadersList } = require('../fetch/headers')
5
-
6
3
  /**
7
4
  * @param {string} value
8
5
  * @returns {boolean}
@@ -275,35 +272,11 @@ function stringify (cookie) {
275
272
  return out.join('; ')
276
273
  }
277
274
 
278
- let kHeadersListNode
279
-
280
- function getHeadersList (headers) {
281
- try {
282
- return internalGetHeadersList(headers)
283
- } catch {
284
- // fall-through
285
- }
286
-
287
- if (!kHeadersListNode) {
288
- kHeadersListNode = Object.getOwnPropertySymbols(headers).find(
289
- (symbol) => symbol.description === 'headers list'
290
- )
291
-
292
- assert(kHeadersListNode, 'Headers cannot be parsed')
293
- }
294
-
295
- const headersList = headers[kHeadersListNode]
296
- assert(headersList)
297
-
298
- return headersList
299
- }
300
-
301
275
  module.exports = {
302
276
  isCTLExcludingHtab,
303
277
  validateCookieName,
304
278
  validateCookiePath,
305
279
  validateCookieValue,
306
280
  toIMFDate,
307
- stringify,
308
- getHeadersList
281
+ stringify
309
282
  }
@@ -641,14 +641,6 @@ Object.defineProperties(Headers.prototype, {
641
641
  },
642
642
  [util.inspect.custom]: {
643
643
  enumerable: false
644
- },
645
- // Compatibility for global headers
646
- [Symbol('headers list')]: {
647
- configurable: false,
648
- enumerable: false,
649
- get: function () {
650
- return getHeadersList(this)
651
- }
652
644
  }
653
645
  })
654
646
 
@@ -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')
@@ -791,37 +791,12 @@ function isCancelled (fetchParams) {
791
791
  fetchParams.controller.state === 'terminated'
792
792
  }
793
793
 
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
794
  /**
820
795
  * @see https://fetch.spec.whatwg.org/#concept-method-normalize
821
796
  * @param {string} method
822
797
  */
823
798
  function normalizeMethod (method) {
824
- return normalizeMethodRecordBase[method.toLowerCase()] ?? method
799
+ return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
825
800
  }
826
801
 
827
802
  // https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
@@ -1639,7 +1614,6 @@ module.exports = {
1639
1614
  urlHasHttpsScheme,
1640
1615
  urlIsHttpHttpsScheme,
1641
1616
  readAllBytes,
1642
- normalizeMethodRecord,
1643
1617
  simpleRangeHeaderValue,
1644
1618
  buildContentRange,
1645
1619
  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.1",
3
+ "version": "6.19.0",
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.13.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 {