Skip to content

Commit 085bb3a

Browse files
committed
test: http complete response after socket double end
1 parent 3947f0e commit 085bb3a

File tree

1 file changed

+91
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)