|
10 | 10 | * [Building for externally shared node builtins](#external-builds) |
11 | 11 | * [Benchmarks](#benchmarks) |
12 | 12 | * [Documentation](#documentation) |
| 13 | + * [Reproduction](#reproduction) |
13 | 14 | * [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin) |
14 | 15 | * [Moderation Policy](#moderation-policy) |
15 | 16 |
|
|
58 | 59 | > This requires [docker](https://www.docker.com/) installed on your machine. |
59 | 60 |
|
60 | 61 | ```bash |
61 | | -npm run build-wasm |
| 62 | +npm run build:wasm |
62 | 63 | ``` |
63 | 64 |
|
64 | 65 | #### Copy the sources to `undici` |
@@ -198,6 +199,157 @@ cd docs && npm i && npm run serve |
198 | 199 |
|
199 | 200 | The documentation will be available at `http://localhost:3000`. |
200 | 201 |
|
| 202 | +<a id="reproduction"></a> |
| 203 | +### Reproduction |
| 204 | + |
| 205 | +When reporting a bug, a high-quality reproduction helps maintainers diagnose and fix the issue quickly. Follow the guidelines below to create a minimal, standalone reproduction script. |
| 206 | + |
| 207 | +#### What Makes a Good Reproduction |
| 208 | + |
| 209 | +- **Standalone**: The script must be self-contained and run without external dependencies beyond `undici` and Node.js built-in modules. |
| 210 | +- **Minimal**: Strip everything unrelated to the bug — no production configs, no frameworks, no unnecessary middleware. |
| 211 | +- **Reproducible**: Run the script and confirm the bug occurs before submitting. |
| 212 | +- **Specific**: Include the exact `undici` API call that triggers the issue (e.g., `Client`, `Pool`, `Agent`, `fetch`, `request`, `stream`, `pipeline`). |
| 213 | +- **Run with Node's test runner**: Use `node --test` (Node 20+) or `node test/your-repro.js`. |
| 214 | + |
| 215 | +#### Standalone Server Pattern |
| 216 | + |
| 217 | +Use `createServer` from `node:http` (or `node:https` for TLS-related bugs) to run a local server inside the same script. This avoids external service dependencies and keeps the reproduction fully self-contained. |
| 218 | + |
| 219 | +```javascript |
| 220 | +'use strict' |
| 221 | + |
| 222 | +const { test } = require('node:test') |
| 223 | +const { createServer } = require('node:http') |
| 224 | +const { once } = require('node:events') |
| 225 | +const { Client } = require('undici') |
| 226 | + |
| 227 | +// https://github.com/nodejs/undici/issues/XXXX |
| 228 | + |
| 229 | +test('short description of the bug', { timeout: 60000 }, async (t) => { |
| 230 | + t.plan(1) |
| 231 | + |
| 232 | + // 1. Start a local HTTP server that reproduces the scenario |
| 233 | + const server = createServer({ joinDuplicateHeaders: true }, async (req, res) => { |
| 234 | + // Simulate the bug-triggering response |
| 235 | + res.statusCode = 200 |
| 236 | + res.setHeader('content-length', 100) |
| 237 | + res.end('hello'.repeat(20)) |
| 238 | + }) |
| 239 | + t.after(() => { server.closeAllConnections?.(); server.close() }) |
| 240 | + |
| 241 | + server.listen(0) |
| 242 | + await once(server, 'listening') |
| 243 | + |
| 244 | + // 2. Create the undici client pointing to the local server |
| 245 | + const client = new Client(`http://localhost:${server.address().port}`) |
| 246 | + t.after(() => client.close()) |
| 247 | + |
| 248 | + // 3. Perform the request that triggers the bug |
| 249 | + const { body } = await client.request({ path: '/', method: 'GET' }) |
| 250 | + |
| 251 | + let responseBody = '' |
| 252 | + for await (const chunk of body) { |
| 253 | + responseBody += chunk |
| 254 | + } |
| 255 | + |
| 256 | + t.assert.strictEqual(responseBody, 'hello'.repeat(20)) |
| 257 | +}) |
| 258 | +``` |
| 259 | +
|
| 260 | +Save the script as `test/repro-XXXX.js` and run it: |
| 261 | +
|
| 262 | +```bash |
| 263 | +node --test test/repro-XXXX.js |
| 264 | +``` |
| 265 | +
|
| 266 | +#### Fetch-Based Reproduction |
| 267 | +
|
| 268 | +For bugs involving `fetch`, use the same standalone server pattern: |
| 269 | +
|
| 270 | +```javascript |
| 271 | +'use strict' |
| 272 | + |
| 273 | +const { test } = require('node:test') |
| 274 | +const { createServer } = require('node:http') |
| 275 | +const { once } = require('node:events') |
| 276 | +const { fetch, Agent } = require('undici') |
| 277 | + |
| 278 | +// https://github.com/nodejs/undici/issues/XXXX |
| 279 | + |
| 280 | +test('short description of the fetch bug', { timeout: 60000 }, async (t) => { |
| 281 | + t.plan(1) |
| 282 | + |
| 283 | + const server = createServer({ joinDuplicateHeaders: true }, async (req, res) => { |
| 284 | + res.statusCode = 200 |
| 285 | + res.setHeader('content-type', 'application/json') |
| 286 | + res.end(JSON.stringify({ ok: true })) |
| 287 | + }) |
| 288 | + t.after(() => server.close()) |
| 289 | + |
| 290 | + server.listen(0) |
| 291 | + await once(server, 'listening') |
| 292 | + |
| 293 | + const agent = new Agent({ keepAliveTimeout: 1 }) |
| 294 | + t.after(() => agent.close()) |
| 295 | + |
| 296 | + const response = await fetch(`http://localhost:${server.address().port}/`, { |
| 297 | + dispatcher: agent |
| 298 | + }) |
| 299 | + |
| 300 | + await response.body.cancel() |
| 301 | + |
| 302 | + t.assert.strictEqual(response.status, 200) |
| 303 | +}) |
| 304 | +``` |
| 305 | +
|
| 306 | +#### Using Pool or Agent |
| 307 | +
|
| 308 | +For bugs involving connection pooling or agent behavior: |
| 309 | +
|
| 310 | +```javascript |
| 311 | +'use strict' |
| 312 | + |
| 313 | +const { test } = require('node:test') |
| 314 | +const { createServer } = require('node:http') |
| 315 | +const { once } = require('node:events') |
| 316 | +const { Pool } = require('undici') |
| 317 | + |
| 318 | +// https://github.com/nodejs/undici/issues/XXXX |
| 319 | + |
| 320 | +test('Pool bug reproduction', { timeout: 60000 }, async (t) => { |
| 321 | + t.plan(1) |
| 322 | + |
| 323 | + const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { |
| 324 | + res.end('ok') |
| 325 | + }) |
| 326 | + t.after(() => server.close()) |
| 327 | + |
| 328 | + server.listen(0) |
| 329 | + await once(server, 'listening') |
| 330 | + |
| 331 | + const pool = new Pool(`http://localhost:${server.address().port}`) |
| 332 | + t.after(() => pool.close()) |
| 333 | + |
| 334 | + const data = await pool.request({ path: '/', method: 'GET' }) |
| 335 | + await data.body.dump() |
| 336 | + |
| 337 | + t.assert.strictEqual(data.statusCode, 200) |
| 338 | +}) |
| 339 | +``` |
| 340 | +
|
| 341 | +#### Adding the Reproduction to the Repository |
| 342 | +
|
| 343 | +If you are submitting a bug fix via a pull request, include a test file `test/issue-XXXX.js` that reproduces the bug and passes with the fix. |
| 344 | +
|
| 345 | +#### Checklist for Submitting a Bug Report |
| 346 | +
|
| 347 | +1. [ ] Write a standalone reproduction script. |
| 348 | +2. [ ] Run it locally to confirm the bug is reproducible. |
| 349 | +3. [ ] Attach the script (or a gist link) in the bug report. |
| 350 | +4. [ ] Include the exact undici version and Node.js version. |
| 351 | +5. [ ] Describe the expected vs. actual behavior. |
| 352 | +
|
201 | 353 | <a id="developers-certificate-of-origin"></a> |
202 | 354 | ## Developer's Certificate of Origin 1.1 |
203 | 355 |
|
|
0 commit comments