undici 6.3.0 → 6.4.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.
package/README.md CHANGED
@@ -18,34 +18,21 @@ npm i undici
18
18
  ## Benchmarks
19
19
 
20
20
  The benchmark is a simple `hello world` [example](benchmarks/benchmark.js) using a
21
- number of unix sockets (connections) with a pipelining depth of 10 running on Node 20.6.0.
22
-
23
- ### Connections 1
24
-
25
-
26
- | Tests | Samples | Result | Tolerance | Difference with slowest |
27
- |---------------------|---------|---------------|-----------|-------------------------|
28
- | http - no keepalive | 15 | 5.32 req/sec | ± 2.61 % | - |
29
- | http - keepalive | 10 | 5.35 req/sec | ± 2.47 % | + 0.44 % |
30
- | undici - fetch | 15 | 41.85 req/sec | ± 2.49 % | + 686.04 % |
31
- | undici - pipeline | 40 | 50.36 req/sec | ± 2.77 % | + 845.92 % |
32
- | undici - stream | 15 | 60.58 req/sec | ± 2.75 % | + 1037.72 % |
33
- | undici - request | 10 | 61.19 req/sec | ± 2.60 % | + 1049.24 % |
34
- | undici - dispatch | 20 | 64.84 req/sec | ± 2.81 % | + 1117.81 % |
35
-
36
-
37
- ### Connections 50
38
-
39
- | Tests | Samples | Result | Tolerance | Difference with slowest |
40
- |---------------------|---------|------------------|-----------|-------------------------|
41
- | undici - fetch | 30 | 2107.19 req/sec | ± 2.69 % | - |
42
- | http - no keepalive | 10 | 2698.90 req/sec | ± 2.68 % | + 28.08 % |
43
- | http - keepalive | 10 | 4639.49 req/sec | ± 2.55 % | + 120.17 % |
44
- | undici - pipeline | 40 | 6123.33 req/sec | ± 2.97 % | + 190.59 % |
45
- | undici - stream | 50 | 9426.51 req/sec | ± 2.92 % | + 347.35 % |
46
- | undici - request | 10 | 10162.88 req/sec | ± 2.13 % | + 382.29 % |
47
- | undici - dispatch | 50 | 11191.11 req/sec | ± 2.98 % | + 431.09 % |
48
-
21
+ 50 TCP connections with a pipelining depth of 10 running on Node 20.10.0.
22
+
23
+ Tests │ Samples │ Result │ Tolerance │ Difference with slowest │
24
+ |─────────────────────|─────────|─────────────────|───────────|─────────────────────────|
25
+ │ got │ 45 │ 1661.71 req/sec │ ± 2.93 % │ - │
26
+ node-fetch │ 20 2164.81 req/sec │ ± 2.63 % │ + 30.28 % │
27
+ │ undici - fetch │ 35 │ 2274.27 req/sec │ ± 2.70 % │ + 36.86 % │
28
+ http - no keepalive 15 │ 2376.04 req/sec ± 2.99 % │ + 42.99 % │
29
+ axios │ 25 2612.93 req/sec ± 2.89 % + 57.24 %
30
+ request │ 40 2712.19 req/sec ± 2.92 % + 63.22 %
31
+ http - keepalive │ 45 4393.25 req/sec ± 2.86 % + 164.38 %
32
+ undici - pipeline │ 45 5484.69 req/sec ± 2.87 % + 230.06 %
33
+ undici - request 55 7773.98 req/sec ± 2.93 % + 367.83 %
34
+ undici - stream │ 70 8425.96 req/sec ± 2.91 % + 407.07 %
35
+ │ undici - dispatch │ 50 │ 9488.99 req/sec │ ± 2.85 % │ + 471.04 % │
49
36
 
50
37
  ## Quick Start
51
38
 
@@ -62,9 +49,7 @@ const {
62
49
  console.log('response received', statusCode)
63
50
  console.log('headers', headers)
64
51
 
65
- for await (const data of body) {
66
- console.log('data', data)
67
- }
52
+ for await (const data of body) { console.log('data', data) }
68
53
 
69
54
  console.log('trailers', trailers)
70
55
  ```
package/docs/api/Debug.md CHANGED
@@ -53,10 +53,10 @@ This flag enables debug statements for the `Websocket` API.
53
53
  > **Note**: statements can overlap with `UNDICI` ones if `undici` or `fetch` flag has been enabled as well.
54
54
 
55
55
  ```sh
56
- NODE_DEBUG=fetch node script.js
56
+ NODE_DEBUG=websocket node script.js
57
57
 
58
58
  WEBSOCKET 18309: connecting to echo.websocket.org using https:h1
59
59
  WEBSOCKET 18309: connected to echo.websocket.org using https:h1
60
60
  WEBSOCKET 18309: sending request to GET https://echo.websocket.org//
61
61
  WEBSOCKET 18309: connection opened <ip_address>
62
- ```
62
+ ```
@@ -0,0 +1,96 @@
1
+ # Class: RedirectHandler
2
+
3
+ A class that handles redirection logic for HTTP requests.
4
+
5
+ ## `new RedirectHandler(dispatch, maxRedirections, opts, handler, redirectionLimitReached)`
6
+
7
+ Arguments:
8
+
9
+ - **dispatch** `function` - The dispatch function to be called after every retry.
10
+ - **maxRedirections** `number` - Maximum number of redirections allowed.
11
+ - **opts** `object` - Options for handling redirection.
12
+ - **handler** `object` - An object containing handlers for different stages of the request lifecycle.
13
+ - **redirectionLimitReached** `boolean` (default: `false`) - A flag that the implementer can provide to enable or disable the feature. If set to `false`, it indicates that the caller doesn't want to use the feature and prefers the old behavior.
14
+
15
+ Returns: `RedirectHandler`
16
+
17
+ ### Parameters
18
+
19
+ - **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every redirection.
20
+ - **maxRedirections** `number` (required) - Maximum number of redirections allowed.
21
+ - **opts** `object` (required) - Options for handling redirection.
22
+ - **handler** `object` (required) - Handlers for different stages of the request lifecycle.
23
+ - **redirectionLimitReached** `boolean` (default: `false`) - A flag that the implementer can provide to enable or disable the feature. If set to `false`, it indicates that the caller doesn't want to use the feature and prefers the old behavior.
24
+
25
+ ### Properties
26
+
27
+ - **location** `string` - The current redirection location.
28
+ - **abort** `function` - The abort function.
29
+ - **opts** `object` - The options for handling redirection.
30
+ - **maxRedirections** `number` - Maximum number of redirections allowed.
31
+ - **handler** `object` - Handlers for different stages of the request lifecycle.
32
+ - **history** `Array` - An array representing the history of URLs during redirection.
33
+ - **redirectionLimitReached** `boolean` - Indicates whether the redirection limit has been reached.
34
+
35
+ ### Methods
36
+
37
+ #### `onConnect(abort)`
38
+
39
+ Called when the connection is established.
40
+
41
+ Parameters:
42
+
43
+ - **abort** `function` - The abort function.
44
+
45
+ #### `onUpgrade(statusCode, headers, socket)`
46
+
47
+ Called when an upgrade is requested.
48
+
49
+ Parameters:
50
+
51
+ - **statusCode** `number` - The HTTP status code.
52
+ - **headers** `object` - The headers received in the response.
53
+ - **socket** `object` - The socket object.
54
+
55
+ #### `onError(error)`
56
+
57
+ Called when an error occurs.
58
+
59
+ Parameters:
60
+
61
+ - **error** `Error` - The error that occurred.
62
+
63
+ #### `onHeaders(statusCode, headers, resume, statusText)`
64
+
65
+ Called when headers are received.
66
+
67
+ Parameters:
68
+
69
+ - **statusCode** `number` - The HTTP status code.
70
+ - **headers** `object` - The headers received in the response.
71
+ - **resume** `function` - The resume function.
72
+ - **statusText** `string` - The status text.
73
+
74
+ #### `onData(chunk)`
75
+
76
+ Called when data is received.
77
+
78
+ Parameters:
79
+
80
+ - **chunk** `Buffer` - The data chunk received.
81
+
82
+ #### `onComplete(trailers)`
83
+
84
+ Called when the request is complete.
85
+
86
+ Parameters:
87
+
88
+ - **trailers** `object` - The trailers received.
89
+
90
+ #### `onBodySent(chunk)`
91
+
92
+ Called when the request body is sent.
93
+
94
+ Parameters:
95
+
96
+ - **chunk** `Buffer` - The chunk of the request body sent.
package/index.js CHANGED
@@ -21,14 +21,6 @@ const DecoratorHandler = require('./lib/handler/DecoratorHandler')
21
21
  const RedirectHandler = require('./lib/handler/RedirectHandler')
22
22
  const createRedirectInterceptor = require('./lib/interceptor/redirectInterceptor')
23
23
 
24
- let hasCrypto
25
- try {
26
- require('crypto')
27
- hasCrypto = true
28
- } catch {
29
- hasCrypto = false
30
- }
31
-
32
24
  Object.assign(Dispatcher.prototype, api)
33
25
 
34
26
  module.exports.Dispatcher = Dispatcher
@@ -102,14 +94,10 @@ function makeDispatcher (fn) {
102
94
  module.exports.setGlobalDispatcher = setGlobalDispatcher
103
95
  module.exports.getGlobalDispatcher = getGlobalDispatcher
104
96
 
105
- let fetchImpl = null
106
- module.exports.fetch = async function fetch (resource) {
107
- if (!fetchImpl) {
108
- fetchImpl = require('./lib/fetch').fetch
109
- }
110
-
97
+ const fetchImpl = require('./lib/fetch').fetch
98
+ module.exports.fetch = async function fetch (init, options = undefined) {
111
99
  try {
112
- return await fetchImpl(...arguments)
100
+ return await fetchImpl(init, options)
113
101
  } catch (err) {
114
102
  if (typeof err === 'object') {
115
103
  Error.captureStackTrace(err, this)
@@ -149,11 +137,7 @@ const { parseMIMEType, serializeAMimeType } = require('./lib/fetch/dataURL')
149
137
  module.exports.parseMIMEType = parseMIMEType
150
138
  module.exports.serializeAMimeType = serializeAMimeType
151
139
 
152
- if (hasCrypto) {
153
- const { WebSocket } = require('./lib/websocket/websocket')
154
-
155
- module.exports.WebSocket = WebSocket
156
- }
140
+ module.exports.WebSocket = require('./lib/websocket/websocket').WebSocket
157
141
 
158
142
  module.exports.request = makeDispatcher(api.request)
159
143
  module.exports.stream = makeDispatcher(api.stream)
@@ -6,8 +6,9 @@ const { kEnumerableProperty, isDisturbed } = require('../core/util')
6
6
  const { kHeadersList } = require('../core/symbols')
7
7
  const { webidl } = require('../fetch/webidl')
8
8
  const { Response, cloneResponse } = require('../fetch/response')
9
- const { Request } = require('../fetch/request')
10
- const { kState, kHeaders, kGuard, kRealm } = require('../fetch/symbols')
9
+ const { Request, fromInnerRequest } = require('../fetch/request')
10
+ const { Headers } = require('../fetch/headers')
11
+ const { kState, kHeaders, kGuard } = require('../fetch/symbols')
11
12
  const { fetching } = require('../fetch/index')
12
13
  const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require('../fetch/util')
13
14
  const assert = require('assert')
@@ -49,7 +50,7 @@ class Cache {
49
50
  request = webidl.converters.RequestInfo(request)
50
51
  options = webidl.converters.CacheQueryOptions(options)
51
52
 
52
- const p = await this.matchAll(request, options)
53
+ const p = this.#internalMatchAll(request, options, 1)
53
54
 
54
55
  if (p.length === 0) {
55
56
  return
@@ -64,64 +65,7 @@ class Cache {
64
65
  if (request !== undefined) request = webidl.converters.RequestInfo(request)
65
66
  options = webidl.converters.CacheQueryOptions(options)
66
67
 
67
- // 1.
68
- let r = null
69
-
70
- // 2.
71
- if (request !== undefined) {
72
- if (request instanceof Request) {
73
- // 2.1.1
74
- r = request[kState]
75
-
76
- // 2.1.2
77
- if (r.method !== 'GET' && !options.ignoreMethod) {
78
- return []
79
- }
80
- } else if (typeof request === 'string') {
81
- // 2.2.1
82
- r = new Request(request)[kState]
83
- }
84
- }
85
-
86
- // 5.
87
- // 5.1
88
- const responses = []
89
-
90
- // 5.2
91
- if (request === undefined) {
92
- // 5.2.1
93
- for (const requestResponse of this.#relevantRequestResponseList) {
94
- responses.push(requestResponse[1])
95
- }
96
- } else { // 5.3
97
- // 5.3.1
98
- const requestResponses = this.#queryCache(r, options)
99
-
100
- // 5.3.2
101
- for (const requestResponse of requestResponses) {
102
- responses.push(requestResponse[1])
103
- }
104
- }
105
-
106
- // 5.4
107
- // We don't implement CORs so we don't need to loop over the responses, yay!
108
-
109
- // 5.5.1
110
- const responseList = []
111
-
112
- // 5.5.2
113
- for (const response of responses) {
114
- // 5.5.2.1
115
- const responseObject = new Response(null)
116
- responseObject[kState] = response
117
- responseObject[kHeaders][kHeadersList] = response.headersList
118
- responseObject[kHeaders][kGuard] = 'immutable'
119
-
120
- responseList.push(responseObject.clone())
121
- }
122
-
123
- // 6.
124
- return Object.freeze(responseList)
68
+ return this.#internalMatchAll(request, options)
125
69
  }
126
70
 
127
71
  async add (request) {
@@ -500,7 +444,7 @@ class Cache {
500
444
  * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys
501
445
  * @param {any} request
502
446
  * @param {import('../../types/cache').CacheQueryOptions} options
503
- * @returns {readonly Request[]}
447
+ * @returns {Promise<readonly Request[]>}
504
448
  */
505
449
  async keys (request = undefined, options = {}) {
506
450
  webidl.brandCheck(this, Cache)
@@ -559,12 +503,12 @@ class Cache {
559
503
 
560
504
  // 5.4.2
561
505
  for (const request of requests) {
562
- const requestObject = new Request('https://a')
563
- requestObject[kState] = request
564
- requestObject[kHeaders][kHeadersList] = request.headersList
565
- requestObject[kHeaders][kGuard] = 'immutable'
566
- requestObject[kRealm] = request.client
567
-
506
+ const requestObject = fromInnerRequest(
507
+ request,
508
+ new AbortController().signal,
509
+ 'immutable',
510
+ { settingsObject: request.client }
511
+ )
568
512
  // 5.4.2.1
569
513
  requestList.push(requestObject)
570
514
  }
@@ -789,6 +733,72 @@ class Cache {
789
733
 
790
734
  return true
791
735
  }
736
+
737
+ #internalMatchAll (request, options, maxResponses = Infinity) {
738
+ // 1.
739
+ let r = null
740
+
741
+ // 2.
742
+ if (request !== undefined) {
743
+ if (request instanceof Request) {
744
+ // 2.1.1
745
+ r = request[kState]
746
+
747
+ // 2.1.2
748
+ if (r.method !== 'GET' && !options.ignoreMethod) {
749
+ return []
750
+ }
751
+ } else if (typeof request === 'string') {
752
+ // 2.2.1
753
+ r = new Request(request)[kState]
754
+ }
755
+ }
756
+
757
+ // 5.
758
+ // 5.1
759
+ const responses = []
760
+
761
+ // 5.2
762
+ if (request === undefined) {
763
+ // 5.2.1
764
+ for (const requestResponse of this.#relevantRequestResponseList) {
765
+ responses.push(requestResponse[1])
766
+ }
767
+ } else { // 5.3
768
+ // 5.3.1
769
+ const requestResponses = this.#queryCache(r, options)
770
+
771
+ // 5.3.2
772
+ for (const requestResponse of requestResponses) {
773
+ responses.push(requestResponse[1])
774
+ }
775
+ }
776
+
777
+ // 5.4
778
+ // We don't implement CORs so we don't need to loop over the responses, yay!
779
+
780
+ // 5.5.1
781
+ const responseList = []
782
+
783
+ // 5.5.2
784
+ for (const response of responses) {
785
+ // 5.5.2.1
786
+ const responseObject = new Response(kConstruct)
787
+ responseObject[kState] = response
788
+ responseObject[kHeaders] = new Headers(kConstruct)
789
+ responseObject[kHeaders][kHeadersList] = response.headersList
790
+ responseObject[kHeaders][kGuard] = 'immutable'
791
+
792
+ responseList.push(responseObject.clone())
793
+
794
+ if (responseList.length >= maxResponses) {
795
+ break
796
+ }
797
+ }
798
+
799
+ // 6.
800
+ return Object.freeze(responseList)
801
+ }
792
802
  }
793
803
 
794
804
  Object.defineProperties(Cache.prototype, {
@@ -114,7 +114,7 @@ class CacheStorage {
114
114
 
115
115
  /**
116
116
  * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys
117
- * @returns {string[]}
117
+ * @returns {Promise<string[]>}
118
118
  */
119
119
  async keys () {
120
120
  webidl.brandCheck(this, CacheStorage)
package/lib/client.js CHANGED
@@ -1769,7 +1769,7 @@ function writeH2 (client, session, request) {
1769
1769
 
1770
1770
  session.ref()
1771
1771
 
1772
- const shouldEndStream = method === 'GET' || method === 'HEAD'
1772
+ const shouldEndStream = method === 'GET' || method === 'HEAD' || body === null
1773
1773
  if (expectContinue) {
1774
1774
  headers[HTTP2_HEADER_EXPECT] = '100-continue'
1775
1775
  stream = session.request(headers, { endStream: shouldEndStream, signal })
package/lib/core/util.js CHANGED
@@ -349,14 +349,8 @@ function validateHandler (handler, method, upgrade) {
349
349
  // A body is disturbed if it has been read from and it cannot
350
350
  // be re-used without losing state or data.
351
351
  function isDisturbed (body) {
352
- return !!(body && (
353
- stream.isDisturbed
354
- ? stream.isDisturbed(body) || body[kBodyUsed] // TODO (fix): Why is body[kBodyUsed] needed?
355
- : body[kBodyUsed] ||
356
- body.readableDidRead ||
357
- (body._readableState && body._readableState.dataEmitted) ||
358
- isReadableAborted(body)
359
- ))
352
+ // TODO (fix): Why is body[kBodyUsed] needed?
353
+ return !!(body && (stream.isDisturbed(body) || body[kBodyUsed]))
360
354
  }
361
355
 
362
356
  function isErrored (body) {
@@ -122,7 +122,7 @@ class Fetch extends EE {
122
122
  }
123
123
 
124
124
  // https://fetch.spec.whatwg.org/#fetch-method
125
- function fetch (input, init = {}) {
125
+ function fetch (input, init = undefined) {
126
126
  webidl.argumentLengthCheck(arguments, 1, { header: 'globalThis.fetch' })
127
127
 
128
128
  // 1. Let p be a new promise.
@@ -248,7 +248,7 @@ function fetch (input, init = {}) {
248
248
  request,
249
249
  processResponseEndOfBody: handleFetchDone,
250
250
  processResponse,
251
- dispatcher: init.dispatcher ?? getGlobalDispatcher() // undici
251
+ dispatcher: init?.dispatcher ?? getGlobalDispatcher() // undici
252
252
  })
253
253
 
254
254
  // 14. Return p.
@@ -327,13 +327,6 @@ function markResourceTiming (timingInfo, originalURL, initiatorType, globalThis,
327
327
 
328
328
  // https://fetch.spec.whatwg.org/#abort-fetch
329
329
  function abortFetch (p, request, responseObject, error) {
330
- // Note: AbortSignal.reason was added in node v17.2.0
331
- // which would give us an undefined error to reject with.
332
- // Remove this once node v16 is no longer supported.
333
- if (!error) {
334
- error = new DOMException('The operation was aborted.', 'AbortError')
335
- }
336
-
337
330
  // 1. Reject promise with error.
338
331
  p.reject(error)
339
332
 
@@ -751,14 +751,6 @@ class Request {
751
751
 
752
752
  // 3. Let clonedRequestObject be the result of creating a Request object,
753
753
  // given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
754
- const clonedRequestObject = new Request(kConstruct)
755
- clonedRequestObject[kState] = clonedRequest
756
- clonedRequestObject[kRealm] = this[kRealm]
757
- clonedRequestObject[kHeaders] = new Headers(kConstruct)
758
- clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList
759
- clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard]
760
- clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm]
761
-
762
754
  // 4. Make clonedRequestObject’s signal follow this’s signal.
763
755
  const ac = new AbortController()
764
756
  if (this.signal.aborted) {
@@ -771,10 +763,9 @@ class Request {
771
763
  }
772
764
  )
773
765
  }
774
- clonedRequestObject[kSignal] = ac.signal
775
766
 
776
767
  // 4. Return clonedRequestObject.
777
- return clonedRequestObject
768
+ return fromInnerRequest(clonedRequest, ac.signal, this[kHeaders][kGuard], this[kRealm])
778
769
  }
779
770
  }
780
771
 
@@ -844,6 +835,26 @@ function cloneRequest (request) {
844
835
  return newRequest
845
836
  }
846
837
 
838
+ /**
839
+ * @param {any} innerRequest
840
+ * @param {AbortSignal} signal
841
+ * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard
842
+ * @param {any} [realm]
843
+ * @returns {Request}
844
+ */
845
+ function fromInnerRequest (innerRequest, signal, guard, realm) {
846
+ const request = new Request(kConstruct)
847
+ request[kState] = innerRequest
848
+ request[kRealm] = realm
849
+ request[kSignal] = signal
850
+ request[kSignal][kRealm] = realm
851
+ request[kHeaders] = new Headers(kConstruct)
852
+ request[kHeaders][kHeadersList] = innerRequest.headersList
853
+ request[kHeaders][kGuard] = guard
854
+ request[kHeaders][kRealm] = realm
855
+ return request
856
+ }
857
+
847
858
  Object.defineProperties(Request.prototype, {
848
859
  method: kEnumerableProperty,
849
860
  url: kEnumerableProperty,
@@ -970,4 +981,4 @@ webidl.converters.RequestInit = webidl.dictionaryConverter([
970
981
  }
971
982
  ])
972
983
 
973
- module.exports = { Request, makeRequest }
984
+ module.exports = { Request, makeRequest, fromInnerRequest }
@@ -38,6 +38,7 @@ class RedirectHandler {
38
38
  this.maxRedirections = maxRedirections
39
39
  this.handler = handler
40
40
  this.history = []
41
+ this.redirectionLimitReached = false
41
42
 
42
43
  if (util.isStream(this.opts.body)) {
43
44
  // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
@@ -91,6 +92,16 @@ class RedirectHandler {
91
92
  ? null
92
93
  : parseLocation(statusCode, headers)
93
94
 
95
+ if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
96
+ if (this.request) {
97
+ this.request.abort(new Error('max redirects'))
98
+ }
99
+
100
+ this.redirectionLimitReached = true
101
+ this.abort(new Error('max redirects'))
102
+ return
103
+ }
104
+
94
105
  if (this.opts.origin) {
95
106
  this.history.push(new URL(this.opts.path, this.opts.origin))
96
107
  }
@@ -188,11 +188,21 @@ function buildKey (opts) {
188
188
  }
189
189
 
190
190
  function generateKeyValues (data) {
191
- return Object.entries(data).reduce((keyValuePairs, [key, value]) => [
192
- ...keyValuePairs,
193
- Buffer.from(`${key}`),
194
- Array.isArray(value) ? value.map(x => Buffer.from(`${x}`)) : Buffer.from(`${value}`)
195
- ], [])
191
+ const keys = Object.keys(data)
192
+ const result = []
193
+ for (let i = 0; i < keys.length; ++i) {
194
+ const key = keys[i]
195
+ const value = data[key]
196
+ const name = Buffer.from(`${key}`)
197
+ if (Array.isArray(value)) {
198
+ for (let j = 0; j < value.length; ++j) {
199
+ result.push(name, Buffer.from(`${value[j]}`))
200
+ }
201
+ } else {
202
+ result.push(name, Buffer.from(`${value}`))
203
+ }
204
+ }
205
+ return result
196
206
  }
197
207
 
198
208
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.3.0",
3
+ "version": "6.4.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": {
@@ -92,7 +92,7 @@
92
92
  "bench": "PORT=3042 concurrently -k -s first npm:bench:server npm:bench:run",
93
93
  "bench:server": "node benchmarks/server.js",
94
94
  "prebench:run": "node benchmarks/wait.js",
95
- "bench:run": "CONNECTIONS=1 node benchmarks/benchmark.js; CONNECTIONS=50 node benchmarks/benchmark.js",
95
+ "bench:run": "SAMPLES=100 CONNECTIONS=50 node benchmarks/benchmark.js",
96
96
  "serve:website": "docsify serve .",
97
97
  "prepare": "husky install",
98
98
  "fuzz": "jsfuzz test/fuzzing/fuzz.js corpus"
@@ -136,7 +136,11 @@
136
136
  "tsd": "^0.30.1",
137
137
  "typescript": "^5.0.2",
138
138
  "wait-on": "^7.0.1",
139
- "ws": "^8.11.0"
139
+ "ws": "^8.11.0",
140
+ "axios": "^1.6.5",
141
+ "got": "^14.0.0",
142
+ "node-fetch": "^3.3.2",
143
+ "request": "^2.88.2"
140
144
  },
141
145
  "engines": {
142
146
  "node": ">=18.0"
@@ -131,6 +131,8 @@ declare namespace Dispatcher {
131
131
  opaque?: unknown;
132
132
  /** Default: 0 */
133
133
  maxRedirections?: number;
134
+ /** Default: false */
135
+ redirectionLimitReached?: boolean;
134
136
  /** Default: `null` */
135
137
  responseHeader?: 'raw' | null;
136
138
  }
@@ -141,6 +143,8 @@ declare namespace Dispatcher {
141
143
  signal?: AbortSignal | EventEmitter | null;
142
144
  /** Default: 0 */
143
145
  maxRedirections?: number;
146
+ /** Default: false */
147
+ redirectionLimitReached?: boolean;
144
148
  /** Default: `null` */
145
149
  onInfo?: (info: { statusCode: number, headers: Record<string, string | string[]> }) => void;
146
150
  /** Default: `null` */
@@ -164,6 +168,8 @@ declare namespace Dispatcher {
164
168
  signal?: AbortSignal | EventEmitter | null;
165
169
  /** Default: 0 */
166
170
  maxRedirections?: number;
171
+ /** Default: false */
172
+ redirectionLimitReached?: boolean;
167
173
  /** Default: `null` */
168
174
  responseHeader?: 'raw' | null;
169
175
  }
@@ -229,7 +235,7 @@ declare namespace Dispatcher {
229
235
  * @link https://fetch.spec.whatwg.org/#body-mixin
230
236
  */
231
237
  interface BodyMixin {
232
- readonly body?: never; // throws on node v16.6.0
238
+ readonly body?: never;
233
239
  readonly bodyUsed: boolean;
234
240
  arrayBuffer(): Promise<ArrayBuffer>;
235
241
  blob(): Promise<Blob>;
@@ -1,9 +1,15 @@
1
1
  import Dispatcher from "./dispatcher";
2
2
 
3
- export declare class RedirectHandler implements Dispatcher.DispatchHandlers{
4
- constructor (dispatch: Dispatcher, maxRedirections: number, opts: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandlers)
3
+ export declare class RedirectHandler implements Dispatcher.DispatchHandlers {
4
+ constructor(
5
+ dispatch: Dispatcher,
6
+ maxRedirections: number,
7
+ opts: Dispatcher.DispatchOptions,
8
+ handler: Dispatcher.DispatchHandlers,
9
+ redirectionLimitReached: boolean
10
+ );
5
11
  }
6
12
 
7
- export declare class DecoratorHandler implements Dispatcher.DispatchHandlers{
8
- constructor (handler: Dispatcher.DispatchHandlers)
13
+ export declare class DecoratorHandler implements Dispatcher.DispatchHandlers {
14
+ constructor(handler: Dispatcher.DispatchHandlers);
9
15
  }
@@ -44,9 +44,8 @@ declare class BodyReadable extends Readable {
44
44
  */
45
45
  readonly bodyUsed: boolean
46
46
 
47
- /** Throws on node 16.6.0
48
- *
49
- * If body is null, it should return null as the body
47
+ /**
48
+ * If body is null, it should return null as the body
50
49
  *
51
50
  * If body is not null, should return the body as a ReadableStream
52
51
  *