Skip to content

Commit

Permalink
test: http complete response after socket double end
Browse files Browse the repository at this point in the history
PR-URL: #36633
Fixes: #36620
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Danielle Adams <[email protected]>
  • Loading branch information
mitsos1os authored and danielleadams committed Jan 12, 2021
1 parent a9a2dd3 commit 17a5233
Showing 1 changed file with 95 additions and 0 deletions.
95 changes: 95 additions & 0 deletions test/parallel/test-http-outgoing-end-cork.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const http = require('http');

const REQ_TIMEOUT = 500; // Set max ms of request time before abort

// Set total allowed test timeout to avoid infinite loop
// that will hang test suite
const TOTAL_TEST_TIMEOUT = 1000;

// Placeholder for sockets handled, to make sure that we
// will reach a socket re-use case.
const handledSockets = new Set();

let metReusedSocket = false; // Flag for request loop termination.

const doubleEndResponse = (res) => {
// First end the request while sending some normal data
res.end('regular end of request', 'utf8', common.mustCall());
// Make sure the response socket is uncorked after first call of end
assert.strictEqual(res.writableCorked, 0);
res.end(); // Double end the response to prep for next socket re-use.
};

const sendDrainNeedingData = (res) => {
// Send data to socket more than the high watermark so that
// it definitely needs drain
const highWaterMark = res.socket.writableHighWaterMark;
const bufferToSend = Buffer.alloc(highWaterMark + 100);
const ret = res.write(bufferToSend); // Write the request data.
// Make sure that we had back pressure on response stream.
assert.strictEqual(ret, false);
res.once('drain', () => res.end()); // End on drain.
};

const server = http.createServer((req, res) => {
const { socket: responseSocket } = res;
if (handledSockets.has(responseSocket)) { // re-used socket, send big data!
metReusedSocket = true; // stop request loop
console.debug('FOUND REUSED SOCKET!');
sendDrainNeedingData(res);
} else { // not used again
// add to make sure we recognise it when we meet socket again
handledSockets.add(responseSocket);
doubleEndResponse(res);
}
});

server.listen(0); // Start the server on a random port.

const sendRequest = (agent) => new Promise((resolve, reject) => {
const timeout = setTimeout(common.mustNotCall(() => {
reject(new Error('Request timed out'));
}), REQ_TIMEOUT);
http.get({
port: server.address().port,
path: '/',
agent
}, common.mustCall((res) => {
const resData = [];
res.on('data', (data) => resData.push(data));
res.on('end', common.mustCall(() => {
const totalData = resData.reduce((total, elem) => total + elem.length, 0);
clearTimeout(timeout); // Cancel rejection timeout.
resolve(totalData); // fulfill promise
}));
}));
});

server.once('listening', async () => {
const testTimeout = setTimeout(common.mustNotCall(() => {
console.error('Test running for a while but could not met re-used socket');
process.exit(1);
}), TOTAL_TEST_TIMEOUT);
// Explicitly start agent to force socket reuse.
const agent = new http.Agent({ keepAlive: true });
// Start the request loop
let reqNo = 0;
while (!metReusedSocket) {
try {
console.log(`Sending req no ${++reqNo}`);
const totalData = await sendRequest(agent);
console.log(`${totalData} bytes were received for request ${reqNo}`);
} catch (err) {
console.error(err);
process.exit(1);
}
}
// Successfully tested conditions and ended loop
clearTimeout(testTimeout);
console.log('Closing server');
agent.destroy();
server.close();
});

0 comments on commit 17a5233

Please sign in to comment.