GeezerVison is showing github.com — deps: update undici to 8.9.0 · nodejs/node@0354678
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/.gitignore +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -93,3 +93,6 @@ AGENTS.md | 93 | 93 | .githuman | 94 | 94 |
| 95 | 95 | benchmarks/package-lock.json | | 96 | + | | 97 | +# File generated by /scripts/platform-shell.js | | 98 | +scripts/.npmrc |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/README.md +5Lines changed: 5 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -408,6 +408,11 @@ For more information about their behavior, please reference the body mixin from | 408 | 408 |
| 409 | 409 | This section documents our most commonly used API methods. Additional APIs are documented in their own files within the [docs](./docs/) folder and are accessible via the navigation list on the left side of the docs site. | 410 | 410 |
| | 411 | +For the top-level APIs below, the `url` argument supplies the request origin and | | 412 | +path. Do not pass `origin` or `path` in the second `options` argument. The linked | | 413 | +`Dispatcher` option types include those fields because dispatcher methods are | | 414 | +lower-level APIs that do not receive a separate `url` argument. | | 415 | + | 411 | 416 | ### `undici.request([url, options]): Promise` | 412 | 417 |
| 413 | 418 | Arguments: | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/SECURITY.md +21Lines changed: 21 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -87,6 +87,17 @@ meet the following criteria: | 87 | 87 | * Dependencies installed by the application. | 88 | 88 | * The DNS resolution results provided by the operating system or configured | 89 | 89 | resolvers. | | 90 | +* The network environment in which the process runs, including any private or | | 91 | + internal networks it can reach, for the privacy of traffic on that network. | | 92 | + Deployments that require stronger isolation, monitoring, or egress controls | | 93 | + are responsible for providing them. | | 94 | +* Any proxy server configured by the application, runtime, or environment. | | 95 | + Proxies are trusted network intermediaries and must be authorized by the | | 96 | + relevant network or deployment authority. Undici's proxy support is for | | 97 | + routing traffic through such proxies, for example to reach external networks | | 98 | + through a firewall; it is not intended to provide anonymity, hide traffic, or | | 99 | + bypass organizational, regulatory, or legal controls. Untrusted proxies are | | 100 | + outside the supported threat model. | 90 | 101 |
| 91 | 102 | In other words, if untrusted data passing through undici to the application | 92 | 103 | can trigger actions other than those documented for the APIs, there is likely | @@ -172,6 +183,16 @@ lead to a loss of confidentiality, integrity, or availability. | 172 | 183 | input to request options) are the application's responsibility, not | 173 | 184 | vulnerabilities in undici. | 174 | 185 |
| | 186 | +#### Unauthorized or untrusted proxy use | | 187 | + | | 188 | +* If an application configures undici to use a proxy that is untrusted, | | 189 | + malicious, or not authorized by the relevant network or deployment authority, | | 190 | + any resulting privacy loss, policy bypass, regulatory exposure, traffic | | 191 | + manipulation, or unavailability is the deployment's responsibility. Undici | | 192 | + cannot determine whether a proxy is authorized for a user's network or | | 193 | + jurisdiction, and preventing use of proxies to hide traffic or evade controls | | 194 | + is a non-goal. | | 195 | + | 175 | 196 | ## Receiving security updates | 176 | 197 |
| 177 | 198 | Security notifications will be distributed via | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/docs/docs/api/Client.md +4Lines changed: 4 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -49,6 +49,10 @@ added: v1.0.0 | 49 | 49 | * `headersTimeout` {number|null} The timeout, in milliseconds, the parser | 50 | 50 | waits to receive the complete HTTP headers before the request times out. Use | 51 | 51 | `0` to disable it entirely. **Default:** `300e3`. | | 52 | + HTTP/1.1 headers/body parser timeouts are not guaranteed to fire with exact | | 53 | + millisecond precision: delays up to 1000ms use native timers, while larger | | 54 | + delays use undici's lower-overhead fast timers with a target resolution | | 55 | + around 500ms. | 52 | 56 | * `connectTimeout` {number|null} The timeout, in milliseconds, for | 53 | 57 | establishing a socket connection. Use `0` to disable it entirely. | 54 | 58 | **Default:** `10e3`. | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/docs/docs/api/Dispatcher.md +6Lines changed: 6 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -228,6 +228,10 @@ added: v4.0.0 | 228 | 228 | * `bodyTimeout` {number|null} The time, in milliseconds, after which the request | 229 | 229 | times out while receiving body data. Monitors the time between body chunks. | 230 | 230 | Use `0` to disable it entirely. Defaults to 300 seconds. | | 231 | + HTTP/1.1 headers/body parser timeouts are not guaranteed to fire with exact | | 232 | + millisecond precision: delays up to 1000ms use native timers, while larger | | 233 | + delays use undici's lower-overhead fast timers with a target resolution | | 234 | + around 500ms. | 231 | 235 | * `reset` {boolean} Whether the request should establish a keep-alive | 232 | 236 | connection. **Default:** `false`. | 233 | 237 | * `expectContinue` {boolean} For HTTP/2, appends the `expect: 100-continue` | @@ -729,6 +733,8 @@ await client.request({ path: '/', method: 'GET' }) | 729 | 733 | ``` | 730 | 734 |
| 731 | 735 | For the full list of built-in interceptors provided by undici, see [Interceptors](Interceptors.md). | | 736 | +For an example of a custom interceptor that wraps handler callbacks, see | | 737 | +[Custom interceptors](Interceptors.md#custom-interceptors). | 732 | 738 |
| 733 | 739 | ### Event: `'connect'` | 734 | 740 |
| |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/docs/docs/api/Interceptors.md +59Lines changed: 59 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -32,6 +32,65 @@ const client = new Client('https://example.com').compose( | 32 | 32 |
| 33 | 33 | --- | 34 | 34 |
| | 35 | +## Custom interceptors | | 36 | + | | 37 | +Custom interceptors use the same shape as | | 38 | +[`dispatcher.compose()`](./Dispatcher.md#dispatchercomposeinterceptors-interceptor): | | 39 | +an interceptor takes a `dispatch` function and returns another dispatch-like | | 40 | +function with the same `(options, handler)` signature. | | 41 | + | | 42 | +When an interceptor wraps the handler, forward the callbacks that it does not | | 43 | +handle itself. The complete handler callback list is documented under | | 44 | +[`dispatcher.dispatch(options, handler)`](./Dispatcher.md#dispatcherdispatchoptions-handler). | | 45 | + | | 46 | +```js | | 47 | +import { Agent } from 'undici' | | 48 | + | | 49 | +const timingInterceptor = dispatch => { | | 50 | + return (options, handler) => { | | 51 | + const started = performance.now() | | 52 | + | | 53 | + return dispatch(options, { | | 54 | + ...handler, | | 55 | + onResponseStart (controller, statusCode, headers, statusMessage) { | | 56 | + const duration = Math.round(performance.now() - started) | | 57 | + const method = options.method ?? 'GET' | | 58 | + const origin = options.origin ?? '' | | 59 | + | | 60 | + console.log(`${method} ${origin}${options.path} -> ${statusCode} in ${duration}ms`) | | 61 | + | | 62 | + return handler.onResponseStart?.( | | 63 | + controller, | | 64 | + statusCode, | | 65 | + headers, | | 66 | + statusMessage | | 67 | + ) | | 68 | + }, | | 69 | + onResponseError (controller, error) { | | 70 | + const duration = Math.round(performance.now() - started) | | 71 | + | | 72 | + console.error(`request failed after ${duration}ms`, error) | | 73 | + | | 74 | + return handler.onResponseError?.(controller, error) | | 75 | + } | | 76 | + }) | | 77 | + } | | 78 | +} | | 79 | + | | 80 | +const dispatcher = new Agent().compose(timingInterceptor) | | 81 | + | | 82 | +const { body } = await dispatcher.request({ | | 83 | + origin: 'https://example.com', | | 84 | + path: '/', | | 85 | + method: 'GET' | | 86 | +}) | | 87 | + | | 88 | +await body.dump() | | 89 | +await dispatcher.close() | | 90 | +``` | | 91 | +
| | 92 | +--- | | 93 | +
| 35 | 94 | ## `interceptors.dump([opts])` | 36 | 95 |
| 37 | 96 | Reads and discards the response body up to a configurable size limit. Useful | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/docs/docs/api/MockAgent.md +27Lines changed: 27 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -17,6 +17,13 @@ registered on the {MockClient} or {MockPool} instances returned by | 17 | 17 | `MockAgent` is set as the dispatcher (for example through | 18 | 18 | [`setGlobalDispatcher()`][] or a per-request `dispatcher` option). | 19 | 19 |
| | 20 | +> [!NOTE] | | 21 | +> [`setGlobalDispatcher()`][] only affects undici APIs that use the global | | 22 | +> dispatcher, such as `request()` and `fetch()`. It does not replace or | | 23 | +> monkeypatch {Pool} or {Client} instances that were created separately. To test | | 24 | +> code that accepts or creates a pool/client directly, pass the {MockPool} or | | 25 | +> {MockClient} returned by [`mockAgent.get(origin)`][] into that code. | | 26 | +
| 20 | 27 | ```mjs | 21 | 28 | import { MockAgent } from 'undici' | 22 | 29 |
| @@ -139,6 +146,26 @@ for await (const data of body) { | 139 | 146 | } | 140 | 147 | ``` | 141 | 148 |
| | 149 | +```mjs displayName="Testing code that accepts a pool" | | 150 | +import { MockAgent } from 'undici' | | 151 | + | | 152 | +async function getStatus (pool) { | | 153 | + const { statusCode } = await pool.request({ | | 154 | + path: '/foo', | | 155 | + method: 'GET' | | 156 | + }) | | 157 | + | | 158 | + return statusCode | | 159 | +} | | 160 | + | | 161 | +const mockAgent = new MockAgent() | | 162 | +const mockPool = mockAgent.get('http://localhost:3000') | | 163 | + | | 164 | +mockPool.intercept({ path: '/foo', method: 'GET' }).reply(200) | | 165 | + | | 166 | +console.log(await getStatus(mockPool)) // 200 | | 167 | +``` | | 168 | + | 142 | 169 | ```mjs displayName="Returning a MockClient" | 143 | 170 | import { MockAgent, request } from 'undici' | 144 | 171 |
| |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/docs/docs/api/MockPool.md +28-1Lines changed: 28 additions & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -136,7 +136,8 @@ The reply behaviour of a matching request is defined through the returned | 136 | 136 | computing all reply options dynamically rather than just the body. | 137 | 137 | * `callback` {Function} A `(opts: MockResponseCallbackOptions) => | 138 | 138 | { statusCode, data, responseOptions }` function invoked with the incoming | 139 | | - request. | | 139 | + request. The callback may be asynchronous; a returned promise is awaited | | 140 | + and must resolve to the same shape. | 140 | 141 | * Returns: {MockScope} | 141 | 142 | * `replyWithError(error)` {Function} Defines an error for a matching request to | 142 | 143 | throw. | @@ -263,6 +264,32 @@ for await (const data of body) { | 263 | 264 | } | 264 | 265 | ``` | 265 | 266 |
| | 267 | +```mjs displayName="Reply with an asynchronous options callback" | | 268 | +import { readFile } from 'node:fs/promises' | | 269 | +import { MockAgent, setGlobalDispatcher, request } from 'undici' | | 270 | + | | 271 | +const mockAgent = new MockAgent() | | 272 | +setGlobalDispatcher(mockAgent) | | 273 | + | | 274 | +const mockPool = mockAgent.get('http://localhost:3000') | | 275 | + | | 276 | +mockPool.intercept({ | | 277 | + path: '/fixture', | | 278 | + method: 'GET' | | 279 | +}).reply(async ({ path }) => ({ | | 280 | + statusCode: 200, | | 281 | + data: await readFile(new URL('./fixture.json', import.meta.url)) | | 282 | +})) | | 283 | + | | 284 | +const { statusCode, body } = await request('http://localhost:3000/fixture') | | 285 | + | | 286 | +console.log('response received', statusCode) // response received 200 | | 287 | + | | 288 | +for await (const data of body) { | | 289 | + console.log('data', data.toString('utf8')) // contents of fixture.json | | 290 | +} | | 291 | +``` | | 292 | +
| 266 | 293 | ```mjs displayName="Multiple intercepts" | 267 | 294 | import { MockAgent, setGlobalDispatcher, request } from 'undici' | 268 | 295 |
| |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/docs/docs/index.md +5Lines changed: 5 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -410,6 +410,11 @@ For more information about their behavior, please reference the body mixin from | 410 | 410 |
| 411 | 411 | This section documents our most commonly used API methods. Additional APIs are documented in their own files within the [docs](./docs/) folder and are accessible via the navigation list on the left side of the docs site. | 412 | 412 |
| | 413 | +For the top-level APIs below, the `url` argument supplies the request origin and | | 414 | +path. Do not pass `origin` or `path` in the second `options` argument. The linked | | 415 | +`Dispatcher` option types include those fields because dispatcher methods are | | 416 | +lower-level APIs that do not receive a separate `url` argument. | | 417 | + | 413 | 418 | ### `undici.request([url, options])` | 414 | 419 |
| 415 | 420 | * `url` {string|URL|UrlObject} | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/lib/cache/memory-cache-store.js +54-9Lines changed: 54 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -218,17 +218,62 @@ class MemoryCacheStore extends EventEmitter { | 218 | 218 | } | 219 | 219 |
| 220 | 220 | function findEntry (key, entries, now) { | 221 | | - return entries.find((entry) => ( | 222 | | - entry.deleteAt > now && | 223 | | - entry.method === key.method && | 224 | | - (entry.vary == null || Object.keys(entry.vary).every(headerName => { | 225 | | - if (entry.vary[headerName] === null) { | 226 | | - return key.headers[headerName] === undefined | | 221 | + for (let i = 0; i < entries.length; i++) { | | 222 | + const entry = entries[i] | | 223 | + if ( | | 224 | + entry.deleteAt > now && | | 225 | + entry.method === key.method && | | 226 | + varyMatches(key, entry) | | 227 | + ) { | | 228 | + return entry | | 229 | + } | | 230 | + } | | 231 | +} | | 232 | + | | 233 | +function varyMatches (key, entry) { | | 234 | + if (entry.vary == null) { | | 235 | + return true | | 236 | + } | | 237 | + | | 238 | + for (const headerName in entry.vary) { | | 239 | + if (Object.hasOwn(entry.vary, headerName) && !headerValueEquals(key.headers?.[headerName], entry.vary[headerName])) { | | 240 | + return false | | 241 | + } | | 242 | + } | | 243 | + | | 244 | + return true | | 245 | +} | | 246 | + | | 247 | +/** | | 248 | + * @param {string|string[]|null|undefined} lhs | | 249 | + * @param {string|string[]|null|undefined} rhs | | 250 | + * @returns {boolean} | | 251 | + */ | | 252 | +function headerValueEquals (lhs, rhs) { | | 253 | + if (lhs == null && rhs == null) { | | 254 | + return true | | 255 | + } | | 256 | + | | 257 | + if ((lhs == null && rhs != null) || | | 258 | + (lhs != null && rhs == null)) { | | 259 | + return false | | 260 | + } | | 261 | + | | 262 | + if (Array.isArray(lhs) && Array.isArray(rhs)) { | | 263 | + if (lhs.length !== rhs.length) { | | 264 | + return false | | 265 | + } | | 266 | + | | 267 | + for (let i = 0; i < lhs.length; i++) { | | 268 | + if (lhs[i] !== rhs[i]) { | | 269 | + return false | 227 | 270 | } | | 271 | + } | | 272 | + | | 273 | + return true | | 274 | + } | 228 | 275 |
| 229 | | - return entry.vary[headerName] === key.headers[headerName] | 230 | | - })) | 231 | | - )) | | 276 | + return lhs === rhs | 232 | 277 | } | 233 | 278 |
| 234 | 279 | module.exports = MemoryCacheStore |
|