Skip to content

Commit

Permalink
[fix] Fix case where abortHandshake() does not close the connection
Browse files Browse the repository at this point in the history
On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if
called after the request completed.

Fixes #1869
  • Loading branch information
lpinca committed Apr 18, 2021
1 parent 23ba6b2 commit 67e25ff
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 2 deletions.
10 changes: 10 additions & 0 deletions lib/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,16 @@ function abortHandshake(websocket, stream, message) {

if (stream.setHeader) {
stream.abort();

if (stream.socket && !stream.socket.destroyed) {
//
// On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if
// called after the request completed. See
// https://github.com/websockets/ws/issues/1869.
//
stream.socket.destroy();
}

stream.once('abort', websocket.emitClose.bind(websocket));
websocket.emit('error', err);
} else {
Expand Down
52 changes: 50 additions & 2 deletions test/websocket.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,7 @@ describe('WebSocket', () => {
});

describe('#close', () => {
it('closes the connection if called while connecting (1/2)', (done) => {
it('closes the connection if called while connecting (1/3)', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);

Expand All @@ -1461,7 +1461,7 @@ describe('WebSocket', () => {
});
});

it('closes the connection if called while connecting (2/2)', (done) => {
it('closes the connection if called while connecting (2/3)', (done) => {
const wss = new WebSocket.Server(
{
verifyClient: (info, cb) => setTimeout(cb, 300, true),
Expand All @@ -1484,6 +1484,54 @@ describe('WebSocket', () => {
);
});

it('closes the connection if called while connecting (3/3)', (done) => {
const server = http.createServer();

server.listen(0, function () {
const ws = new WebSocket(`ws://localhost:${server.address().port}`);

ws.on('open', () => done(new Error("Unexpected 'open' event")));
ws.on('error', (err) => {
assert.ok(err instanceof Error);
assert.strictEqual(
err.message,
'WebSocket was closed before the connection was established'
);
ws.on('close', () => {
server.close(done);
});
});

ws.on('unexpected-response', (req, res) => {
assert.strictEqual(res.statusCode, 502);

const chunks = [];

res.on('data', (chunk) => {
chunks.push(chunk);
});

res.on('end', () => {
assert.strictEqual(Buffer.concat(chunks).toString(), 'foo');
ws.close();
});
});
});

server.on('upgrade', (req, socket) => {
socket.on('end', socket.end);

socket.write(
`HTTP/1.1 502 ${http.STATUS_CODES[502]}\r\n` +
'Connection: keep-alive\r\n' +
'Content-type: text/html\r\n' +
'Content-Length: 3\r\n' +
'\r\n' +
'foo'
);
});
});

it('can be called from an error listener while connecting', (done) => {
const ws = new WebSocket('ws://localhost:1337');

Expand Down

0 comments on commit 67e25ff

Please # to comment.