Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions deps/undici/src/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ Create a commit which includes all of the updated files in lib/llhttp.

### Steps:

`npm run test:wpt` and `node test/web-platform-tests/wpt-runner.mjs setup` will initialize the WPT submodule automatically when it is missing.

If you want to prepare the checkout explicitly, run:

```bash
git submodule update --init --recursive
```
Expand Down
56 changes: 56 additions & 0 deletions deps/undici/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,57 @@ const { statusCode, body } = await request('https://api.example.com/data');
const data = await body.json();
```

### Keep `fetch` and `FormData` together

When you send a `FormData` body, keep `fetch` and `FormData` from the same
implementation.

Use one of these patterns:

```js
// Built-in globals
const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```

```js
// undici module imports
import { fetch, FormData } from 'undici'

const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```

If you want the installed `undici` package to provide the globals, call
`install()` first:

```js
import { install } from 'undici'

install()

const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```

`install()` replaces the global `fetch`, `Headers`, `Response`, `Request`, and
`FormData` implementations with undici's versions, so they all match.

Avoid mixing a global `FormData` with `undici.fetch()`, or `undici.FormData`
with the built-in global `fetch()`.

### Version Compatibility

You can check which version of undici is bundled with your Node.js version:
Expand Down Expand Up @@ -263,6 +314,11 @@ The `install()` function adds the following classes to `globalThis`:
- `CloseEvent`, `ErrorEvent`, `MessageEvent` - WebSocket events
- `EventSource` - Server-sent events client

When you call `install()`, these globals come from the same undici
implementation. For example, global `fetch` and global `FormData` will both be
undici's versions, which is the recommended setup if you want to use undici
through globals.

This is useful for:
- Polyfilling environments that don't have fetch
- Ensuring consistent fetch behavior across different Node.js versions
Expand Down
14 changes: 8 additions & 6 deletions deps/undici/src/docs/docs/api/DiagnosticsChannel.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,24 @@ diagnosticsChannel.channel('undici:websocket:open').subscribe(({
console.log(websocket) // the WebSocket instance

// Handshake response details
console.log(handshakeResponse.status) // 101 for successful WebSocket upgrade
console.log(handshakeResponse.statusText) // 'Switching Protocols'
console.log(handshakeResponse.status) // 101 for HTTP/1.1, 200 for HTTP/2 extended CONNECT
console.log(handshakeResponse.statusText) // 'Switching Protocols' for HTTP/1.1, commonly 'OK' for HTTP/2 in Node.js
console.log(handshakeResponse.headers) // Object containing response headers
})
```

### Handshake Response Object

The `handshakeResponse` object contains the HTTP response that upgraded the connection to WebSocket:
The `handshakeResponse` object contains the HTTP response that established the WebSocket connection:

- `status` (number): The HTTP status code (101 for successful WebSocket upgrade)
- `statusText` (string): The HTTP status message ('Switching Protocols' for successful upgrade)
- `status` (number): The HTTP status code (`101` for HTTP/1.1 upgrade, `200` for HTTP/2 extended CONNECT)
- `statusText` (string): The HTTP status message (`'Switching Protocols'` for HTTP/1.1, commonly `'OK'` for HTTP/2 in Node.js)
- `headers` (object): The HTTP response headers from the server, including:
- `sec-websocket-accept` and other WebSocket-related headers
- `upgrade: 'websocket'`
- `connection: 'upgrade'`
- `sec-websocket-accept` and other WebSocket-related headers

The `upgrade` and `connection` headers are only present for HTTP/1.1 handshakes.

This information is particularly useful for debugging and monitoring WebSocket connections, as it provides access to the initial HTTP handshake response that established the WebSocket connection.

Expand Down
8 changes: 8 additions & 0 deletions deps/undici/src/docs/docs/api/Fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ This API is implemented as per the standard, you can find documentation on [MDN]

If any parameters are passed to the FormData constructor other than `undefined`, an error will be thrown. Other parameters are ignored.

When you use `FormData` as a request body, keep `fetch` and `FormData` from the
same implementation. Use the built-in global `FormData` with the built-in
global `fetch()`, and use `undici`'s `FormData` with `undici.fetch()`.

If you want the installed `undici` package to provide the globals, call
[`install()`](/docs/api/GlobalInstallation.md) so `fetch`, `Headers`,
`Response`, `Request`, and `FormData` are installed together as a matching set.

## Response

This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response)
Expand Down
48 changes: 48 additions & 0 deletions deps/undici/src/docs/docs/api/GlobalInstallation.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,54 @@ The `install()` function adds the following classes to `globalThis`:
| `MessageEvent` | WebSocket message event |
| `EventSource` | Server-sent events client |

## Using `FormData` with `fetch`

If you send a `FormData` body, use matching implementations for `fetch` and
`FormData`.

These two patterns are safe:

```js
// Built-in globals from Node.js
const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```

```js
// Globals installed from the undici package
import { install } from 'undici'

install()

const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```

After `install()`, `fetch`, `Headers`, `Response`, `Request`, and `FormData`
all come from the installed `undici` package, so they work as a matching set.

If you do not want to install globals, import both from `undici` instead:

```js
import { fetch, FormData } from 'undici'

const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```

Avoid mixing a global `FormData` with `undici.fetch()`, or `undici.FormData`
with the built-in global `fetch()`. Keeping them paired avoids surprising
multipart behavior across Node.js and undici versions.

## Use Cases

Global installation is useful for:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,93 @@ When you install undici from npm, you get the full library with all of its
additional APIs, and potentially a newer release than what your Node.js version
bundles.

## Keep `fetch` and `FormData` from the same implementation

When you send a `FormData` body, keep `fetch` and `FormData` together from the
same implementation.

Use one of these patterns:

### Built-in globals

```js
const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')

await fetch('https://example.com', {
method: 'POST',
body
})
```

### `undici` module imports

```js
import { fetch, FormData } from 'undici'

const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')

await fetch('https://example.com', {
method: 'POST',
body
})
```

### `undici.install()` globals

If you want the installed `undici` package to provide the globals, call
[`install()`](/docs/api/GlobalInstallation.md):

```js
import { install } from 'undici'

install()

const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')

await fetch('https://example.com', {
method: 'POST',
body
})
```

`install()` replaces the global `fetch`, `Headers`, `Response`, `Request`, and
`FormData` implementations with undici's versions, and also installs undici's
`WebSocket`, `CloseEvent`, `ErrorEvent`, `MessageEvent`, and `EventSource`
globals.

Avoid mixing implementations in the same request, for example:

```js
import { fetch } from 'undici'

const body = new FormData()

await fetch('https://example.com', {
method: 'POST',
body
})
```

```js
import { FormData } from 'undici'

const body = new FormData()

await fetch('https://example.com', {
method: 'POST',
body
})
```

Those combinations may behave differently across Node.js and undici versions.
Using matching pairs keeps multipart handling predictable.

## When you do NOT need to install undici

If all of the following are true, you can rely on the built-in globals and skip
Expand Down Expand Up @@ -119,12 +206,12 @@ You can always check the exact bundled version at runtime with
`process.versions.undici`.

Installing undici from npm does not replace the built-in globals. If you want
your installed version to override the global `fetch`, use
[`setGlobalDispatcher`](/docs/api/GlobalInstallation.md) or import `fetch`
your installed version to replace the global `fetch` and related classes, use
[`install()`](/docs/api/GlobalInstallation.md). Otherwise, import `fetch`
directly from `'undici'`:

```js
import { fetch } from 'undici'; // uses your installed version, not the built-in
import { fetch } from 'undici' // uses your installed version, not the built-in
```

## Further reading
Expand Down
43 changes: 43 additions & 0 deletions deps/undici/src/docs/docs/best-practices/writing-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,46 @@ const agent = new Agent({

setGlobalDispatcher(agent)
```

## Guarding against unexpected disconnects

Undici's `Client` automatically reconnects after a socket error. This means
a test can silently disconnect, reconnect, and still pass. Unfortunately,
this could mask bugs like unexpected parser errors or protocol violations.
To catch these silent reconnections, add a disconnect guard after creating
a `Client`:

```js
const { Client } = require('undici')
const { test, after } = require('node:test')
const { tspl } = require('@matteo.collina/tspl')

test('example with disconnect guard', async (t) => {
t = tspl(t, { plan: 1 })

const client = new Client('http://localhost:3000')
after(() => client.close())

client.on('disconnect', () => {
if (!client.closed && !client.destroyed) {
t.fail('unexpected disconnect')
}
})

// ... test logic ...
})
```

`client.close()` and `client.destroy()` both emit `'disconnect'` events, but
those are expected. The guard only fails when a disconnect happens during the
active test (i.e., `!client.closed && !client.destroyed` is true).

Skip the guard for tests where a disconnect is expected behavior, such as:

- Signal aborts (`signal.emit('abort')`, `ac.abort()`)
- Server-side destruction (`res.destroy()`, `req.socket.destroy()`)
- Client-side body destruction mid-stream (`data.body.destroy()`)
- Timeout errors (`HeadersTimeoutError`, `BodyTimeoutError`)
- Successful upgrades (the socket is detached from the `Client`)
- Retry/reconnect tests where the disconnect triggers the retry
- HTTP parser errors from malformed responses (`HTTPParserError`)
10 changes: 6 additions & 4 deletions deps/undici/src/lib/core/diagnostics.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,12 @@ function trackWebSocketEvents (debugLog = websocketDebuglog) {

diagnosticsChannel.subscribe('undici:websocket:open',
evt => {
const {
address: { address, port }
} = evt
debugLog('connection opened %s%s', address, port ? `:${port}` : '')
if (evt.address != null) {
const { address, port } = evt.address
debugLog('connection opened %s%s', address, port ? `:${port}` : '')
} else {
debugLog('connection opened')
}
})

diagnosticsChannel.subscribe('undici:websocket:close',
Expand Down
16 changes: 12 additions & 4 deletions deps/undici/src/lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,21 @@ function processHeader (request, key, val) {
} else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
throw new InvalidArgumentError(`invalid ${headerName} header`)
} else if (headerName === 'connection') {
const value = typeof val === 'string' ? val.toLowerCase() : null
if (value !== 'close' && value !== 'keep-alive') {
// Per RFC 7230 Section 6.1, Connection header can contain
// a comma-separated list of connection option tokens (header names)
const value = typeof val === 'string' ? val : null
if (value === null) {
throw new InvalidArgumentError('invalid connection header')
}

if (value === 'close') {
request.reset = true
for (const token of value.toLowerCase().split(',')) {
const trimmed = token.trim()
if (!isValidHTTPToken(trimmed)) {
throw new InvalidArgumentError('invalid connection header')
}
if (trimmed === 'close') {
request.reset = true
}
}
} else if (headerName === 'expect') {
throw new NotSupportedError('expect header not supported')
Expand Down
Loading
Loading