-
Notifications
You must be signed in to change notification settings - Fork 572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The RetryHandler receives a duplicate body when the server does not support Range requests. #3356
Comments
Hmm, yeah this indeed is a bug, often due to the fact that we buffer the response in an effort of providing a range request over a next retry. It's not an easy fix, as the data get's automatically forwarded to the main handler, so the fetch handler is the one that buffered the body. We can either have a flag that disables that range request or change the way we buffer the body (maybe keep it within the Retry handler) to identify if a request can be safely resumed with the buffered body or totally discard it. Servers are not meant to reply in a specific way, if they do not support it, they can safely reply with a |
@metcoder95 I'm not sure I understand what the problem is? Cna you try to explain? Or huddle me on slack. |
Ah, I see now. You need to fail the retry if position > 0 and response is not a range request. See how https://github.com/nxtedition/nxt-undici/blob/main/lib/interceptor/response-retry.js does it. In particular look at https://github.com/nxtedition/nxt-undici/blob/main/lib/interceptor/response-retry.js#L116. |
It is more like the limitation of combination between Note that even changing the internal state of We may buffer the response inside |
Yeah, is basically what @climba03003 suggested; once as long as we started buffering we cannot revert what we've already buffered. So, here I'm also not really a fan of buffering ourselves but rather better fail on it, but wanted to put all options over the table. But believe that failing might be the best, so we avoid double-buffering. |
If it throw in this case, what is the meaning of retry handler exist? The issue is demonstrating a slow network or sudden peak of the server will break the retry mechanism. Another solution is remember the received bytes and skip all already handled bytes.
|
I'd say that the biggest problem comes with the fact that the servers might or might not support the range request. If a network issue occurs or the server just breaks, the retry can identify and continue the next retry based on the last byte read. I believe that the I also imagine a situation when they want to disable that, but that's a topic of another issue |
I believe we should think of the worst case, most server do not support
Yes, but the current implementation is heavily based on existence of The current use case will be limited to either
We have no way to knows if the down stream ever consumed the data, so provide a fix to block seems unrealistic.
The retry should support any methods that are safe (or even idempotent). |
The retry handler is useful to retry in case there is a bad response code or an error in a network condition. I used it with APIs all the time. The case of a truncated response is tricky. |
It is only true for bad response code, but not really for network condition. I means network interruption in the middle.
The problem is that the '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 { fetch, Agent, RetryAgent } = require('..')
test('https://github.com/nodejs/undici/issues/3356', async (t) => {
t = tspl(t, { plan: 2 })
let shouldRetry = true
const server = createServer()
server.on('request', (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' })
if (shouldRetry) {
shouldRetry = false
res.flushHeaders()
res.write('h')
setTimeout(() => { res.end('ello world!') }, 1000)
} else {
res.end('hello world!')
}
})
server.listen(0)
await once(server, 'listening')
after(async () => {
server.close()
await once(server, 'close')
})
const agent = new RetryAgent(new Agent({ bodyTimeout: 500 }), {
errorCodes: ['UND_ERR_BODY_TIMEOUT']
})
const response = await fetch(`http://localhost:${server.address().port}`, {
dispatcher: agent
})
setTimeout(async () => {
console.log('start consume')
t.equal(response.status, 200)
t.equal(await response.text(), 'hello world!') // you can see even delayed the consume, it is the same result.
}, 2000)
await t.completed
}) |
Yes, aborting the request might be the best option for now. Only upon responses of servers not supporting |
output:
The text was updated successfully, but these errors were encountered: