Repository: websockets/ws
Pull request #1177 (2017-07-25 11:45:07)

allow client to specify handshake request timeout
Hi,
I have the need to control the connection timeout when initialising the connection,
I did my best to fall into your coding style and added the option in the documentation and added a unit test.
Hoping you will find this to your liking,

Let me know if there's anything I need to change, thanks! 

id0Sch: @lpinca thanks for the quick reply , I indeed suck at picking out message strings and such.

lpinca: @websockets/admin LGTY?

Patch:
diff --git a/doc/ws.md b/doc/ws.md
index 4da6e4ea..9e5e6f41 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -170,6 +170,7 @@ This class represents a WebSocket. It extends the `EventEmitter`.
 - `protocols` {String|Array} The list of subprotocols.
 - `options` {Object}
   - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header.
+  - `handshakeTimeout` {Number} Timeout in milliseconds for the handshake request.
   - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate.
   - `localAddress` {String} Local interface to bind for network connections.
   - `protocolVersion` {Number} Value of the `Sec-WebSocket-Version` header.
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 320c725e..760ae34f 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -471,6 +471,7 @@ function initAsServerClient (socket, head, options) {
  * @param {Object} options Connection options
  * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header
  * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
+ * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request
  * @param {String} options.localAddress Local interface to bind for network connections
  * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header
  * @param {Object} options.headers An object containing request headers
@@ -493,6 +494,7 @@ function initAsClient (address, protocols, options) {
     protocolVersion: protocolVersions[1],
     protocol: protocols.join(','),
     perMessageDeflate: true,
+    handshakeTimeout: null,
     localAddress: null,
     headers: null,
     family: null,
@@ -632,6 +634,14 @@ function initAsClient (address, protocols, options) {
 
   this._req = httpObj.get(requestOptions);
 
+  if (options.handshakeTimeout) {
+    this._req.setTimeout(options.handshakeTimeout, () => {
+      this._req.abort();
+      this.emit('error', new Error('opening handshake has timed out'));
+      this.finalize(true);
+    });
+  }
+
   this._req.on('error', (error) => {
     if (this._req.aborted) return;
 


Test patch:
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 0ebfbe52..1aa45d12 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -461,6 +461,21 @@ describe('WebSocket', function () {
         req.abort();
       });
     });
+
+    it('emits an error if the opening handshake timeout expires', function (done) {
+      server.once('upgrade', (req, socket) => socket.on('end', socket.end));
+
+      const ws = new WebSocket(`ws://localhost:${port}`, null, {
+        handshakeTimeout: 100
+      });
+
+      ws.on('open', () => assert.fail(null, null, 'connect shouldn\'t be raised here'));
+      ws.on('error', (err) => {
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.message, 'opening handshake has timed out');
+        done();
+      });
+    });
   });
 
   describe('connection with query string', function () {