The client depends on node-fetch v2.6.1
testServer.js
// -- DevNull Start --
var util = require('util')
, stream = require('stream')
, Writable = stream.Writable
, setImmediate = setImmediate || function (fn) { setTimeout(fn, 0) }
;
util.inherits(DevNull, Writable);
function DevNull (opts) {
if (!(this instanceof DevNull)) return new DevNull(opts);
opts = opts || {};
Writable.call(this, opts);
}
DevNull.prototype._write = function (chunk, encoding, cb) {
setImmediate(cb);
}
// -- DevNull End --
const http = require('http');
const server = http.createServer();
server.on('request', async (req, res) => {
try {
req.socket.on('end', function() {
console.log('SOCKET END: other end of the socket sends a FIN packet');
});
req.socket.on('timeout', function() {
console.log('SOCKET TIMEOUT');
});
req.socket.on('error', function(error) {
console.log('SOCKET ERROR: ' + JSON.stringify(error));
});
req.socket.on('close', function(had_error) {
console.log('SOCKET CLOSED. IT WAS ERROR: ' + had_error);
});
const writeStream = DevNull();
const promise = new Promise((resolve, reject) => {
req.on('end', resolve);
req.on('error', reject);
});
req.pipe(writeStream);
await promise;
res.writeHead(200);
res.end('OK');
} catch (err) {
res.writeHead(500);
res.end(err.message);
}
});
server.listen(8081)
.on('listening', () => { console.log('Listening on port', server.address().port); });
testClient.js
// -- RandomStream Start --
var crypto = require('crypto');
var stream = require('stream');
var util = require('util');
var Readable = stream.Readable;
function RandomStream(length, options) {
// allow calling with or without new
if (!(this instanceof RandomStream)) {
return new RandomStream(length, options);
}
// init Readable
Readable.call(this, options);
// save the length to generate
this.lenToGenerate = length;
}
util.inherits(RandomStream, Readable);
RandomStream.prototype._read = function (size) {
if (!size) size = 1024; // default size
var ready = true;
while (ready) { // only cont while push returns true
if (size > this.lenToGenerate) { // only this left
size = this.lenToGenerate;
}
if (size) {
ready = this.push(crypto.randomBytes(size));
this.lenToGenerate -= size;
}
// when done, push null and exit loop
if (!this.lenToGenerate) {
this.push(null);
ready = false;
}
}
};
// -- RandomStream End --
const fetch = require('node-fetch');
const runSuccess = async () => { // Runs in ~35 seconds
const t = Date.now();
try {
const resp = await fetch('http://localhost:8081/test', {
method: 'PUT',
body: new RandomStream(256e6) // new RandomStream(1024e6)
});
const data = await resp.text();
console.log(Date.now() - t, data);
} catch (err) {
console.warn(Date.now() - t, err);
}
};
const runFail = async () => { // Fails after 60 seconds
const t = Date.now();
try {
const resp = await fetch('http://localhost:8081/test', {
method: 'PUT',
body: new RandomStream(1024e6)
});
const data = await resp.text();
console.log(Date.now() - t, data);
} catch (err) {
console.warn(Date.now() - t, err);
}
};
// runSuccess().then(() => process.exit(0));
runFail().then(() => process.exit(0));
npm i node-fetchnode testServer.js - This creates a new HTTP server listening on port 8081node testClient.jsThe value inside RandomStream needs to be high enough to cause the request to take longer than 60 seconds.
In Node.js 10:
$ node testClient.js
145014 'OK'
In Node.js 12:
$ node testClient.js
60014 FetchError: request to http://localhost:8081/test failed, reason: write ECONNRESET
at ClientRequest.<anonymous> (/home/*snip*/node_modules/node-fetch/lib/index.js:1461:11)
at ClientRequest.emit (events.js:326:22)
at Socket.socketErrorListener (_http_client.js:428:9)
at Socket.emit (events.js:314:20)
at errorOrDestroy (internal/streams/destroy.js:108:12)
at onwriteError (_stream_writable.js:418:5)
at onwrite (_stream_writable.js:445:5)
at internal/streams/destroy.js:50:7
at Socket._destroy (net.js:681:5)
at Socket.destroy (internal/streams/destroy.js:38:8) {
type: 'system',
errno: 'ECONNRESET',
code: 'ECONNRESET'
}
If there is any workaround, please let me know.
I'm facing the same issue on the same platform.
Do you think you could try to create a more minimal repro? Maybe without using node-fetch?
For the moment no, however I was able to fuss with this a bit more.
Setting server.headersTimeout = 10000; as the last line of the server. Then the reproduction client returns:
10041 FetchError: request to http://localhost:8081/test failed, reason: read ECONNRESET
Setting server.headersTimeout = 0; causes the reproduction client to return a similar output to Node.js 10
Experiencing the same issue on Node 12.9.0 but not Node 12.8.2.
Could this change be related to the issue?
https://github.com/nodejs/node/pull/32329
@ronag
I created a repro without node-fetch based on @ipbc-dev's code: https://github.com/neversun/node-issue-35661
There are 3 examples of a working (short) request, a long one with around 2 MB of data and a long but slow one with ~100KB of data.
What I see is, that it works on 12.18.2 and fails on 12.19.0:
{CLIENT} [read] 60.948s, size of read 16384, left 147456
{CLIENT} Error: socket hang up
at connResetException (internal/errors.js:609:14)
at Socket.socketOnEnd (_http_client.js:459:23)
at Socket.emit (events.js:326:22)
at endReadableNT (_stream_readable.js:1223:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
code: 'ECONNRESET'
}
````
Also I found a strange thing to happen on 12.19.0 if you attach a timeout handler like...
```js
const server = http.createServer()
// server.on('request', async (req, res) => {
// ...
// })
server.on('timeout', () => {
console.log('timeout')
})
...then the timeout handler is going to be called at around 60s. The request will come to a successful end then.
The timeout handler is not called on 12.18.2.
EDIT:
Platform: macOS (System Version: macOS 10.15.7 (19H2), Kernel Version: Darwin 19.6.0)
@nodejs/http
The problem is in the server. Here's a minimal example with no dependencies. It creates a server and a client which opens a POST request and keeps transmitting all the time. After 8s it is considered a success. With Node 12.19.0 it fails after 5s with "Premature close".
const http = require('http');
const stream = require('stream');
const time = () => new Date().toISOString();
const server = http.createServer((req, res) => {
console.log(time(), 'Received request');
stream.finished(req, (error) => {
console.error(time(), 'Stream error:', error && error.message);
process.exit(1);
});
});
server.headersTimeout = 5000;
server.listen(7000);
const options = {
host: 'localhost',
port: 7000,
path: '/',
method: 'POST',
headers: {
'Content-Type': 'application/x-octet-stream'
}
};
var req = http.request(options);
req.on('error', (err) => console.error(time(), 'client error', err));
// post the data
setInterval(() => req.write('hello'), 100);
setTimeout(() => {
console.log(time(), 'Success!');
process.exit(0);
}, 8000);
Call stack on error:
<anonymous> (f:\Projects\jstest\test.js:7)
<anonymous> (<node_internals>/internal/util.js:392)
onclose (<node_internals>/internal/streams/end-of-stream.js:76)
emit (<node_internals>/events.js:314)
abortIncoming (<node_internals>/_http_server.js:533)
socketOnClose (<node_internals>/_http_server.js:526)
emit (<node_internals>/events.js:326)
<anonymous> (<node_internals>/net.js:676)
callbackTrampoline (<node_internals>/internal/async_hooks.js:126)
TCPWRAP (Unknown Source:1)
init (<node_internals>/internal/inspector_async_hook.js:25)
emitInitNative (<node_internals>/internal/async_hooks.js:195)
I also suspect https://github.com/nodejs/node/pull/34131. It doesn't reproduce with 14.14.0, so I suppose the backport was partial, or it depends on another commit that was not backported?
Makes a lot of sense :)
backport PR welcome
@mcollina @addaleax FYI This seems like a valid issue.
@ronag Can you remember why you added dont-land-on-v12.x on #34578?
@Flarna probably because https://github.com/nodejs/node/pull/34131 wasn't backported
We've landed a fix in the 12.20.0-rc
Could someone please check to see if this fixes their issues?
-- Please reply above this line --
Hello!
We received your request and will get in touch with you as soon as
possible.
We usually reply WITHIN 24 HOURS BETWEEN MONDAY TO FRIDAY.
In the meantime, you can have a look at the BitTube Knowledge Base:
https://kb.bittube.app [1] and reply to this email with 'SOLVED', if
you found the answer to your question.
Thanks for your understanding.
Talk to you soon,
BitTube Team.
We've landed a fix in the 12.20.0-rc
Could someone please check to see if this fixes their issues?
@MylesBorins Is it possible to provide a Docker image build? Then I could test it very quickly.
All tests of my repro are passing with 12.20.0-rc.1 馃憤
Most helpful comment
@ronag
I created a repro without node-fetch based on @ipbc-dev's code: https://github.com/neversun/node-issue-35661
There are 3 examples of a working (short) request, a long one with around 2 MB of data and a long but slow one with ~100KB of data.
What I see is, that it works on 12.18.2 and fails on 12.19.0:
...then the timeout handler is going to be called at around 60s. The request will come to a successful end then.
The timeout handler is not called on 12.18.2.
EDIT:
Platform: macOS (System Version: macOS 10.15.7 (19H2), Kernel Version: Darwin 19.6.0)