Skip to content

Commit 17a5233

Browse files
mitsos1osdanielleadams
authored andcommitted
test: http complete response after socket double end
PR-URL: #36633 Fixes: #36620 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Danielle Adams <[email protected]>
1 parent a9a2dd3 commit 17a5233

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const http = require('http');
5+
6+
const REQ_TIMEOUT = 500; // Set max ms of request time before abort
7+
8+
// Set total allowed test timeout to avoid infinite loop
9+
// that will hang test suite
10+
const TOTAL_TEST_TIMEOUT = 1000;
11+
12+
// Placeholder for sockets handled, to make sure that we
13+
// will reach a socket re-use case.
14+
const handledSockets = new Set();
15+
16+
let metReusedSocket = false; // Flag for request loop termination.
17+
18+
const doubleEndResponse = (res) => {
19+
// First end the request while sending some normal data
20+
res.end('regular end of request', 'utf8', common.mustCall());
21+
// Make sure the response socket is uncorked after first call of end
22+
assert.strictEqual(res.writableCorked, 0);
23+
res.end(); // Double end the response to prep for next socket re-use.
24+
};
25+
26+
const sendDrainNeedingData = (res) => {
27+
// Send data to socket more than the high watermark so that
28+
// it definitely needs drain
29+
const highWaterMark = res.socket.writableHighWaterMark;
30+
const bufferToSend = Buffer.alloc(highWaterMark + 100);
31+
const ret = res.write(bufferToSend); // Write the request data.
32+
// Make sure that we had back pressure on response stream.
33+
assert.strictEqual(ret, false);
34+
res.once('drain', () => res.end()); // End on drain.
35+
};
36+
37+
const server = http.createServer((req, res) => {
38+
const { socket: responseSocket } = res;
39+
if (handledSockets.has(responseSocket)) { // re-used socket, send big data!
40+
metReusedSocket = true; // stop request loop
41+
console.debug('FOUND REUSED SOCKET!');
42+
sendDrainNeedingData(res);
43+
} else { // not used again
44+
// add to make sure we recognise it when we meet socket again
45+
handledSockets.add(responseSocket);
46+
doubleEndResponse(res);
47+
}
48+
});
49+
50+
server.listen(0); // Start the server on a random port.
51+
52+
const sendRequest = (agent) => new Promise((resolve, reject) => {
53+
const timeout = setTimeout(common.mustNotCall(() => {
54+
reject(new Error('Request timed out'));
55+
}), REQ_TIMEOUT);
56+
http.get({
57+
port: server.address().port,
58+
path: '/',
59+
agent
60+
}, common.mustCall((res) => {
61+
const resData = [];
62+
res.on('data', (data) => resData.push(data));
63+
res.on('end', common.mustCall(() => {
64+
const totalData = resData.reduce((total, elem) => total + elem.length, 0);
65+
clearTimeout(timeout); // Cancel rejection timeout.
66+
resolve(totalData); // fulfill promise
67+
}));
68+
}));
69+
});
70+
71+
server.once('listening', async () => {
72+
const testTimeout = setTimeout(common.mustNotCall(() => {
73+
console.error('Test running for a while but could not met re-used socket');
74+
process.exit(1);
75+
}), TOTAL_TEST_TIMEOUT);
76+
// Explicitly start agent to force socket reuse.
77+
const agent = new http.Agent({ keepAlive: true });
78+
// Start the request loop
79+
let reqNo = 0;
80+
while (!metReusedSocket) {
81+
try {
82+
console.log(`Sending req no ${++reqNo}`);
83+
const totalData = await sendRequest(agent);
84+
console.log(`${totalData} bytes were received for request ${reqNo}`);
85+
} catch (err) {
86+
console.error(err);
87+
process.exit(1);
88+
}
89+
}
90+
// Successfully tested conditions and ended loop
91+
clearTimeout(testTimeout);
92+
console.log('Closing server');
93+
agent.destroy();
94+
server.close();
95+
});

0 commit comments

Comments
 (0)