Skip to content

Commit 71121e5

Browse files
authored
fix: throw on retry when payload is consume by downstream (#3389)
* fix: throw on retry when payload is consumed * fixup * fixup
1 parent 4b0921c commit 71121e5

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

lib/handler/retry-handler.js

Lines changed: 12 additions & 2 deletions
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

Lines changed: 57 additions & 0 deletions
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)