Repository navigation
No keepAliveTimeout for HTTP server after answering a POST request synchronously #39137
Description
- Version: v16.4.0
- Platform: Linux test01 4.19.0-13-amd64 # 1 SMP Debian 4.19.160-2 (2020-11-28) x86_64 GNU/Linux
- Subsystem: http
What steps will reproduce the bug?
When using the "normal" NodeJS "hello world" HTTP server example, which answers any request synchronously, and sending it a POST request with a body, server.keepAliveTimeout (default 5 sec) does not work. The HTTP connection remains open ("forever"?).
Complete Testcase
Here we test all 4 combinations of sync/async response and get/post request to a simple HTTP server, and expect a connection close from server after 1 sec - which works fine for all combinations except "synchronous response for a POST request". If the connection is still open, we try another request - to prove, the connection still works:
'use strict';
const http = require('http');
const net = require('net');
const server = http.createServer((req, res) => {
if (req.url === '/async') {
req.on('data', () => {});
req.on('end', () => {
res.end('test-body');
console.log('Server: Sent response asynchronously');
});
} else {
res.end('test-body');
console.log('Server: Sent response synchronously');
}
});
server.keepAliveTimeout = 1000;
server.listen(0, async () => {
await sendRequests('async', 'get');
await sendRequests('async', 'post');
await sendRequests('sync', 'get');
await sendRequests('sync', 'post'); // keepAliveTimeout does not work here!!
server.close();
});
async function sendRequests(type, method) {
console.log(`=== Testing ${type} response for method "${method}" and wait for keep-alive-close`);
return new Promise((resolve) => {
const client = new net.Socket();
let nextReqTimeout = null;
client.connect(server.address().port, '127.0.0.1', () => {
console.log('Client: Connected to server');
httpRequest();
nextReqTimeout = setTimeout(() => {
console.log('Client: ERROR: HTTP-Connection is still open - trying another request');
httpRequest();
nextReqTimeout = setTimeout(() => {
console.log('Client: ERROR: HTTP-Connection is still open - closing from client side now');
nextReqTimeout = null;
client.end();
}, server.keepAliveTimeout * 2);
}, server.keepAliveTimeout * 2);
});
client.on('data', function(data) {
console.log('Client: Got response data');
});
client.on('close', function() {
if (nextReqTimeout) {
console.log('Client: Server closed connection as expected');
clearTimeout(nextReqTimeout);
}
resolve();
});
function httpRequest() {
const rawRequests = {
'post': 'POST /' + type + ' HTTP/1.1\r\n' +
'Connection: keep-alive\r\n' +
'Content-Type: application/x-www-form-urlencoded\r\n' +
'Content-Length: 10\r\n' +
'\r\n' +
'Test=67890',
'get': 'GET /' + type + ' HTTP/1.1\r\n' +
'Connection: keep-alive\r\n' +
'\r\n'
};
console.log('Client: Sending request');
client.write(rawRequests[method]);
}
});
}What is the expected behavior?
The keepAliveTimeout should also work after answering a POST request synchronously.
Possible cause
Maybe this is a race condition here:
- In
resOnFinishtheserver.keepAliveTimeoutis set on the socket, after sending the response - After that,
socketOnDatais called internally for the remaining POST body, which callsonParserExecuteCommon, which resets the socket timeout first - Normally
socketOnDatais called before sending the response, so in most cases it works as expected
Maybe there should be a swicth, to not reset the socket-timeout when socketOnData is called after the response was sent?
Activity
ronag commentedon Jun 30, 2021
@nodejs/http I think this is an issue. Not entirely sure how to fix it. Should we even allow reading from the request once the response has been ended?
not-implemented commentedon Jun 30, 2021
I think there are only two possibilities for the http module, when the application answers a POST request synchronously (or at least before the request body has been fully read from socket):
I think the current behaviour is not bad ... of course we should clearify that "reading from the request once the response has been ended" from application side should not be possible - maybe this is already the case?
So just not-resetting the timeout in socketOnData after the response was sent may be the easyest/best way to fix?
mcollina commentedon Jun 30, 2021
I think so. Would you like to send a PR?
not-implemented commentedon Jul 7, 2021
I tried a (draft) PR for further discussion/tips: #39295
http: fix keepAliveTimeout after answering a HTTP-POST request synchr…
mudassir733 commentedon Jun 16, 2026
If the issue is not resolved yet can i work on it?
not-implemented commentedon Jun 16, 2026
@mudassir733 you are welcome :-)
There is already an older PR draft: #39295
efekrskl commentedon Jul 8, 2026
Is this still happening? As far as I see, since v20 I can't reproduce it. Might be a side effect of another change though.
Any chance you could verify if this is still happening on your end?
HuzaifaAbdulRehman commentedon Jul 23, 2026
@efekrskl I verified this using the original synchronous-response/POST scenario on Node v24.13.0, Windows 10 x64, and I could not reproduce the original failure.
With
server.keepAliveTimeout = 250andserver.keepAliveTimeoutBuffer = 0, the server closed the connection after approximately 264 ms. With the default 1000 ms buffer, it closed after approximately 1268 ms.One detail for the original reproducer is that current Node versions include
keepAliveTimeoutBuffer, so the observation window should exceedkeepAliveTimeout + keepAliveTimeoutBuffer.This supports the behavior having been fixed indirectly, although verification on current Linux would still be useful before closing.
LeonxLJX commentedon Sep 1, 2026
greenheadHQ commentedon Sep 27, 2026
Sharing results from macOS as well, in case they help (Apple Silicon M1 Max).
I ran the original script with two changes: a
Hostheader on the requests, and an observation window longer thankeepAliveTimeout + keepAliveTimeoutBuffer. Without theHostheader, since v20.0.0 (#45597) the server replies 400 and closes the connection right away, so the keep-alive path is never reached and every case ends with "closed as expected".The
resetSocketTimeoutcall inonParserExecuteCommonthat the original report pointed at was removed in #58178, which is in v20.20.1, v22.18.0, v24.3.0, and v25.0.0 and later.