Skip to content

Commit b60a081

Browse files
climba03003github-actions[bot]
authored andcommitted
fix: throw on retry when payload is consume by downstream (#3389)
* fix: throw on retry when payload is consumed * fixup * fixup (cherry picked from commit 71121e5)
1 parent be0c779 commit b60a081

File tree

2 files changed

+69
-2
lines changed

2 files changed

+69
-2
lines changed

lib/handler/retry-handler.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,18 @@ class RetryHandler {
192192
if (this.resume != null) {
193193
this.resume = null
194194

195-
if (statusCode !== 206) {
196-
return true
195+
// Only Partial Content 206 supposed to provide Content-Range,
196+
// any other status code that partially consumed the payload
197+
// should not be retry because it would result in downstream
198+
// wrongly concatanete multiple responses.
199+
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
200+
this.abort(
201+
new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
202+
headers,
203+
data: { count: this.retryCount }
204+
})
205+
)
206+
return false
197207
}
198208

199209
const contentRange = parseRangeHeader(headers['content-range'])

test/issue-3356.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict'
2+
3+
const { tspl } = require('@matteo.collina/tspl')
4+
const { test, after } = require('node:test')
5+
const { createServer } = require('node:http')
6+
const { once } = require('node:events')
7+
8+
const { fetch, Agent, RetryAgent } = require('..')
9+
10+
test('https://github.com/nodejs/undici/issues/3356', async (t) => {
11+
t = tspl(t, { plan: 3 })
12+
13+
let shouldRetry = true
14+
const server = createServer()
15+
server.on('request', (req, res) => {
16+
res.writeHead(200, { 'content-type': 'text/plain' })
17+
if (shouldRetry) {
18+
shouldRetry = false
19+
20+
res.flushHeaders()
21+
res.write('h')
22+
setTimeout(() => { res.end('ello world!') }, 100)
23+
} else {
24+
res.end('hello world!')
25+
}
26+
})
27+
28+
server.listen(0)
29+
30+
await once(server, 'listening')
31+
32+
after(async () => {
33+
server.close()
34+
35+
await once(server, 'close')
36+
})
37+
38+
const agent = new RetryAgent(new Agent({ bodyTimeout: 50 }), {
39+
errorCodes: ['UND_ERR_BODY_TIMEOUT']
40+
})
41+
42+
const response = await fetch(`http://localhost:${server.address().port}`, {
43+
dispatcher: agent
44+
})
45+
setTimeout(async () => {
46+
try {
47+
t.equal(response.status, 200)
48+
// consume response
49+
await response.text()
50+
} catch (err) {
51+
t.equal(err.name, 'TypeError')
52+
t.equal(err.cause.code, 'UND_ERR_REQ_RETRY')
53+
}
54+
}, 200)
55+
56+
await t.completed
57+
})

0 commit comments

Comments
 (0)