Skip to content

Commit 2ee9ab3

Browse files
committed
[fix] Abort the request if close is called while connecting
Fixes #388
1 parent 8aa65e2 commit 2ee9ab3

3 files changed

Lines changed: 122 additions & 104 deletions

File tree

lib/WebSocket.js

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,10 @@ class WebSocket extends EventEmitter {
262262
*/
263263
close (code, data) {
264264
if (this.readyState === WebSocket.CLOSED) return;
265-
266265
if (this.readyState === WebSocket.CONNECTING) {
267-
this.readyState = WebSocket.CLOSED;
268-
return;
266+
this._req.abort();
267+
this.emit('error', new Error('closed while connecting'));
268+
return this.finalize(true);
269269
}
270270

271271
if (this.readyState === WebSocket.CLOSING) {
@@ -374,6 +374,11 @@ class WebSocket extends EventEmitter {
374374
*/
375375
terminate () {
376376
if (this.readyState === WebSocket.CLOSED) return;
377+
if (this.readyState === WebSocket.CONNECTING) {
378+
this._req.abort();
379+
this.emit('error', new Error('closed while connecting'));
380+
return this.finalize(true);
381+
}
377382

378383
if (this._socket) {
379384
this.readyState = WebSocket.CLOSING;
@@ -385,8 +390,6 @@ class WebSocket extends EventEmitter {
385390
//
386391
clearTimeout(this._closeTimer);
387392
this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
388-
} else if (this.readyState === WebSocket.CONNECTING) {
389-
this.finalize(true);
390393
}
391394
}
392395
}
@@ -610,43 +613,32 @@ function initAsClient (address, protocols, options) {
610613

611614
if (agent) requestOptions.agent = agent;
612615

613-
const req = httpObj.get(requestOptions);
616+
this._req = httpObj.get(requestOptions);
617+
618+
this._req.on('error', (error) => {
619+
if (this._req.aborted) return;
614620

615-
req.on('error', (error) => {
616621
this.emit('error', error);
617-
this.finalize(error);
622+
this.finalize(true);
618623
});
619624

620-
req.on('response', (res) => {
621-
var error;
622-
623-
if (!this.emit('unexpected-response', req, res)) {
624-
error = new Error(`unexpected server response (${res.statusCode})`);
625-
req.abort();
626-
this.emit('error', error);
625+
this._req.on('response', (res) => {
626+
if (!this.emit('unexpected-response', this._req, res)) {
627+
this._req.abort();
628+
this.emit('error', new Error(`unexpected server response (${res.statusCode})`));
629+
this.finalize(true);
627630
}
628-
629-
this.finalize(error);
630631
});
631632

632-
req.on('upgrade', (res, socket, head) => {
633-
if (this.readyState === WebSocket.CLOSED) {
634-
// client closed before server accepted connection
635-
this.emit('close');
636-
this.removeAllListeners();
637-
socket.end();
638-
return;
639-
}
640-
633+
this._req.on('upgrade', (res, socket, head) => {
641634
const digest = crypto.createHash('sha1')
642635
.update(key + GUID, 'binary')
643636
.digest('base64');
644637

645638
if (res.headers['sec-websocket-accept'] !== digest) {
639+
socket.destroy();
646640
this.emit('error', new Error('invalid server key'));
647-
this.removeAllListeners();
648-
socket.end();
649-
return;
641+
return this.finalize(true);
650642
}
651643

652644
const serverProt = res.headers['sec-websocket-protocol'];
@@ -662,30 +654,28 @@ function initAsClient (address, protocols, options) {
662654
}
663655

664656
if (protError) {
657+
socket.destroy();
665658
this.emit('error', new Error(protError));
666-
this.removeAllListeners();
667-
socket.end();
668-
return;
659+
return this.finalize(true);
669660
}
670661

671662
if (serverProt) this.protocol = serverProt;
672663

673664
const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']);
665+
674666
if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) {
675667
try {
676668
perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]);
677669
} catch (err) {
670+
socket.destroy();
678671
this.emit('error', new Error('invalid extension parameter'));
679-
this.removeAllListeners();
680-
socket.end();
681-
return;
672+
return this.finalize(true);
682673
}
674+
683675
this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
684676
}
685677

678+
this._req = null;
686679
this.setSocket(socket, head);
687-
688-
req.removeAllListeners();
689-
agent = null;
690680
});
691681
}

test/WebSocket.test.js

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -95,39 +95,28 @@ describe('WebSocket', function () {
9595
wss.on('connection', (ws) => ws.send('foobar'));
9696
});
9797

98-
it('#url exposes the server url', function (done) {
99-
server.createServer(++port, (srv) => {
100-
const url = `ws://localhost:${port}`;
101-
const ws = new WebSocket(url);
102-
103-
assert.strictEqual(ws.url, url);
98+
it('#url exposes the server url', function () {
99+
const url = `ws://localhost:${port}`;
100+
const ws = new WebSocket(url, { agent: new CustomAgent() });
104101

105-
ws.on('close', () => srv.close(done));
106-
ws.close();
107-
});
102+
assert.strictEqual(ws.url, url);
108103
});
109104

110-
it('#protocolVersion exposes the protocol version', function (done) {
111-
server.createServer(++port, (srv) => {
112-
const ws = new WebSocket(`ws://localhost:${port}`);
113-
114-
assert.strictEqual(ws.protocolVersion, 13);
115-
116-
ws.on('close', () => srv.close(done));
117-
ws.close();
105+
it('#protocolVersion exposes the protocol version', function () {
106+
const ws = new WebSocket(`ws://localhost:${port}`, {
107+
agent: new CustomAgent()
118108
});
109+
110+
assert.strictEqual(ws.protocolVersion, 13);
119111
});
120112

121113
describe('#bufferedAmount', function () {
122-
it('defaults to zero', function (done) {
123-
server.createServer(++port, (srv) => {
124-
const ws = new WebSocket(`ws://localhost:${port}`);
125-
126-
assert.strictEqual(ws.bufferedAmount, 0);
127-
128-
ws.on('close', () => srv.close(done));
129-
ws.close();
114+
it('defaults to zero', function () {
115+
const ws = new WebSocket(`ws://localhost:${port}`, {
116+
agent: new CustomAgent()
130117
});
118+
119+
assert.strictEqual(ws.bufferedAmount, 0);
131120
});
132121

133122
it('defaults to zero upon "open"', function (done) {
@@ -203,15 +192,12 @@ describe('WebSocket', function () {
203192
});
204193

205194
describe('#readyState', function () {
206-
it('defaults to connecting', function (done) {
207-
server.createServer(++port, (srv) => {
208-
const ws = new WebSocket(`ws://localhost:${port}`);
209-
210-
assert.strictEqual(ws.readyState, WebSocket.CONNECTING);
211-
212-
ws.on('close', () => srv.close(done));
213-
ws.close();
195+
it('defaults to connecting', function () {
196+
const ws = new WebSocket(`ws://localhost:${port}`, {
197+
agent: new CustomAgent()
214198
});
199+
200+
assert.strictEqual(ws.readyState, WebSocket.CONNECTING);
215201
});
216202

217203
it('set to open once connection is established', function (done) {
@@ -236,7 +222,7 @@ describe('WebSocket', function () {
236222
srv.close(done);
237223
});
238224

239-
ws.close(1001);
225+
ws.on('open', () => ws.close(1001));
240226
});
241227
});
242228

@@ -249,7 +235,7 @@ describe('WebSocket', function () {
249235
srv.close(done);
250236
});
251237

252-
ws.terminate();
238+
ws.on('open', () => ws.terminate());
253239
});
254240
});
255241
});
@@ -308,26 +294,68 @@ describe('WebSocket', function () {
308294
});
309295

310296
describe('connection establishing', function () {
311-
it('can disconnect before connection is established', function (done) {
312-
server.createServer(++port, (srv) => {
297+
it('can terminate before connection is established (1/2)', function (done) {
298+
const wss = new WebSocketServer({ port: ++port }, () => {
313299
const ws = new WebSocket(`ws://localhost:${port}`);
314300

315301
ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
316-
ws.on('close', () => srv.close(done));
302+
ws.on('error', (err) => {
303+
assert.ok(err instanceof Error);
304+
assert.strictEqual(err.message, 'closed while connecting');
305+
ws.on('close', () => wss.close(done));
306+
});
317307
ws.terminate();
318308
});
319309
});
320310

321-
it('can close before connection is established', function (done) {
322-
server.createServer(++port, (srv) => {
311+
it('can terminate before connection is established (2/2)', function (done) {
312+
const wss = new WebSocketServer({
313+
verifyClient: (info, cb) => setTimeout(cb, 300, true),
314+
port: ++port
315+
}, () => {
323316
const ws = new WebSocket(`ws://localhost:${port}`);
324317

325318
ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
326-
ws.on('close', () => srv.close(done));
319+
ws.on('error', (err) => {
320+
assert.ok(err instanceof Error);
321+
assert.strictEqual(err.message, 'closed while connecting');
322+
ws.on('close', () => wss.close(done));
323+
});
324+
setTimeout(() => ws.terminate(), 150);
325+
});
326+
});
327+
328+
it('can close before connection is established (1/2)', function (done) {
329+
const wss = new WebSocketServer({ port: ++port }, () => {
330+
const ws = new WebSocket(`ws://localhost:${port}`);
331+
332+
ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
333+
ws.on('error', (err) => {
334+
assert.ok(err instanceof Error);
335+
assert.strictEqual(err.message, 'closed while connecting');
336+
ws.on('close', () => wss.close(done));
337+
});
327338
ws.close(1001);
328339
});
329340
});
330341

342+
it('can close before connection is established (2/2)', function (done) {
343+
const wss = new WebSocketServer({
344+
verifyClient: (info, cb) => setTimeout(cb, 300, true),
345+
port: ++port
346+
}, () => {
347+
const ws = new WebSocket(`ws://localhost:${port}`);
348+
349+
ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
350+
ws.on('error', (err) => {
351+
assert.ok(err instanceof Error);
352+
assert.strictEqual(err.message, 'closed while connecting');
353+
ws.on('close', () => wss.close(done));
354+
});
355+
setTimeout(() => ws.close(1001), 150);
356+
});
357+
});
358+
331359
it('can handle error before request is upgraded', function (done) {
332360
// Here, we don't create a server, to guarantee that the connection will
333361
// fail before the request is upgraded
@@ -791,15 +819,17 @@ describe('WebSocket', function () {
791819
});
792820

793821
it('before connect should pass error through callback, if present', function (done) {
794-
server.createServer(++port, (srv) => {
822+
const wss = new WebSocketServer({ port: ++port }, () => {
795823
const ws = new WebSocket(`ws://localhost:${port}`);
796824

797-
ws.send('hi', (error) => {
798-
assert.ok(error instanceof Error);
799-
srv.close(done);
800-
ws.terminate();
825+
ws.send('hi', (err) => {
826+
assert.ok(err instanceof Error);
827+
assert.strictEqual(err.message, 'not opened');
828+
ws.on('close', () => wss.close(done));
801829
});
802830
});
831+
832+
wss.on('connection', (ws) => ws.close());
803833
});
804834

805835
it('without data should be successful', function (done) {

test/WebSocketServer.test.js

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -202,38 +202,37 @@ describe('WebSocketServer', function () {
202202
const ws = new WebSocket(`ws://localhost:${port}`);
203203
});
204204

205-
wss.on('connection', (client) => {
205+
wss.on('connection', (ws) => {
206206
assert.strictEqual(wss.clients.size, 1);
207-
wss.close();
208-
done();
207+
wss.close(done);
209208
});
210209
});
211210

212211
it('can be disabled', function (done) {
213212
const wss = new WebSocketServer({ port: ++port, clientTracking: false }, () => {
214213
assert.strictEqual(wss.clients, undefined);
215214
const ws = new WebSocket(`ws://localhost:${port}`);
215+
216+
ws.on('open', () => ws.close());
216217
});
217218

218-
wss.on('connection', (client) => {
219+
wss.on('connection', (ws) => {
219220
assert.strictEqual(wss.clients, undefined);
220-
wss.close();
221-
done();
221+
ws.on('close', () => wss.close(done));
222222
});
223223
});
224224

225225
it('is updated when client terminates the connection', function (done) {
226226
const wss = new WebSocketServer({ port: ++port }, () => {
227227
const ws = new WebSocket(`ws://localhost:${port}`);
228228

229-
wss.on('connection', (client) => {
230-
client.on('close', () => {
231-
assert.strictEqual(wss.clients.size, 0);
232-
wss.close();
233-
done();
234-
});
229+
ws.on('open', () => ws.terminate());
230+
});
235231

236-
ws.close();
232+
wss.on('connection', (ws) => {
233+
ws.on('close', () => {
234+
assert.strictEqual(wss.clients.size, 0);
235+
wss.close(done);
237236
});
238237
});
239238
});
@@ -242,14 +241,13 @@ describe('WebSocketServer', function () {
242241
const wss = new WebSocketServer({ port: ++port }, () => {
243242
const ws = new WebSocket(`ws://localhost:${port}`);
244243

245-
wss.on('connection', (client) => {
246-
client.on('close', () => {
247-
assert.strictEqual(wss.clients.size, 0);
248-
wss.close();
249-
done();
250-
});
244+
ws.on('open', () => ws.close());
245+
});
251246

252-
ws.close();
247+
wss.on('connection', (ws) => {
248+
ws.on('close', () => {
249+
assert.strictEqual(wss.clients.size, 0);
250+
wss.close(done);
253251
});
254252
});
255253
});

0 commit comments

Comments
 (0)