-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]>
- Loading branch information
1 parent
a9a2dd3
commit 17a5233
Showing
1 changed file
with
95 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |