From 67e25ff50230d131d76b1061ca0be5c991df161f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 18 Apr 2021 09:34:27 +0200 Subject: [PATCH] [fix] Fix case where `abortHandshake()` does not close the connection On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if called after the request completed. Fixes #1869 --- lib/websocket.js | 10 ++++++++ test/websocket.test.js | 52 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 0e2a83d06..539238190 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -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 { diff --git a/test/websocket.test.js b/test/websocket.test.js index acc94c81f..8bfc9e151 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -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}`); @@ -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), @@ -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');