undici 7.0.0 → 7.1.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
@@ -434,6 +434,8 @@ and `undici.Agent`) which will enable the family autoselection algorithm when es
434
434
  * [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
435
435
  * [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
436
436
  * [__Szymon Marczak__](https://github.com/szmarczak), <https://www.npmjs.com/~szmarczak>
437
+
438
+ ## Past Collaborators
437
439
  * [__Tomas Della Vedova__](https://github.com/delvedor), <https://www.npmjs.com/~delvedor>
438
440
 
439
441
  ### Releasers
@@ -17,8 +17,6 @@ Returns: `Client`
17
17
 
18
18
  ### Parameter: `ClientOptions`
19
19
 
20
- > ⚠️ Warning: The `H2` support is experimental.
21
-
22
20
  * **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 socket everytime.
23
21
  * **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
22
  * **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.
@@ -34,6 +32,17 @@ Returns: `Client`
34
32
  * **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
35
33
  * **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
36
34
 
35
+ > **Notes about HTTP/2**
36
+ > - It only works under TLS connections. h2c is not supported.
37
+ > - The server must support HTTP/2 and choose it as the protocol during the ALPN negotiation.
38
+ > - The server must not have a bigger priority for HTTP/1.1 than HTTP/2.
39
+ > - Pseudo headers are automatically attached to the request. If you try to set them, they will be overwritten.
40
+ > - The `:path` header is automatically set to the request path.
41
+ > - The `:method` header is automatically set to the request method.
42
+ > - The `:scheme` header is automatically set to the request scheme.
43
+ > - The `:authority` header is automatically set to the request `host[:port]`.
44
+ > - `PUSH` frames are yet not supported.
45
+
37
46
  #### Parameter: `ConnectOptions`
38
47
 
39
48
  Every Tls option, see [here](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback).
@@ -128,3 +128,91 @@ Implements [`Agent.dispatch(options, handlers)`](/docs/docs/api/Agent.md#paramet
128
128
  ### `ProxyAgent.request(options[, callback])`
129
129
 
130
130
  See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
131
+
132
+
133
+ #### Example - ProxyAgent with Fetch
134
+
135
+ This example demonstrates how to use `fetch` with a proxy via `ProxyAgent`. It is particularly useful for scenarios requiring proxy tunneling.
136
+
137
+ ```javascript
138
+ import { ProxyAgent, fetch } from 'undici';
139
+
140
+ // Define the ProxyAgent
141
+ const proxyAgent = new ProxyAgent('http://localhost:8000');
142
+
143
+ // Make a GET request through the proxy
144
+ const response = await fetch('http://localhost:3000/foo', {
145
+ dispatcher: proxyAgent,
146
+ method: 'GET',
147
+ });
148
+
149
+ console.log('Response status:', response.status);
150
+ console.log('Response data:', await response.text());
151
+ ```
152
+
153
+ ---
154
+
155
+ #### Example - ProxyAgent with a Custom Proxy Server
156
+
157
+ This example shows how to create a custom proxy server and use it with `ProxyAgent`.
158
+
159
+ ```javascript
160
+ import * as http from 'node:http';
161
+ import { createProxy } from 'proxy';
162
+ import { ProxyAgent, fetch } from 'undici';
163
+
164
+ // Create a proxy server
165
+ const proxyServer = createProxy(http.createServer());
166
+ proxyServer.listen(8000, () => {
167
+ console.log('Proxy server running on port 8000');
168
+ });
169
+
170
+ // Define and use the ProxyAgent
171
+ const proxyAgent = new ProxyAgent('http://localhost:8000');
172
+
173
+ const response = await fetch('http://example.com', {
174
+ dispatcher: proxyAgent,
175
+ method: 'GET',
176
+ });
177
+
178
+ console.log('Response status:', response.status);
179
+ console.log('Response data:', await response.text());
180
+ ```
181
+
182
+ ---
183
+
184
+ #### Example - ProxyAgent with HTTPS Tunneling
185
+
186
+ This example demonstrates how to perform HTTPS tunneling using a proxy.
187
+
188
+ ```javascript
189
+ import { ProxyAgent, fetch } from 'undici';
190
+
191
+ // Define a ProxyAgent for HTTPS proxy
192
+ const proxyAgent = new ProxyAgent('https://secure.proxy.server');
193
+
194
+ // Make a request to an HTTPS endpoint via the proxy
195
+ const response = await fetch('https://secure.endpoint.com/api/data', {
196
+ dispatcher: proxyAgent,
197
+ method: 'GET',
198
+ });
199
+
200
+ console.log('Response status:', response.status);
201
+ console.log('Response data:', await response.json());
202
+ ```
203
+
204
+ #### Example - ProxyAgent as a Global Dispatcher
205
+
206
+ `ProxyAgent` can be configured as a global dispatcher, making it available for all requests without explicitly passing it. This simplifies code and is useful when a single proxy configuration applies to all requests.
207
+
208
+ ```javascript
209
+ import { ProxyAgent, setGlobalDispatcher, fetch } from 'undici';
210
+
211
+ // Define and configure the ProxyAgent
212
+ const proxyAgent = new ProxyAgent('http://localhost:8000');
213
+ setGlobalDispatcher(proxyAgent);
214
+
215
+ // Make requests without specifying the dispatcher
216
+ const response = await fetch('http://example.com');
217
+ console.log('Response status:', response.status);
218
+ console.log('Response data:', await response.text());
package/index.js CHANGED
@@ -53,9 +53,10 @@ try {
53
53
  const SqliteCacheStore = require('./lib/cache/sqlite-cache-store')
54
54
  module.exports.cacheStores.SqliteCacheStore = SqliteCacheStore
55
55
  } catch (err) {
56
- if (err.code !== 'ERR_UNKNOWN_BUILTIN_MODULE') {
57
- throw err
58
- }
56
+ // Most likely node:sqlite was not present, since SqliteCacheStore is
57
+ // optional, don't throw. Don't check specific error codes here because while
58
+ // ERR_UNKNOWN_BUILTIN_MODULE is expected, users have seen other codes like
59
+ // MODULE_NOT_FOUND
59
60
  }
60
61
 
61
62
  module.exports.buildConnector = buildConnector
@@ -35,9 +35,6 @@ const kOpenStreams = Symbol('open streams')
35
35
 
36
36
  let extractBody
37
37
 
38
- // Experimental
39
- let h2ExperimentalWarned = false
40
-
41
38
  /** @type {import('http2')} */
42
39
  let http2
43
40
  try {
@@ -82,13 +79,6 @@ function parseH2Headers (headers) {
82
79
  async function connectH2 (client, socket) {
83
80
  client[kSocket] = socket
84
81
 
85
- if (!h2ExperimentalWarned) {
86
- h2ExperimentalWarned = true
87
- process.emitWarning('H2 support is experimental, expect them to change at any time.', {
88
- code: 'UNDICI-H2'
89
- })
90
- }
91
-
92
82
  const session = http2.connect(client[kUrl], {
93
83
  createConnection: () => socket,
94
84
  peerMaxConcurrentStreams: client[kMaxConcurrentStreams],
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
+ const WrapHandler = require('./wrap-handler')
4
5
 
5
6
  /**
6
7
  * @deprecated
@@ -9,63 +10,58 @@ module.exports = class DecoratorHandler {
9
10
  #handler
10
11
  #onCompleteCalled = false
11
12
  #onErrorCalled = false
13
+ #onResponseStartCalled = false
12
14
 
13
15
  constructor (handler) {
14
16
  if (typeof handler !== 'object' || handler === null) {
15
17
  throw new TypeError('handler must be an object')
16
18
  }
17
- this.#handler = handler
19
+ this.#handler = WrapHandler.wrap(handler)
18
20
  }
19
21
 
20
- onConnect (...args) {
21
- return this.#handler.onConnect?.(...args)
22
+ onRequestStart (...args) {
23
+ this.#handler.onRequestStart?.(...args)
22
24
  }
23
25
 
24
- onError (...args) {
25
- this.#onErrorCalled = true
26
- return this.#handler.onError?.(...args)
27
- }
28
-
29
- onUpgrade (...args) {
26
+ onRequestUpgrade (...args) {
30
27
  assert(!this.#onCompleteCalled)
31
28
  assert(!this.#onErrorCalled)
32
29
 
33
- return this.#handler.onUpgrade?.(...args)
30
+ return this.#handler.onRequestUpgrade?.(...args)
34
31
  }
35
32
 
36
- onResponseStarted (...args) {
33
+ onResponseStart (...args) {
37
34
  assert(!this.#onCompleteCalled)
38
35
  assert(!this.#onErrorCalled)
36
+ assert(!this.#onResponseStartCalled)
39
37
 
40
- return this.#handler.onResponseStarted?.(...args)
41
- }
42
-
43
- onHeaders (...args) {
44
- assert(!this.#onCompleteCalled)
45
- assert(!this.#onErrorCalled)
38
+ this.#onResponseStartCalled = true
46
39
 
47
- return this.#handler.onHeaders?.(...args)
40
+ return this.#handler.onResponseStart?.(...args)
48
41
  }
49
42
 
50
- onData (...args) {
43
+ onResponseData (...args) {
51
44
  assert(!this.#onCompleteCalled)
52
45
  assert(!this.#onErrorCalled)
53
46
 
54
- return this.#handler.onData?.(...args)
47
+ return this.#handler.onResponseData?.(...args)
55
48
  }
56
49
 
57
- onComplete (...args) {
50
+ onResponseEnd (...args) {
58
51
  assert(!this.#onCompleteCalled)
59
52
  assert(!this.#onErrorCalled)
60
53
 
61
54
  this.#onCompleteCalled = true
62
- return this.#handler.onComplete?.(...args)
55
+ return this.#handler.onResponseEnd?.(...args)
63
56
  }
64
57
 
65
- onBodySent (...args) {
66
- assert(!this.#onCompleteCalled)
67
- assert(!this.#onErrorCalled)
68
-
69
- return this.#handler.onBodySent?.(...args)
58
+ onResponseError (...args) {
59
+ this.#onErrorCalled = true
60
+ return this.#handler.onResponseError?.(...args)
70
61
  }
62
+
63
+ /**
64
+ * @deprecated
65
+ */
66
+ onBodySent () {}
71
67
  }
@@ -222,19 +222,18 @@ class DNSDispatchHandler extends DecoratorHandler {
222
222
  #state = null
223
223
  #opts = null
224
224
  #dispatch = null
225
- #handler = null
226
225
  #origin = null
226
+ #controller = null
227
227
 
228
228
  constructor (state, { origin, handler, dispatch }, opts) {
229
229
  super(handler)
230
230
  this.#origin = origin
231
- this.#handler = handler
232
231
  this.#opts = { ...opts }
233
232
  this.#state = state
234
233
  this.#dispatch = dispatch
235
234
  }
236
235
 
237
- onError (err) {
236
+ onResponseError (controller, err) {
238
237
  switch (err.code) {
239
238
  case 'ETIMEDOUT':
240
239
  case 'ECONNREFUSED': {
@@ -242,7 +241,8 @@ class DNSDispatchHandler extends DecoratorHandler {
242
241
  // We delete the record and retry
243
242
  this.#state.runLookup(this.#origin, this.#opts, (err, newOrigin) => {
244
243
  if (err) {
245
- return this.#handler.onError(err)
244
+ super.onResponseError(controller, err)
245
+ return
246
246
  }
247
247
 
248
248
  const dispatchOpts = {
@@ -253,18 +253,18 @@ class DNSDispatchHandler extends DecoratorHandler {
253
253
  this.#dispatch(dispatchOpts, this)
254
254
  })
255
255
 
256
- // if dual-stack disabled, we error out
257
256
  return
258
257
  }
259
258
 
260
- this.#handler.onError(err)
261
- return
259
+ // if dual-stack disabled, we error out
260
+ super.onResponseError(controller, err)
261
+ break
262
262
  }
263
263
  case 'ENOTFOUND':
264
264
  this.#state.deleteRecord(this.#origin)
265
265
  // eslint-disable-next-line no-fallthrough
266
266
  default:
267
- this.#handler.onError(err)
267
+ super.onResponseError(controller, err)
268
268
  break
269
269
  }
270
270
  }
@@ -1,19 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const util = require('../core/util')
4
3
  const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
5
4
  const DecoratorHandler = require('../handler/decorator-handler')
6
5
 
7
6
  class DumpHandler extends DecoratorHandler {
8
7
  #maxSize = 1024 * 1024
9
- #abort = null
10
8
  #dumped = false
11
- #aborted = false
12
9
  #size = 0
13
- #reason = null
14
- #handler = null
10
+ #controller = null
11
+ aborted = false
12
+ reason = false
15
13
 
16
- constructor ({ maxSize }, handler) {
14
+ constructor ({ maxSize, signal }, handler) {
17
15
  if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
18
16
  throw new InvalidArgumentError('maxSize must be a number greater than 0')
19
17
  }
@@ -21,23 +19,22 @@ class DumpHandler extends DecoratorHandler {
21
19
  super(handler)
22
20
 
23
21
  this.#maxSize = maxSize ?? this.#maxSize
24
- this.#handler = handler
22
+ // this.#handler = handler
25
23
  }
26
24
 
27
- onConnect (abort) {
28
- this.#abort = abort
29
-
30
- this.#handler.onConnect(this.#customAbort.bind(this))
25
+ #abort (reason) {
26
+ this.aborted = true
27
+ this.reason = reason
31
28
  }
32
29
 
33
- #customAbort (reason) {
34
- this.#aborted = true
35
- this.#reason = reason
30
+ onRequestStart (controller, context) {
31
+ controller.abort = this.#abort.bind(this)
32
+ this.#controller = controller
33
+
34
+ return super.onRequestStart(controller, context)
36
35
  }
37
36
 
38
- // TODO: will require adjustment after new hooks are out
39
- onHeaders (statusCode, rawHeaders, resume, statusMessage) {
40
- const headers = util.parseHeaders(rawHeaders)
37
+ onResponseStart (controller, statusCode, headers, statusMessage) {
41
38
  const contentLength = headers['content-length']
42
39
 
43
40
  if (contentLength != null && contentLength > this.#maxSize) {
@@ -48,55 +45,50 @@ class DumpHandler extends DecoratorHandler {
48
45
  )
49
46
  }
50
47
 
51
- if (this.#aborted) {
48
+ if (this.aborted === true) {
52
49
  return true
53
50
  }
54
51
 
55
- return this.#handler.onHeaders(
56
- statusCode,
57
- rawHeaders,
58
- resume,
59
- statusMessage
60
- )
52
+ return super.onResponseStart(controller, statusCode, headers, statusMessage)
61
53
  }
62
54
 
63
- onError (err) {
55
+ onResponseError (controller, err) {
64
56
  if (this.#dumped) {
65
57
  return
66
58
  }
67
59
 
68
- err = this.#reason ?? err
60
+ err = this.#controller.reason ?? err
69
61
 
70
- this.#handler.onError(err)
62
+ super.onResponseError(controller, err)
71
63
  }
72
64
 
73
- onData (chunk) {
65
+ onResponseData (controller, chunk) {
74
66
  this.#size = this.#size + chunk.length
75
67
 
76
68
  if (this.#size >= this.#maxSize) {
77
69
  this.#dumped = true
78
70
 
79
- if (this.#aborted) {
80
- this.#handler.onError(this.#reason)
71
+ if (this.aborted === true) {
72
+ super.onResponseError(controller, this.reason)
81
73
  } else {
82
- this.#handler.onComplete([])
74
+ super.onResponseEnd(controller, {})
83
75
  }
84
76
  }
85
77
 
86
78
  return true
87
79
  }
88
80
 
89
- onComplete (trailers) {
81
+ onResponseEnd (controller, trailers) {
90
82
  if (this.#dumped) {
91
83
  return
92
84
  }
93
85
 
94
- if (this.#aborted) {
95
- this.#handler.onError(this.reason)
86
+ if (this.#controller.aborted === true) {
87
+ super.onResponseError(controller, this.reason)
96
88
  return
97
89
  }
98
90
 
99
- this.#handler.onComplete(trailers)
91
+ super.onResponseEnd(controller, trailers)
100
92
  }
101
93
  }
102
94
 
@@ -107,13 +99,9 @@ function createDumpInterceptor (
107
99
  ) {
108
100
  return dispatch => {
109
101
  return function Intercept (opts, handler) {
110
- const { dumpMaxSize = defaultMaxSize } =
111
- opts
102
+ const { dumpMaxSize = defaultMaxSize } = opts
112
103
 
113
- const dumpHandler = new DumpHandler(
114
- { maxSize: dumpMaxSize },
115
- handler
116
- )
104
+ const dumpHandler = new DumpHandler({ maxSize: dumpMaxSize, signal: opts.signal }, handler)
117
105
 
118
106
  return dispatch(opts, dumpHandler)
119
107
  }
@@ -1,43 +1,41 @@
1
1
  'use strict'
2
2
 
3
- const { parseHeaders } = require('../core/util')
3
+ // const { parseHeaders } = require('../core/util')
4
4
  const DecoratorHandler = require('../handler/decorator-handler')
5
5
  const { ResponseError } = require('../core/errors')
6
6
 
7
7
  class ResponseErrorHandler extends DecoratorHandler {
8
- #handler
9
8
  #statusCode
10
9
  #contentType
11
10
  #decoder
12
11
  #headers
13
12
  #body
14
13
 
15
- constructor (opts, { handler }) {
14
+ constructor (_opts, { handler }) {
16
15
  super(handler)
17
- this.#handler = handler
18
16
  }
19
17
 
20
- onConnect (abort) {
18
+ #checkContentType (contentType) {
19
+ return this.#contentType.indexOf(contentType) === 0
20
+ }
21
+
22
+ onRequestStart (controller, context) {
21
23
  this.#statusCode = 0
22
24
  this.#contentType = null
23
25
  this.#decoder = null
24
26
  this.#headers = null
25
27
  this.#body = ''
26
28
 
27
- return this.#handler.onConnect(abort)
28
- }
29
-
30
- #checkContentType (contentType) {
31
- return this.#contentType.indexOf(contentType) === 0
29
+ return super.onRequestStart(controller, context)
32
30
  }
33
31
 
34
- onHeaders (statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
32
+ onResponseStart (controller, statusCode, headers, statusMessage) {
35
33
  this.#statusCode = statusCode
36
34
  this.#headers = headers
37
35
  this.#contentType = headers['content-type']
38
36
 
39
37
  if (this.#statusCode < 400) {
40
- return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
38
+ return super.onResponseStart(controller, statusCode, headers, statusMessage)
41
39
  }
42
40
 
43
41
  if (this.#checkContentType('application/json') || this.#checkContentType('text/plain')) {
@@ -45,15 +43,15 @@ class ResponseErrorHandler extends DecoratorHandler {
45
43
  }
46
44
  }
47
45
 
48
- onData (chunk) {
46
+ onResponseData (controller, chunk) {
49
47
  if (this.#statusCode < 400) {
50
- return this.#handler.onData(chunk)
48
+ return super.onResponseData(controller, chunk)
51
49
  }
52
50
 
53
51
  this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''
54
52
  }
55
53
 
56
- onComplete (rawTrailers) {
54
+ onResponseEnd (controller, trailers) {
57
55
  if (this.#statusCode >= 400) {
58
56
  this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
59
57
 
@@ -77,14 +75,14 @@ class ResponseErrorHandler extends DecoratorHandler {
77
75
  Error.stackTraceLimit = stackTraceLimit
78
76
  }
79
77
 
80
- this.#handler.onError(err)
78
+ super.onResponseError(controller, err)
81
79
  } else {
82
- this.#handler.onComplete(rawTrailers)
80
+ super.onResponseEnd(controller, trailers)
83
81
  }
84
82
  }
85
83
 
86
- onError (err) {
87
- this.#handler.onError(err)
84
+ onResponseError (err) {
85
+ super.onResponseError(err)
88
86
  }
89
87
  }
90
88
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.0.0",
3
+ "version": "7.1.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": {