From ab540347bec07a7488791320f6763ae1684fe366 Mon Sep 17 00:00:00 2001 From: eliotschu Date: Sat, 6 Dec 2025 15:00:21 -0600 Subject: [PATCH] fix: handle FormData body type correctly in RetryAgent retried requests --- lib/core/util.js | 2 ++ test/issue-4691.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 test/issue-4691.js diff --git a/lib/core/util.js b/lib/core/util.js index fd5b0dfaafb..abfa156f153 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -58,6 +58,8 @@ function wrapRequestBody (body) { // to determine whether or not it has been disturbed. This is just // a workaround. return new BodyAsyncIterable(body) + } else if (body && isFormDataLike(body)) { + return body } else if ( body && typeof body !== 'string' && diff --git a/test/issue-4691.js b/test/issue-4691.js new file mode 100644 index 00000000000..3bd936f2312 --- /dev/null +++ b/test/issue-4691.js @@ -0,0 +1,71 @@ +'use strict' + +const { tspl } = require('@matteo.collina/tspl') +const { test, after } = require('node:test') +const { createServer } = require('node:http') +const { once } = require('node:events') + +const { RetryAgent, Client, FormData } = require('..') +// https://github.com/nodejs/undici/issues/4691 +test('Should retry status code with FormData body', async t => { + t = tspl(t, { plan: 2 }) + + let counter = 0 + const server = createServer({ joinDuplicateHeaders: true }) + const opts = { + maxRetries: 5, + timeout: 1, + timeoutFactor: 1 + } + + server.on('request', (req, res) => { + switch (counter++) { + case 0: + req.destroy() + return + case 1: + res.writeHead(500) + res.end('failed') + return + case 2: + res.writeHead(200) + res.end('hello world!') + return + default: + t.fail() + } + }) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`) + const agent = new RetryAgent(client, opts) + + after(async () => { + await agent.close() + server.close() + + await once(server, 'close') + }) + + const formData = new FormData() + formData.append('field', 'test string') + agent.request({ + method: 'GET', + path: '/', + headers: { + 'content-type': 'application/json' + }, + body: formData + }).then((res) => { + t.equal(res.statusCode, 200) + res.body.setEncoding('utf8') + let chunks = '' + res.body.on('data', chunk => { chunks += chunk }) + res.body.on('end', () => { + t.equal(chunks, 'hello world!') + }) + }) + }) + + await t.completed +})