From 403a0ee7c32afeca844745bb2dae947a6c0ace2a Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 1 May 2016 20:17:36 -0400 Subject: [PATCH 001/489] test: update `should` usage These changes are in anticipation of upcoming changes in node v7.0.0. See: https://github.com/nodejs/node/pull/6102 --- test/WebSocket.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 9a696411f..6c6231732 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1990,7 +1990,7 @@ describe('WebSocket', function() { var srv = http.createServer(); srv.listen(++port, function() { srv.on('upgrade', function(req, socket, upgradeHeade) { - req.headers.should.not.have.property('origin'); + should(req.headers).not.have.property('origin'); srv.close(); done(); }); From a786c25e564b00b364f575d739757cc91bb4cabf Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Sun, 12 Jun 2016 21:12:36 +0800 Subject: [PATCH 002/489] Clean up code --- lib/BufferPool.js | 32 ++++++++++++++++---------------- lib/WebSocket.js | 9 +-------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 8ee599057..8e0fbe998 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -11,32 +11,32 @@ function BufferPool(initialSize, growStrategy, shrinkStrategy) { throw new TypeError("Classes can't be function-called"); } - if (typeof initialSize === 'function') { - shrinkStrategy = growStrategy; - growStrategy = initialSize; - initialSize = 0; - } - else if (typeof initialSize === 'undefined') { - initialSize = 0; - } this._growStrategy = (growStrategy || function(db, size) { return db.used + size; }).bind(null, this); + this._shrinkStrategy = (shrinkStrategy || function(db) { return initialSize; }).bind(null, this); - this._buffer = initialSize ? new Buffer(initialSize) : null; + + this._buffer = new Buffer(initialSize); this._offset = 0; this._used = 0; this._changeFactor = 0; - this.__defineGetter__('size', function(){ - return this._buffer == null ? 0 : this._buffer.length; - }); - this.__defineGetter__('used', function(){ - return this._used; - }); } +Object.defineProperty(BufferPool.prototype, 'size', { + get: function() { + return this._buffer.length; + } +}); + +Object.defineProperty(BufferPool.prototype, 'used', { + get: function() { + return this._used; + } +}); + BufferPool.prototype.get = function(length) { if (this._buffer == null || this._offset + length > this._buffer.length) { var newBuf = new Buffer(this._growStrategy(length)); @@ -54,7 +54,7 @@ BufferPool.prototype.reset = function(forceNewBuffer) { if (len < this.size) this._changeFactor -= 1; if (forceNewBuffer || this._changeFactor < -2) { this._changeFactor = 0; - this._buffer = len ? new Buffer(len) : null; + this._buffer = new Buffer(len); } this._offset = 0; this._used = 0; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..dc53c4eda 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -232,14 +232,7 @@ WebSocket.prototype.send = function send(data, options, cb) { if (typeof options.binary === 'undefined') { options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || - data instanceof Uint8Array || - data instanceof Uint16Array || - data instanceof Uint32Array || - data instanceof Int8Array || - data instanceof Int16Array || - data instanceof Int32Array || - data instanceof Float32Array || - data instanceof Float64Array); + ArrayBuffer.isView(data)); } if (typeof options.mask === 'undefined') options.mask = !this._isServer; From bb5e960eb4d1964093c1451bf58cd7093dee4e4a Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Sun, 12 Jun 2016 22:46:23 +0800 Subject: [PATCH 003/489] Add node 6 into travis list --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5002b4984..2d5608479 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js sudo: false node_js: + - "6" - "5" - "4" - "0.12" From 7ed327f6c80684b7f88731439c059405037eb47e Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Sun, 12 Jun 2016 22:57:45 +0800 Subject: [PATCH 004/489] Add eslint as lint tool --- .eslintignore | 1 + .eslintrc | 16 ++++++++++++++++ Makefile | 3 +++ package.json | 1 + 4 files changed, 21 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..8d87b1d26 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +node_modules/* diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..d29181a59 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,16 @@ +{ + "rules": { + + }, + "env": { + "es6": true, + "node": true + }, + "globals": { + "describe": true, + "it": true, + "before": true, + "after": true + }, + "extends": "eslint:recommended" +} diff --git a/Makefile b/Makefile index 94612c5ce..db9b984af 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ ALL_TESTS = $(shell find test/ -name '*.test.js') ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') +lint: + @./node_modules/.bin/eslint lib test bench + run-tests: @./node_modules/.bin/mocha \ -t 5000 \ diff --git a/package.json b/package.json index b4968c3f3..9a06b04c1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "ansi": "0.3.x", "benchmark": "0.3.x", "bufferutil": "1.2.x", + "eslint": "^2.12.0", "expect.js": "0.3.x", "istanbul": "^0.4.1", "mocha": "2.3.x", From cc5afb528cf537030fc8f228a0912409b2a177e7 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 11:05:36 +0800 Subject: [PATCH 005/489] fix PerMessageDeflate options pass issue --- lib/WebSocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..8e178455d 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -606,7 +606,7 @@ function initAsClient(address, protocols, options) { var extensionsOffer = {}; var perMessageDeflate; if (options.value.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate(typeof options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false); + perMessageDeflate = new PerMessageDeflate(options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } From 2a9e027b421332ac2d5ef8cb3391e8f544237a6c Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 11:42:31 +0800 Subject: [PATCH 006/489] refine code via eslint --- .eslintrc | 5 ++++- Makefile | 2 +- lib/BufferPool.js | 2 -- lib/BufferUtil.fallback.js | 6 ++++-- lib/ErrorCodes.js | 4 ++-- lib/Extensions.js | 6 ++---- lib/PerMessageDeflate.js | 2 +- lib/Receiver.hixie.js | 2 -- lib/Receiver.js | 15 +++++---------- lib/Sender.hixie.js | 3 +-- lib/Sender.js | 1 - lib/WebSocket.js | 5 +++-- lib/WebSocketServer.js | 15 +++++++-------- 13 files changed, 30 insertions(+), 38 deletions(-) diff --git a/.eslintrc b/.eslintrc index d29181a59..d0c121051 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,9 @@ { "rules": { - + "no-unused-vars": ["error", { "args": "none" }], + "no-console": 0, + "no-constant-condition": ["error", { "checkLoops": false }], + "no-empty": ["error", { "allowEmptyCatch": true }] }, "env": { "es6": true, diff --git a/Makefile b/Makefile index db9b984af..c57f69109 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ ALL_TESTS = $(shell find test/ -name '*.test.js') ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') lint: - @./node_modules/.bin/eslint lib test bench + @./node_modules/.bin/eslint lib run-tests: @./node_modules/.bin/mocha \ diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 8ee599057..b23c0ca26 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -4,8 +4,6 @@ * MIT Licensed */ -var util = require('util'); - function BufferPool(initialSize, growStrategy, shrinkStrategy) { if (this instanceof BufferPool === false) { throw new TypeError("Classes can't be function-called"); diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index 7abd0d8a6..c8c7e1fe5 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -21,12 +21,13 @@ exports.BufferUtil = { if (num < 0) num = 4294967296 + num; output.writeUInt32LE(num, offset + i, true); } + /* eslint-disable no-fallthrough */ switch (length % 4) { case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; case 1: output[offset + i] = source[i] ^ mask[0]; - case 0:; } + /* eslint-enable no-fallthrough */ }, unmask: function(data, mask) { var maskNum = mask.readUInt32LE(0, true); @@ -37,11 +38,12 @@ exports.BufferUtil = { if (num < 0) num = 4294967296 + num; data.writeUInt32LE(num, i, true); } + /* eslint-disable no-fallthrough */ switch (length % 4) { case 3: data[i + 2] = data[i + 2] ^ mask[2]; case 2: data[i + 1] = data[i + 1] ^ mask[1]; case 1: data[i] = data[i] ^ mask[0]; - case 0:; } + /* eslint-enable no-fallthrough */ } } diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 55ebd529b..1b4e8eda6 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -20,5 +20,5 @@ module.exports = { 1008: 'policy violation', 1009: 'message too big', 1010: 'extension handshake missing', - 1011: 'an unexpected condition prevented the request from being fulfilled', -}; \ No newline at end of file + 1011: 'an unexpected condition prevented the request from being fulfilled' +}; diff --git a/lib/Extensions.js b/lib/Extensions.js index a465ace2b..40f2e002c 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -1,6 +1,4 @@ -var util = require('util'); - /** * Module exports. */ @@ -54,13 +52,13 @@ function parse(value) { function format(value) { return Object.keys(value).map(function(token) { var paramsList = value[token]; - if (!util.isArray(paramsList)) { + if (!Array.isArray(paramsList)) { paramsList = [paramsList]; } return paramsList.map(function(params) { return [token].concat(Object.keys(params).map(function(k) { var p = params[k]; - if (!util.isArray(p)) p = [p]; + if (!Array.isArray(p)) p = [p]; return p.map(function(v) { return v === true ? k : k + '=' + v; }).join('; '); diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 00a6ea62a..a0b014a2f 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -11,7 +11,7 @@ PerMessageDeflate.extensionName = 'permessage-deflate'; * Per-message Compression Extensions implementation */ -function PerMessageDeflate(options, isServer,maxPayload) { +function PerMessageDeflate(options, isServer, maxPayload) { if (this instanceof PerMessageDeflate === false) { throw new TypeError("Classes can't be function-called"); } diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 598ccbdaf..a5c9c0fa3 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -4,8 +4,6 @@ * MIT Licensed */ -var util = require('util'); - /** * State constants */ diff --git a/lib/Receiver.js b/lib/Receiver.js index 0bf29d800..b80ae371c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -4,8 +4,7 @@ * MIT Licensed */ -var util = require('util') - , Validation = require('./Validation').Validation +var Validation = require('./Validation').Validation , ErrorCodes = require('./ErrorCodes') , BufferPool = require('./BufferPool') , bufferUtil = require('./BufferUtil').BufferUtil @@ -410,6 +409,7 @@ function readUInt32BE(start) { } function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { + /* eslint-disable no-fallthrough */ switch (length) { default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; @@ -429,16 +429,11 @@ function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; case 1: dstBuffer[dstOffset] = srcBuffer[0]; } + /* eslint-enable no-fallthrough */ } function clone(obj) { - var cloned = {}; - for (var k in obj) { - if (obj.hasOwnProperty(k)) { - cloned[k] = obj[k]; - } - } - return cloned; + return Object.assign({}, obj); } /** @@ -696,7 +691,7 @@ var opcodes = { self.reset(); }); this.flush(); - }, + } }, // ping '9': { diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index b87d9dd93..e7567f073 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -5,8 +5,7 @@ */ var events = require('events') - , util = require('util') - , EventEmitter = events.EventEmitter; + , util = require('util'); /** * Hixie Sender implementation diff --git a/lib/Sender.js b/lib/Sender.js index 6ef2ea271..8c2210b3e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,7 +6,6 @@ var events = require('events') , util = require('util') - , EventEmitter = events.EventEmitter , ErrorCodes = require('./ErrorCodes') , bufferUtil = require('./BufferUtil').BufferUtil , PerMessageDeflate = require('./PerMessageDeflate'); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..fc7f892a0 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -221,8 +221,10 @@ WebSocket.prototype.send = function send(data, options, cb) { } if (!data) data = ''; + + var self = this; + if (this._queue) { - var self = this; this._queue.push(function() { self.send(data, options, cb); }); return; } @@ -254,7 +256,6 @@ WebSocket.prototype.send = function send(data, options, cb) { if (data instanceof readable) { startQueue(this); - var self = this; sendStream(this, data, options, function send(error) { process.nextTick(function tock() { diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 476cf7100..4a6cf9b3d 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -12,7 +12,6 @@ var util = require('util') , WebSocket = require('./WebSocket') , Extensions = require('./Extensions') , PerMessageDeflate = require('./PerMessageDeflate') - , tls = require('tls') , url = require('url'); /** @@ -291,7 +290,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { if (typeof self.options.handleProtocols == 'function') { var protList = (protocols || "").split(/, */); var callbackCalled = false; - var res = self.options.handleProtocols(protList, function(result, protocol) { + self.options.handleProtocols(protList, function(result, protocol) { callbackCalled = true; if (!result) abortConnection(socket, 401, 'Unauthorized'); else completeHybiUpgrade2(protocol); @@ -400,7 +399,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { } catch (e) { try { socket.destroy(); } catch (e) {} return; - }; + } }; // handshake completion code to run once nonce has been successfully retrieved @@ -465,17 +464,18 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // retrieve nonce var nonceLength = 8; + var nonce, rest; if (upgradeHead && upgradeHead.length >= nonceLength) { - var nonce = upgradeHead.slice(0, nonceLength); - var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; + nonce = upgradeHead.slice(0, nonceLength); + rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; completeHandshake.call(self, nonce, rest, buildResponseHeader()); } else { // nonce not present in upgradeHead - var nonce = new Buffer(nonceLength); + nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); var received = upgradeHead.length; - var rest = null; + rest = null; var handler = function (data) { var toRead = Math.min(data.length, nonceLength - received); if (toRead === 0) return; @@ -506,7 +506,6 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length == 2) { - var self = this; this.options.verifyClient(info, function(result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; From 04ccdf79e210663db6977a92dcdf9fc4425ef6a4 Mon Sep 17 00:00:00 2001 From: Tejas Manohar Date: Mon, 13 Jun 2016 00:55:12 -0500 Subject: [PATCH 007/489] specify data encoding type 'binary' at hash#update --- lib/WebSocket.js | 2 +- lib/WebSocketServer.js | 6 +++--- test/testserver.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..103fd8f28 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -619,7 +619,7 @@ function initAsClient(address, protocols, options) { // begin handshake var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64'); var shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); var expectedServerKey = shasum.digest('base64'); var agent = options.value.agent; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 476cf7100..789c8813c 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -218,7 +218,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); key = shasum.digest('base64'); var headers = [ @@ -422,9 +422,9 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, - n & 0xFF)); + n & 0xFF), 'binary'); }); - md5.update(nonce.toString('binary')); + md5.update(nonce.toString('binary'), 'binary'); socket.setTimeout(0); socket.setNoDelay(true); diff --git a/test/testserver.js b/test/testserver.js index e17cbb8ea..be158b5c4 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -49,7 +49,7 @@ function validServer(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); key = shasum.digest('base64'); var headers = [ @@ -113,7 +113,7 @@ function invalidRequestHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "bogus"); + shasum.update(key + "bogus", 'binary'); key = shasum.digest('base64'); var headers = [ @@ -142,7 +142,7 @@ function closeAfterConnectHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); key = shasum.digest('base64'); var headers = [ From dc936946463c2bd696339f8aedaaafe250bf0da0 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 13 Jun 2016 09:25:55 +0200 Subject: [PATCH 008/489] [ci] Nuke 0.12 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2d5608479..451ba0135 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ node_js: - "6" - "5" - "4" - - "0.12" addons: apt: sources: From d7db84cd296f15c71d4cad88ac8b7db64bb393ba Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 16:41:54 +0800 Subject: [PATCH 009/489] add indent rule and update code --- .eslintrc | 3 +- lib/BufferUtil.fallback.js | 12 +++---- lib/PerMessageDeflate.js | 20 ++++++------ lib/Receiver.hixie.js | 12 +++---- lib/Receiver.js | 66 +++++++++++++++++++------------------- lib/Sender.js | 12 +++---- lib/WebSocket.js | 10 +++--- lib/WebSocketServer.js | 40 +++++++++++------------ 8 files changed, 88 insertions(+), 87 deletions(-) diff --git a/.eslintrc b/.eslintrc index d0c121051..91cc68d28 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,7 +3,8 @@ "no-unused-vars": ["error", { "args": "none" }], "no-console": 0, "no-constant-condition": ["error", { "checkLoops": false }], - "no-empty": ["error", { "allowEmptyCatch": true }] + "no-empty": ["error", { "allowEmptyCatch": true }], + "indent": ["error", 2] }, "env": { "es6": true, diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index c8c7e1fe5..265287619 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -23,9 +23,9 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; - case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; - case 1: output[offset + i] = source[i] ^ mask[0]; + case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; + case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; + case 1: output[offset + i] = source[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ }, @@ -40,9 +40,9 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: data[i + 2] = data[i + 2] ^ mask[2]; - case 2: data[i + 1] = data[i + 1] ^ mask[1]; - case 1: data[i] = data[i] ^ mask[0]; + case 3: data[i + 2] = data[i + 2] ^ mask[2]; + case 2: data[i + 1] = data[i + 1] ^ mask[1]; + case 1: data[i] = data[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ } diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index a0b014a2f..fefec6c5b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -255,17 +255,17 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ - cumulativeBufferLength+=data.length; - if(cumulativeBufferLength>self._maxPayload){ - buffers=[]; - cleanup(); - var err={type:1009}; - callback(err); - return; - } + if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ + cumulativeBufferLength+=data.length; + if(cumulativeBufferLength>self._maxPayload){ + buffers=[]; + cleanup(); + var err={type:1009}; + callback(err); + return; } - buffers.push(data); + } + buffers.push(data); } function cleanup() { diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index a5c9c0fa3..218fe1386 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -60,12 +60,12 @@ Receiver.prototype.add = function(data) { data = data.slice(1); } else { - if (data[0] !== 0x00) { - self.error('payload must start with 0x00 byte', true); - return; - } - data = data.slice(1); - self.state = BODY; + if (data[0] !== 0x00) { + self.error('payload must start with 0x00 byte', true); + return; + } + data = data.slice(1); + self.state = BODY; } } diff --git a/lib/Receiver.js b/lib/Receiver.js index b80ae371c..39701c136 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -411,23 +411,23 @@ function readUInt32BE(start) { function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { - default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; - case 1: dstBuffer[dstOffset] = srcBuffer[0]; + default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; + case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; + case 1: dstBuffer[dstOffset] = srcBuffer[0]; } /* eslint-enable no-fallthrough */ } @@ -503,7 +503,7 @@ var opcodes = { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { if(err.type===1009){ - return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } return self.error(err.message, 1007); } @@ -512,11 +512,11 @@ var opcodes = { self.currentMessage.push(buffer); } else{ - self.currentMessage=null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); - return; + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); + return; } self.currentMessageLength += buffer.length; } @@ -544,10 +544,10 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (self.maxPayloadExceeded(firstLength)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (self.maxPayloadExceeded(firstLength)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['2'].getData.call(self, firstLength); } else if (firstLength == 126) { @@ -599,7 +599,7 @@ var opcodes = { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { if(err.type===1009){ - return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); + return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); } return self.error(err.message, 1007); } @@ -608,11 +608,11 @@ var opcodes = { self.currentMessage.push(buffer); } else{ - self.currentMessage=null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded'), 1009); - return; + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded'), 1009); + return; } self.currentMessageLength += buffer.length; } diff --git a/lib/Sender.js b/lib/Sender.js index 8c2210b3e..7d49cbf15 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -186,12 +186,12 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, if (compressed) outputBuffer[0] |= 0x40; switch (secondByte) { - case 126: - writeUInt16BE.call(outputBuffer, dataLength, 2); - break; - case 127: - writeUInt32BE.call(outputBuffer, 0, 2); - writeUInt32BE.call(outputBuffer, dataLength, 6); + case 126: + writeUInt16BE.call(outputBuffer, dataLength, 2); + break; + case 127: + writeUInt32BE.call(outputBuffer, 0, 2); + writeUInt32BE.call(outputBuffer, dataLength, 6); } if (maskData) { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index e868d2d95..881d23be7 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -89,7 +89,7 @@ util.inherits(WebSocket, EventEmitter); * Ready States */ ["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) { - WebSocket.prototype[state] = WebSocket[state] = index; + WebSocket.prototype[state] = WebSocket[state] = index; }); /** @@ -432,7 +432,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') - data = new Uint8Array(data).buffer; + data = new Uint8Array(data).buffer; listener.call(target, new MessageEvent(data, !!flags.binary, target)); } @@ -647,9 +647,9 @@ function initAsClient(address, protocols, options) { if (options.value.headers) { for (var header in options.value.headers) { - if (options.value.headers.hasOwnProperty(header)) { + if (options.value.headers.hasOwnProperty(header)) { requestOptions.headers[header] = options.value.headers[header]; - } + } } } @@ -675,7 +675,7 @@ function initAsClient(address, protocols, options) { if (!agent) { // global agent ignores client side certificates - agent = new httpObj.Agent(requestOptions); + agent = new httpObj.Agent(requestOptions); } } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 52f3bc0fd..d56350e64 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -221,7 +221,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' + 'HTTP/1.1 101 Switching Protocols' , 'Upgrade: websocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Accept: ' + key @@ -288,25 +288,25 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { var completeHybiUpgrade1 = function() { // choose from the sub-protocols if (typeof self.options.handleProtocols == 'function') { - var protList = (protocols || "").split(/, */); - var callbackCalled = false; - self.options.handleProtocols(protList, function(result, protocol) { - callbackCalled = true; - if (!result) abortConnection(socket, 401, 'Unauthorized'); - else completeHybiUpgrade2(protocol); - }); - if (!callbackCalled) { + var protList = (protocols || "").split(/, */); + var callbackCalled = false; + self.options.handleProtocols(protList, function(result, protocol) { + callbackCalled = true; + if (!result) abortConnection(socket, 401, 'Unauthorized'); + else completeHybiUpgrade2(protocol); + }); + if (!callbackCalled) { // the handleProtocols handler never called our callback - abortConnection(socket, 501, 'Could not process protocols'); - } - return; + abortConnection(socket, 501, 'Could not process protocols'); + } + return; } else { - if (typeof protocols !== 'undefined') { - completeHybiUpgrade2(protocols.split(/, */)[0]); - } - else { - completeHybiUpgrade2(); - } + if (typeof protocols !== 'undefined') { + completeHybiUpgrade2(protocols.split(/, */)[0]); + } + else { + completeHybiUpgrade2(); + } } } @@ -362,7 +362,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { var onClientVerified = function() { var wshost; if (!req.headers['x-forwarded-host']) - wshost = req.headers.host; + wshost = req.headers.host; else wshost = req.headers['x-forwarded-host']; var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url @@ -371,7 +371,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // build the response header and return a Buffer var buildResponseHeader = function() { var headers = [ - 'HTTP/1.1 101 Switching Protocols' + 'HTTP/1.1 101 Switching Protocols' , 'Upgrade: WebSocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Location: ' + location From 51e7af23a02089c5d027c5693f054c0d75b57543 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 17:08:30 +0800 Subject: [PATCH 010/489] Add space-infix-ops rule and refine code (#747) --- .eslintrc | 3 +- lib/PerMessageDeflate.js | 12 ++++---- lib/Receiver.js | 66 ++++++++++++++++++++-------------------- lib/Sender.hixie.js | 4 +-- lib/Sender.js | 14 ++++----- lib/WebSocketServer.js | 2 +- 6 files changed, 51 insertions(+), 50 deletions(-) diff --git a/.eslintrc b/.eslintrc index 91cc68d28..6d6b366e8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,8 @@ "no-console": 0, "no-constant-condition": ["error", { "checkLoops": false }], "no-empty": ["error", { "allowEmptyCatch": true }], - "indent": ["error", 2] + "indent": ["error", 2], + "space-infix-ops": 2 }, "env": { "es6": true, diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index fefec6c5b..7576d939b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -237,7 +237,7 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { var self = this; var buffers = []; - var cumulativeBufferLength=0; + var cumulativeBufferLength = 0; this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); @@ -255,12 +255,12 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ - cumulativeBufferLength+=data.length; - if(cumulativeBufferLength>self._maxPayload){ - buffers=[]; + if(self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + cumulativeBufferLength += data.length; + if(cumulativeBufferLength > self._maxPayload){ + buffers = []; cleanup(); - var err={type:1009}; + var err = {type:1009}; callback(err); return; } diff --git a/lib/Receiver.js b/lib/Receiver.js index 39701c136..3db66af79 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -18,9 +18,9 @@ function Receiver (extensions,maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } - if(typeof extensions==='number'){ - maxPayload=extensions; - extensions={}; + if(typeof extensions === 'number'){ + maxPayload = extensions; + extensions = {}; } @@ -377,7 +377,7 @@ Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, ca * @api private */ Receiver.prototype.maxPayloadExceeded = function(length) { - if (this.maxPayload=== undefined || this.maxPayload === null || this.maxPayload < 1) { + if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; } var fullLength = this.currentPayloadLength + length; @@ -386,7 +386,7 @@ Receiver.prototype.maxPayloadExceeded = function(length) { return false; } this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); - this.messageBuffer=[]; + this.messageBuffer = []; this.cleanup(); return true; @@ -397,36 +397,36 @@ Receiver.prototype.maxPayloadExceeded = function(length) { */ function readUInt16BE(start) { - return (this[start]<<8) + - this[start+1]; + return (this[start] << 8) + + this[start + 1]; } function readUInt32BE(start) { - return (this[start]<<24) + - (this[start+1]<<16) + - (this[start+2]<<8) + - this[start+3]; + return (this[start] << 24) + + (this[start + 1] << 16) + + (this[start + 2] << 8) + + this[start + 3]; } function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; + case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; case 1: dstBuffer[dstOffset] = srcBuffer[0]; } /* eslint-enable no-fallthrough */ @@ -502,20 +502,20 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type===1009){ + if(err.type === 1009){ return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } else{ - self.currentMessage=null; + self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); + self.error(new Error('Maximum payload exceeded. maxPayload: ' + self.maxPayload), 1009); return; } self.currentMessageLength += buffer.length; @@ -598,17 +598,17 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type===1009){ + if(err.type === 1009){ return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } else{ - self.currentMessage=null; + self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; self.error(new Error('Maximum payload exceeded'), 1009); diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index e7567f073..ac1ecda92 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -53,8 +53,8 @@ Sender.prototype.send = function(data, options, cb) { buffer.write('\x80', 'binary'); // assume length less than 2**14 bytes if (lengthbytes > 1) - buffer.write(String.fromCharCode(128+length/128), offset++, 'binary'); - buffer.write(String.fromCharCode(length&0x7f), offset++, 'binary'); + buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); + buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); } else buffer.write('\x00', 'binary'); } diff --git a/lib/Sender.js b/lib/Sender.js index 7d49cbf15..dd348c48c 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -290,15 +290,15 @@ Sender.prototype.applyExtensions = function(data, fin, compress, callback) { module.exports = Sender; function writeUInt16BE(value, offset) { - this[offset] = (value & 0xff00)>>8; - this[offset+1] = value & 0xff; + this[offset] = (value & 0xff00) >> 8; + this[offset + 1] = value & 0xff; } function writeUInt32BE(value, offset) { - this[offset] = (value & 0xff000000)>>24; - this[offset+1] = (value & 0xff0000)>>16; - this[offset+2] = (value & 0xff00)>>8; - this[offset+3] = value & 0xff; + this[offset] = (value & 0xff000000) >> 24; + this[offset + 1] = (value & 0xff0000) >> 16; + this[offset + 2] = (value & 0xff00) >> 8; + this[offset + 3] = value & 0xff; } function getArrayBuffer(data) { @@ -308,7 +308,7 @@ function getArrayBuffer(data) { , o = data.byteOffset || 0 , buffer = new Buffer(l); for (var i = 0; i < l; ++i) { - buffer[i] = array[o+i]; + buffer[i] = array[o + i]; } return buffer; } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index d56350e64..412d89006 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -86,7 +86,7 @@ function WebSocketServer(options, callback) { upgradeHead.copy(head); self.handleUpgrade(req, socket, head, function(client) { - self.emit('connection'+req.url, client); + self.emit('connection' + req.url, client); self.emit('connection', client); }); }; From da8215263298e49d35f0b4281e7d10deffd587b6 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Tue, 5 Jan 2016 15:02:59 +0800 Subject: [PATCH 011/489] Add strict rule and update code --- .eslintrc | 3 ++- bench/parser.benchmark.js | 12 +++++++----- bench/sender.benchmark.js | 6 ++++-- bench/speed.js | 4 +++- bench/util.js | 10 ++++++---- lib/BufferPool.js | 2 ++ lib/BufferUtil.fallback.js | 2 ++ lib/ErrorCodes.js | 2 ++ lib/Extensions.js | 1 + lib/PerMessageDeflate.js | 1 + lib/Receiver.hixie.js | 2 ++ lib/Receiver.js | 2 ++ lib/Sender.hixie.js | 2 ++ lib/Sender.js | 2 ++ lib/Validation.fallback.js | 2 ++ lib/Validation.js | 4 ++-- lib/WebSocket.js | 4 ++-- lib/WebSocketServer.js | 2 ++ 18 files changed, 46 insertions(+), 17 deletions(-) diff --git a/.eslintrc b/.eslintrc index 6d6b366e8..0a1bb12e0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,8 @@ "no-constant-condition": ["error", { "checkLoops": false }], "no-empty": ["error", { "allowEmptyCatch": true }], "indent": ["error", 2], - "space-infix-ops": 2 + "space-infix-ops": 2, + "strict": 2 }, "env": { "es6": true, diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index ff5f737c0..b912b1953 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * Benchmark dependencies. */ @@ -17,7 +19,7 @@ require('./util'); /** * Setup receiver. */ - + suite.on('start', function () { receiver = new Receiver(); }); @@ -31,10 +33,10 @@ suite.on('cycle', function () { */ var pingMessage = 'Hello' - , pingPacket1 = getBufferFromHexString('89 ' + (pack(2, 0x80 | pingMessage.length)) + + , pingPacket1 = getBufferFromHexString('89 ' + (pack(2, 0x80 | pingMessage.length)) + ' 34 83 a8 68 '+ getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'))); suite.add('ping message', function () { - receiver.add(pingPacket1); + receiver.add(pingPacket1); }); var pingPacket2 = getBufferFromHexString('89 00') @@ -63,7 +65,7 @@ binaryDataPacket = (function() { suite.add('binary data (125 bytes)', function () { try { receiver.add(binaryDataPacket); - + } catch(e) {console.log(e)} }); @@ -100,7 +102,7 @@ suite.on('cycle', function (bench, details) { details.hz.toFixed(2).cyan + ' ops/sec'.grey , details.count.toString().white + ' times executed'.grey , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , + , ].join(', '.grey)); }); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 20c171a50..7c059bfec 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * Benchmark dependencies. */ @@ -17,7 +19,7 @@ require('./util'); /** * Setup sender. */ - + suite.on('start', function () { sender = new Sender(); sender._socket = { write: function() {} }; @@ -51,7 +53,7 @@ suite.on('cycle', function (bench, details) { details.hz.toFixed(2).cyan + ' ops/sec'.grey , details.count.toString().white + ' times executed'.grey , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , + , ].join(', '.grey)); }); diff --git a/bench/speed.js b/bench/speed.js index 3ce641461..f8ae9a49f 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -1,3 +1,5 @@ +'use strict'; + var cluster = require('cluster') , WebSocket = require('../') , WebSocketServer = WebSocket.Server @@ -102,4 +104,4 @@ else { config.push(run); roundtrip.apply(null, config); })(); -} \ No newline at end of file +} diff --git a/bench/util.js b/bench/util.js index 5f0128190..38896d934 100644 --- a/bench/util.js +++ b/bench/util.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * Returns a Buffer from a "ff 00 ff"-type hex string. */ @@ -49,7 +51,7 @@ mask = function(buf, maskString) { if (typeof buf == 'string') buf = new Buffer(buf); var mask = getBufferFromHexString(maskString || '34 83 a8 68'); for (var i = 0; i < buf.length; ++i) { - buf[i] ^= mask[i % 4]; + buf[i] ^= mask[i % 4]; } return buf; } @@ -57,8 +59,8 @@ mask = function(buf, maskString) { /** * Returns a hex string representing the length of a message */ - -getHybiLengthAsHexString = function(len, masked) { + +getHybiLengthAsHexString = function(len, masked) { if (len < 126) { var buf = new Buffer(1); buf[0] = (masked ? 0x80 : 0) | len; @@ -100,6 +102,6 @@ pack = function(length, number) { * Left pads the string 's' to a total length of 'n' with char 'c'. */ -padl = function(s, n, c) { +padl = function(s, n, c) { return new Array(1 + n - s.length).join(c) + s; } diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 990b8f12c..510d4e956 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + function BufferPool(initialSize, growStrategy, shrinkStrategy) { if (this instanceof BufferPool === false) { throw new TypeError("Classes can't be function-called"); diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index 265287619..d81b5f7ab 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + exports.BufferUtil = { merge: function(mergedBuffer, buffers) { var offset = 0; diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 1b4e8eda6..335efb4e8 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + module.exports = { isValidErrorCode: function(code) { return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || diff --git a/lib/Extensions.js b/lib/Extensions.js index 40f2e002c..2b7f41593 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -1,3 +1,4 @@ +'use strict'; /** * Module exports. diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 7576d939b..75ba86bba 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -1,3 +1,4 @@ +'use strict'; var zlib = require('zlib'); diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 218fe1386..686b2c3ca 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * State constants */ diff --git a/lib/Receiver.js b/lib/Receiver.js index 3db66af79..7351069df 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var Validation = require('./Validation').Validation , ErrorCodes = require('./ErrorCodes') , BufferPool = require('./BufferPool') diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index ac1ecda92..0b6271e3e 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var events = require('events') , util = require('util'); diff --git a/lib/Sender.js b/lib/Sender.js index dd348c48c..b9a355be9 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var events = require('events') , util = require('util') , ErrorCodes = require('./ErrorCodes') diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js index 639b0d316..b160d07a3 100644 --- a/lib/Validation.fallback.js +++ b/lib/Validation.fallback.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + exports.Validation = { isValidUTF8: function(buffer) { return true; diff --git a/lib/Validation.js b/lib/Validation.js index 0795fb7f0..284ab2fb1 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -1,11 +1,11 @@ -'use strict'; - /*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ +'use strict'; + try { module.exports = require('utf-8-validate'); } catch (e) { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 881d23be7..a7ad0550d 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -1,11 +1,11 @@ -'use strict'; - /*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ +'use strict'; + var url = require('url') , util = require('util') , http = require('http') diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 412d89006..43fefbbac 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var util = require('util') , events = require('events') , http = require('http') From aa36969301f27b330df1036be6934297eb754fd1 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 17:18:44 +0800 Subject: [PATCH 012/489] lint the project before run test --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c57f69109..3e8a47c5f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ run-coverage: $(TESTFLAGS) \ $(TESTS) -test: +test: lint @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests integrationtest: From 47933b97b5c3b180976d469545258dd7df07689e Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 16:27:11 +0800 Subject: [PATCH 013/489] Add quotes rule and fix coding style --- .eslintrc | 3 ++- lib/Receiver.hixie.js | 2 +- lib/Receiver.js | 2 +- lib/WebSocket.js | 4 ++-- lib/WebSocketServer.js | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0a1bb12e0..3127ebb6f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,7 +6,8 @@ "no-empty": ["error", { "allowEmptyCatch": true }], "indent": ["error", 2], "space-infix-ops": 2, - "strict": 2 + "strict": 2, + "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}] }, "env": { "es6": true, diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 686b2c3ca..96ca6a58c 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -163,7 +163,7 @@ Receiver.prototype.error = function (reason, terminate) { this.onerror(reason, terminate); } else{ - this.onerror(new Error("An error occured"),terminate); + this.onerror(new Error('An error occured'),terminate); } return this; }; diff --git a/lib/Receiver.js b/lib/Receiver.js index 7351069df..cf1f31ee3 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -325,7 +325,7 @@ Receiver.prototype.error = function (reason, protocolErrorCode) { this.onerror(reason, protocolErrorCode); } else{ - this.onerror(new Error("An error occured"),protocolErrorCode); + this.onerror(new Error('An error occured'),protocolErrorCode); } return this; }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a7ad0550d..6a5225aeb 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -88,7 +88,7 @@ util.inherits(WebSocket, EventEmitter); /** * Ready States */ -["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) { +['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each(state, index) { WebSocket.prototype[state] = WebSocket[state] = index; }); @@ -736,7 +736,7 @@ function initAsClient(address, protocols, options) { } var serverProt = res.headers['sec-websocket-protocol']; - var protList = (options.value.protocol || "").split(/, */); + var protList = (options.value.protocol || '').split(/, */); var protError = null; if (!options.value.protocol && serverProt) { diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 43fefbbac..dd5f59bab 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -219,7 +219,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); var headers = [ @@ -290,7 +290,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { var completeHybiUpgrade1 = function() { // choose from the sub-protocols if (typeof self.options.handleProtocols == 'function') { - var protList = (protocols || "").split(/, */); + var protList = (protocols || '').split(/, */); var callbackCalled = false; self.options.handleProtocols(protList, function(result, protocol) { callbackCalled = true; From b1de21be112e06cfbb04f09792d1fa7ba09d7c54 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 10:57:43 +0800 Subject: [PATCH 014/489] Add keyword-spacing rule (#752) --- .eslintrc | 3 ++- lib/PerMessageDeflate.js | 4 ++-- lib/Receiver.hixie.js | 8 ++++---- lib/Receiver.js | 21 +++++++++++---------- lib/WebSocketServer.js | 4 ++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.eslintrc b/.eslintrc index 3127ebb6f..2bbf987e5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,8 @@ "indent": ["error", 2], "space-infix-ops": 2, "strict": 2, - "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}] + "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], + "keyword-spacing": 2 }, "env": { "es6": true, diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 75ba86bba..eb7866b88 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -256,9 +256,9 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - if(self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ cumulativeBufferLength += data.length; - if(cumulativeBufferLength > self._maxPayload){ + if (cumulativeBufferLength > self._maxPayload){ buffers = []; cleanup(); var err = {type:1009}; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 96ca6a58c..b6442933b 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -106,7 +106,7 @@ Receiver.prototype.add = function(data) { } else self.spanLength += data.length; } - while(data) data = doAdd(); + while (data) data = doAdd(); }; /** @@ -156,13 +156,13 @@ Receiver.prototype.parse = function() { Receiver.prototype.error = function (reason, terminate) { if (this.dead) return; this.reset(); - if(typeof reason == 'string'){ + if (typeof reason == 'string'){ this.onerror(new Error(reason), terminate); } - else if(reason.constructor == Error){ + else if (reason.constructor == Error){ this.onerror(reason, terminate); } - else{ + else { this.onerror(new Error('An error occured'),terminate); } return this; diff --git a/lib/Receiver.js b/lib/Receiver.js index cf1f31ee3..15f24cefd 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -20,7 +20,8 @@ function Receiver (extensions,maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } - if(typeof extensions === 'number'){ + + if (typeof extensions === 'number'){ maxPayload = extensions; extensions = {}; } @@ -318,13 +319,13 @@ Receiver.prototype.unmask = function (mask, buf, binary) { Receiver.prototype.error = function (reason, protocolErrorCode) { if (this.dead) return; this.reset(); - if(typeof reason == 'string'){ + if (typeof reason == 'string'){ this.onerror(new Error(reason), protocolErrorCode); } - else if(reason.constructor == Error){ + else if (reason.constructor == Error){ this.onerror(reason, protocolErrorCode); } - else{ + else { this.onerror(new Error('An error occured'),protocolErrorCode); } return this; @@ -504,16 +505,16 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type === 1009){ + if (err.type === 1009){ return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } - else{ + else { self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; @@ -600,16 +601,16 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type === 1009){ + if (err.type === 1009){ return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } - else{ + else { self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index dd5f59bab..c5c3850aa 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -146,9 +146,9 @@ WebSocketServer.prototype.close = function(callback) { } delete this._server; } - if(callback) + if (callback) callback(error); - else if(error) + else if (error) throw error; } From cbc7548a4c5b89c761bf9daadc969d487a95a784 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 12:37:05 +0800 Subject: [PATCH 015/489] refactor code (#754) --- .eslintrc | 6 +++++- lib/PerMessageDeflate.js | 10 +++++----- lib/Receiver.hixie.js | 17 +++-------------- lib/Receiver.js | 4 ++-- lib/WebSocket.js | 2 +- 5 files changed, 16 insertions(+), 23 deletions(-) diff --git a/.eslintrc b/.eslintrc index 2bbf987e5..1abddeefd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,11 @@ "space-infix-ops": 2, "strict": 2, "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], - "keyword-spacing": 2 + "keyword-spacing": 2, + "linebreak-style": [2, "unix"], + "comma-dangle": ["error", "never"], + "no-unreachable": [2], + "comma-spacing": 2 }, "env": { "es6": true, diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index eb7866b88..a55b8cc1c 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -143,7 +143,7 @@ PerMessageDeflate.prototype.acceptAsServer = function(paramsList) { }, this); if (!result) { - throw new Error('Doesn\'t support the offered configuration'); + throw new Error(`Doesn't support the offered configuration`); } return accepted; @@ -194,7 +194,7 @@ PerMessageDeflate.prototype.normalizeParams = function(paramsList) { case 'server_no_context_takeover': case 'client_no_context_takeover': if (value !== true) { - throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); + throw new Error(`invalid extension parameter value for ${key} (${value})`); } params[key] = true; break; @@ -203,16 +203,16 @@ PerMessageDeflate.prototype.normalizeParams = function(paramsList) { if (typeof value === 'string') { value = parseInt(value, 10); if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { - throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); + throw new Error(`invalid extension parameter value for ${key} (${value})`); } } if (!this._isServer && value === true) { - throw new Error('Missing extension parameter value for ' + key); + throw new Error(`Missing extension parameter value for ${key}`); } params[key] = value; break; default: - throw new Error('Not defined extension parameter (' + key + ')'); + throw new Error(`Not defined extension parameter (${key})`); } }, this); return params; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index b6442933b..88f803c9e 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -21,7 +21,7 @@ var BINARYLENGTH = 2 function Receiver () { if (this instanceof Receiver === false) { - throw new TypeError("Classes can't be function-called"); + throw new TypeError(`Classes can't be function-called`); } this.state = EMPTY; @@ -100,7 +100,7 @@ Receiver.prototype.add = function(data) { return; } self.buffers.push(data); - if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) { + if ((self.messageEnd = data.indexOf(0xFF)) != -1) { self.spanLength += self.messageEnd; return self.parse(); } @@ -163,7 +163,7 @@ Receiver.prototype.error = function (reason, terminate) { this.onerror(reason, terminate); } else { - this.onerror(new Error('An error occured'),terminate); + this.onerror(new Error('An error occured'), terminate); } return this; }; @@ -181,14 +181,3 @@ Receiver.prototype.reset = function (reason) { this.messageEnd = -1; this.spanLength = 0; }; - -/** - * Internal api - */ - -function bufferIndex(buffer, byte) { - for (var i = 0, l = buffer.length; i < l; ++i) { - if (buffer[i] === byte) return i; - } - return -1; -} diff --git a/lib/Receiver.js b/lib/Receiver.js index 15f24cefd..a8441dfb0 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -16,7 +16,7 @@ var Validation = require('./Validation').Validation * HyBi Receiver implementation */ -function Receiver (extensions,maxPayload) { +function Receiver (extensions, maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } @@ -326,7 +326,7 @@ Receiver.prototype.error = function (reason, protocolErrorCode) { this.onerror(reason, protocolErrorCode); } else { - this.onerror(new Error('An error occured'),protocolErrorCode); + this.onerror(new Error('An error occured'), protocolErrorCode); } return this; }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 6a5225aeb..b67969945 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -789,7 +789,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { socket.setTimeout(0); socket.setNoDelay(true); - this._receiver = new ReceiverClass(this.extensions,this.maxPayload); + this._receiver = new ReceiverClass(this.extensions, this.maxPayload); this._socket = socket; // socket cleanup handlers From f30da5dad547595c7df134be97be919b4cb5062a Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 12:45:07 +0800 Subject: [PATCH 016/489] no more dependen on options (#753) We just use a little options' feature, it can be implement with ES6 feature now. --- lib/WebSocket.js | 97 ++++++++++++++++++++++-------------------- lib/WebSocketServer.js | 29 +++++++------ package.json | 1 - 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index b67969945..a2f5ff553 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -13,7 +13,6 @@ var url = require('url') , crypto = require('crypto') , stream = require('stream') , Ultron = require('ultron') - , Options = require('options') , Sender = require('./Sender') , Receiver = require('./Receiver') , SenderHixie = require('./Sender.hixie') @@ -22,6 +21,10 @@ var url = require('url') , PerMessageDeflate = require('./PerMessageDeflate') , EventEmitter = require('events').EventEmitter; +var isDefinedAndNonNull = function (options, key) { + return typeof options[key] != 'undefined' && options[key] !== null; +}; + /** * Constants */ @@ -538,24 +541,24 @@ function buildHostHeader(isSecure, hostname, port) { * which may or may not be bound to a sepcific WebSocket instance. */ function initAsServerClient(req, socket, upgradeHead, options) { - options = new Options({ + options = Object.assign({ protocolVersion: protocolVersion, protocol: null, extensions: {}, maxPayload: 0 - }).merge(options); + }, options); // expose state properties - this.protocol = options.value.protocol; - this.protocolVersion = options.value.protocolVersion; - this.extensions = options.value.extensions; + this.protocol = options.protocol; + this.protocolVersion = options.protocolVersion; + this.extensions = options.extensions; this.supports.binary = (this.protocolVersion !== 'hixie-76'); this.upgradeReq = req; this.readyState = WebSocket.CONNECTING; this._isServer = true; - this.maxPayload = options.value.maxPayload; + this.maxPayload = options.maxPayload; // establish connection - if (options.value.protocolVersion === 'hixie-76') { + if (options.protocolVersion === 'hixie-76') { establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); } else { establishConnection.call(this, Receiver, Sender, socket, upgradeHead); @@ -563,7 +566,7 @@ function initAsServerClient(req, socket, upgradeHead, options) { } function initAsClient(address, protocols, options) { - options = new Options({ + options = Object.assign({ origin: null, protocolVersion: protocolVersion, host: null, @@ -581,9 +584,9 @@ function initAsClient(address, protocols, options) { rejectUnauthorized: null, perMessageDeflate: true, localAddress: null - }).merge(options); + }, options); - if (options.value.protocolVersion !== 8 && options.value.protocolVersion !== 13) { + if (options.protocolVersion !== 8 && options.protocolVersion !== 13) { throw new Error('unsupported protocol version'); } @@ -599,24 +602,24 @@ function initAsClient(address, protocols, options) { // prepare extensions var extensionsOffer = {}; var perMessageDeflate; - if (options.value.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate(options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false); + if (options.perMessageDeflate) { + perMessageDeflate = new PerMessageDeflate(options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } // expose state properties this._isServer = false; this.url = address; - this.protocolVersion = options.value.protocolVersion; + this.protocolVersion = options.protocolVersion; this.supports.binary = (this.protocolVersion !== 'hixie-76'); // begin handshake - var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64'); + var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); var shasum = crypto.createHash('sha1'); shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); var expectedServerKey = shasum.digest('base64'); - var agent = options.value.agent; + var agent = options.agent; var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port) @@ -627,7 +630,7 @@ function initAsClient(address, protocols, options) { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Host': headerHost, - 'Sec-WebSocket-Version': options.value.protocolVersion, + 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key } }; @@ -637,18 +640,18 @@ function initAsClient(address, protocols, options) { requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64'); } - if (options.value.protocol) { - requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol; + if (options.protocol) { + requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; } - if (options.value.host) { - requestOptions.headers.Host = options.value.host; + if (options.host) { + requestOptions.headers.Host = options.host; } - if (options.value.headers) { - for (var header in options.value.headers) { - if (options.value.headers.hasOwnProperty(header)) { - requestOptions.headers[header] = options.value.headers[header]; + if (options.headers) { + for (var header in options.headers) { + if (options.headers.hasOwnProperty(header)) { + requestOptions.headers[header] = options.headers[header]; } } } @@ -657,21 +660,21 @@ function initAsClient(address, protocols, options) { requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); } - if (options.isDefinedAndNonNull('pfx') - || options.isDefinedAndNonNull('key') - || options.isDefinedAndNonNull('passphrase') - || options.isDefinedAndNonNull('cert') - || options.isDefinedAndNonNull('ca') - || options.isDefinedAndNonNull('ciphers') - || options.isDefinedAndNonNull('rejectUnauthorized')) { + if (isDefinedAndNonNull(options, 'pfx') + || isDefinedAndNonNull(options, 'key') + || isDefinedAndNonNull(options, 'passphrase') + || isDefinedAndNonNull(options, 'cert') + || isDefinedAndNonNull(options, 'ca') + || isDefinedAndNonNull(options, 'ciphers') + || isDefinedAndNonNull(options, 'rejectUnauthorized')) { - if (options.isDefinedAndNonNull('pfx')) requestOptions.pfx = options.value.pfx; - if (options.isDefinedAndNonNull('key')) requestOptions.key = options.value.key; - if (options.isDefinedAndNonNull('passphrase')) requestOptions.passphrase = options.value.passphrase; - if (options.isDefinedAndNonNull('cert')) requestOptions.cert = options.value.cert; - if (options.isDefinedAndNonNull('ca')) requestOptions.ca = options.value.ca; - if (options.isDefinedAndNonNull('ciphers')) requestOptions.ciphers = options.value.ciphers; - if (options.isDefinedAndNonNull('rejectUnauthorized')) requestOptions.rejectUnauthorized = options.value.rejectUnauthorized; + if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; + if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; + if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; + if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; + if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; + if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; + if (isDefinedAndNonNull(options, 'rejectUnauthorized')) requestOptions.rejectUnauthorized = options.rejectUnauthorized; if (!agent) { // global agent ignores client side certificates @@ -689,13 +692,13 @@ function initAsClient(address, protocols, options) { requestOptions.socketPath = serverUrl.pathname; } - if (options.value.localAddress) { - requestOptions.localAddress = options.value.localAddress; + if (options.localAddress) { + requestOptions.localAddress = options.localAddress; } - if (options.value.origin) { - if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin; - else requestOptions.headers.Origin = options.value.origin; + if (options.origin) { + if (options.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; + else requestOptions.headers.Origin = options.origin; } var self = this; @@ -736,12 +739,12 @@ function initAsClient(address, protocols, options) { } var serverProt = res.headers['sec-websocket-protocol']; - var protList = (options.value.protocol || '').split(/, */); + var protList = (options.protocol || '').split(/, */); var protError = null; - if (!options.value.protocol && serverProt) { + if (!options.protocol && serverProt) { protError = 'server sent a subprotocol even though none requested'; - } else if (options.value.protocol && !serverProt) { + } else if (options.protocol && !serverProt) { protError = 'server sent no subprotocol even though requested'; } else if (serverProt && protList.indexOf(serverProt) === -1) { protError = 'server responded with an invalid protocol'; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index c5c3850aa..4047c0ce3 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -10,12 +10,15 @@ var util = require('util') , events = require('events') , http = require('http') , crypto = require('crypto') - , Options = require('options') , WebSocket = require('./WebSocket') , Extensions = require('./Extensions') , PerMessageDeflate = require('./PerMessageDeflate') , url = require('url'); +var isDefinedAndNonNull = function (options, key) { + return typeof options[key] != 'undefined' && options[key] !== null; +}; + /** * WebSocket Server implementation */ @@ -27,7 +30,7 @@ function WebSocketServer(options, callback) { events.EventEmitter.call(this); - options = new Options({ + options = Object.assign({ host: '0.0.0.0', port: null, server: null, @@ -39,15 +42,15 @@ function WebSocketServer(options, callback) { clientTracking: true, perMessageDeflate: true, maxPayload: null - }).merge(options); + }, options); - if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { + if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { throw new TypeError('`port` or a `server` must be provided'); } var self = this; - if (options.isDefinedAndNonNull('port')) { + if (isDefinedAndNonNull(options, 'port')) { this._server = http.createServer(function (req, res) { var body = http.STATUS_CODES[426]; res.writeHead(426, { @@ -57,21 +60,21 @@ function WebSocketServer(options, callback) { res.end(body); }); this._server.allowHalfOpen = false; - this._server.listen(options.value.port, options.value.host, callback); + this._server.listen(options.port, options.host, callback); this._closeServer = function() { if (self._server) self._server.close(); }; } - else if (options.value.server) { - this._server = options.value.server; - if (options.value.path) { + else if (options.server) { + this._server = options.server; + if (options.path) { // take note of the path, to avoid collisions when multiple websocket servers are // listening on the same http server - if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) { + if (this._server._webSocketPaths && options.server._webSocketPaths[options.path]) { throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); } if (typeof this._server._webSocketPaths !== 'object') { this._server._webSocketPaths = {}; } - this._server._webSocketPaths[options.value.path] = 1; + this._server._webSocketPaths[options.path] = 1; } } if (this._server) { @@ -95,8 +98,8 @@ function WebSocketServer(options, callback) { this._server.on('upgrade', this._onServerUpgrade); } - this.options = options.value; - this.path = options.value.path; + this.options = options; + this.path = options.path; this.clients = []; } diff --git a/package.json b/package.json index 9a06b04c1..68d9f52e0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "test": "make test" }, "dependencies": { - "options": ">=0.0.5", "ultron": "1.0.x" }, "devDependencies": { From 500aabadde29f2d6ad4c679fb890bc17e2c75e3d Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 13:05:44 +0800 Subject: [PATCH 017/489] reduce usage of self = this --- lib/Receiver.js | 142 ++++++++++++++++++++--------------------- lib/WebSocket.js | 9 ++- lib/WebSocketServer.js | 3 +- 3 files changed, 76 insertions(+), 78 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index a8441dfb0..771297834 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -344,11 +344,10 @@ Receiver.prototype.flush = function() { if (!handler) return; this.processing = true; - var self = this; - handler(function() { - self.processing = false; - self.flush(); + handler(() => { + this.processing = false; + this.flush(); }); }; @@ -359,10 +358,10 @@ Receiver.prototype.flush = function() { */ Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) { - var self = this; if (compressed) { - this.extensions[PerMessageDeflate.extensionName].decompress(messageBuffer, fin, function(err, buffer) { - if (self.dead) return; + var extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(messageBuffer, fin, (err, buffer) => { + if (this.dead) return; if (err) { callback(new Error('invalid compressed data')); return; @@ -447,91 +446,90 @@ var opcodes = { // text '1': { start: function(data) { - var self = this; // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (self.maxPayloadExceeded(firstLength)){ - self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(firstLength)){ + this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['1'].getData.call(self, firstLength); + opcodes['1'].getData.call(this, firstLength); } else if (firstLength == 126) { - self.expectHeader(2, function(data) { + this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (self.maxPayloadExceeded(length)){ - self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['1'].getData.call(self, length); + opcodes['1'].getData.call(this, length); }); } else if (firstLength == 127) { - self.expectHeader(8, function(data) { + this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } var length = readUInt32BE.call(data, 4); - if (self.maxPayloadExceeded(length)){ - self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['1'].getData.call(self, readUInt32BE.call(data, 4)); + opcodes['1'].getData.call(this, readUInt32BE.call(data, 4)); }); } }, getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { + if (this.state.masked) { + this.expectHeader(4, (data) => { var mask = data; - self.expectData(length, function(data) { - opcodes['1'].finish.call(self, mask, data); + this.expectData(length, (data) => { + opcodes['1'].finish.call(this, mask, data); }); }); } else { - self.expectData(length, function(data) { - opcodes['1'].finish.call(self, null, data); + this.expectData(length, (data) => { + opcodes['1'].finish.call(this, null, data); }); } }, finish: function(mask, data) { - var self = this; var packet = this.unmask(mask, data, true) || new Buffer(0); var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { + this.messageHandlers.push((callback) => { + this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { if (err.type === 1009){ - return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } - return self.error(err.message, 1007); + return this.error(err.message, 1007); } + if (buffer != null) { - if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ - self.currentMessage.push(buffer); + if (this.maxPayload == 0 || (this.maxPayload > 0 && + (this.currentMessageLength + buffer.length) < this.maxPayload) ){ + this.currentMessage.push(buffer); } else { - self.currentMessage = null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded. maxPayload: ' + self.maxPayload), 1009); + this.currentMessage = null; + this.currentMessage = []; + this.currentMessageLength = 0; + this.error(new Error('Maximum payload exceeded. maxPayload: ' + this.maxPayload), 1009); return; } - self.currentMessageLength += buffer.length; + this.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = Buffer.concat(self.currentMessage); - self.currentMessage = []; - self.currentMessageLength = 0; + var messageBuffer = Buffer.concat(this.currentMessage); + this.currentMessage = []; + this.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { - self.error('invalid utf8 sequence', 1007); + this.error('invalid utf8 sequence', 1007); return; } - self.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); + this.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); } callback(); }); @@ -543,54 +541,52 @@ var opcodes = { // binary '2': { start: function(data) { - var self = this; // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (self.maxPayloadExceeded(firstLength)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(firstLength)){ + this.error('Max payload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['2'].getData.call(self, firstLength); + opcodes['2'].getData.call(this, firstLength); } else if (firstLength == 126) { - self.expectHeader(2, function(data) { + this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (self.maxPayloadExceeded(length)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Max payload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['2'].getData.call(self, length); + opcodes['2'].getData.call(this, length); }); } else if (firstLength == 127) { - self.expectHeader(8, function(data) { + this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } var length = readUInt32BE.call(data, 4, true); - if (self.maxPayloadExceeded(length)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Max payload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['2'].getData.call(self, length); + opcodes['2'].getData.call(this, length); }); } }, getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { + if (this.state.masked) { + this.expectHeader(4, (data) => { var mask = data; - self.expectData(length, function(data) { - opcodes['2'].finish.call(self, mask, data); + this.expectData(length, (data) => { + opcodes['2'].finish.call(this, mask, data); }); }); } else { - self.expectData(length, function(data) { - opcodes['2'].finish.call(self, null, data); + this.expectData(length, (data) => { + opcodes['2'].finish.call(this, null, data); }); } }, @@ -761,27 +757,25 @@ var opcodes = { } }, getData: function(length) { - var self = this; if (this.state.masked) { - this.expectHeader(4, function(data) { + this.expectHeader(4, (data) => { var mask = data; - self.expectData(length, function(data) { - opcodes['10'].finish.call(self, mask, data); + this.expectData(length, (data) => { + opcodes['10'].finish.call(this, mask, data); }); }); } else { - this.expectData(length, function(data) { - opcodes['10'].finish.call(self, null, data); + this.expectData(length, (data) => { + opcodes['10'].finish.call(this, null, data); }); } }, finish: function(mask, data) { - var self = this; - data = self.unmask(mask, data, true); + data = this.unmask(mask, data, true); var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.onpong(data, {masked: state.masked, binary: true}); + this.messageHandlers.push((callback) => { + this.onpong(data, {masked: state.masked, binary: true}); callback(); }); this.flush(); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a2f5ff553..684a734b8 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -269,7 +269,8 @@ WebSocket.prototype.send = function send(data, options, cb) { * Streams data through calls to a user supplied function * * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean - * @param {function} 'function (error, send)' which is executed on successive ticks of which send is 'function (data, final)'. + * @param {function} 'function (error, send)' which is executed on successive + * ticks of which send is 'function (data, final)'. * @api public */ WebSocket.prototype.stream = function stream(options, cb) { @@ -603,7 +604,8 @@ function initAsClient(address, protocols, options) { var extensionsOffer = {}; var perMessageDeflate; if (options.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate(options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false); + var opts = options.perMessageDeflate !== true ? options.perMessageDeflate : {}; + perMessageDeflate = new PerMessageDeflate(opts, false); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } @@ -674,7 +676,8 @@ function initAsClient(address, protocols, options) { if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; - if (isDefinedAndNonNull(options, 'rejectUnauthorized')) requestOptions.rejectUnauthorized = options.rejectUnauthorized; + if (isDefinedAndNonNull(options, 'rejectUnauthorized')) + requestOptions.rejectUnauthorized = options.rejectUnauthorized; if (!agent) { // global agent ignores client side certificates diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4047c0ce3..ee819a5a3 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -370,7 +370,8 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { wshost = req.headers.host; else wshost = req.headers['x-forwarded-host']; - var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url + var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; + var location = (proto + '://' + wshost + req.url) , protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer From 3e1caf42088c7cd236f23b972917588368ad8531 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Fri, 24 Jun 2016 14:16:30 +0200 Subject: [PATCH 018/489] [fix] Default to a sane value --- lib/WebSocketServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4047c0ce3..9174b2adb 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -41,7 +41,7 @@ function WebSocketServer(options, callback) { disableHixie: false, clientTracking: true, perMessageDeflate: true, - maxPayload: null + maxPayload: 100 * 1024 * 1024 }, options); if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { From 9b021ea6acc8108f552162fea1cf094d3d55f8a9 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Jun 2016 16:40:07 +1000 Subject: [PATCH 019/489] wss: fix typo s/sepcific/specific (#765) --- lib/WebSocketServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 9174b2adb..26fea6f3d 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -181,7 +181,7 @@ module.exports = WebSocketServer; /** * Entirely private apis, - * which may or may not be bound to a sepcific WebSocket instance. + * which may or may not be bound to a specific WebSocket instance. */ function handleHybiUpgrade(req, socket, upgradeHead, cb) { From e022a50458e1fb3eece5974e1928b87bf1a3e1fa Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 16:39:16 +0800 Subject: [PATCH 020/489] add comman-style rule and update code --- .eslintrc | 3 ++- lib/Receiver.hixie.js | 6 ++--- lib/Receiver.js | 11 ++++----- lib/Sender.hixie.js | 18 +++++++-------- lib/Sender.js | 24 +++++++++---------- lib/WebSocket.js | 34 +++++++++++++-------------- lib/WebSocketServer.js | 52 +++++++++++++++++++++--------------------- 7 files changed, 73 insertions(+), 75 deletions(-) diff --git a/.eslintrc b/.eslintrc index 1abddeefd..a9f44bb73 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,7 +12,8 @@ "linebreak-style": [2, "unix"], "comma-dangle": ["error", "never"], "no-unreachable": [2], - "comma-spacing": 2 + "comma-spacing": 2, + "comma-style": ["error", "last"] }, "env": { "es6": true, diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 88f803c9e..999fe6c3a 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -10,10 +10,8 @@ * State constants */ -var EMPTY = 0 - , BODY = 1; -var BINARYLENGTH = 2 - , BINARYBODY = 3; +var EMPTY = 0, BODY = 1; +var BINARYLENGTH = 2, BINARYBODY = 3; /** * Hixie Receiver implementation diff --git a/lib/Receiver.js b/lib/Receiver.js index 771297834..da27fe586 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -6,11 +6,11 @@ 'use strict'; -var Validation = require('./Validation').Validation - , ErrorCodes = require('./ErrorCodes') - , BufferPool = require('./BufferPool') - , bufferUtil = require('./BufferUtil').BufferUtil - , PerMessageDeflate = require('./PerMessageDeflate'); +var Validation = require('./Validation').Validation, + ErrorCodes = require('./ErrorCodes'), + BufferPool = require('./BufferPool'), + bufferUtil = require('./BufferUtil').BufferUtil, + PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Receiver implementation @@ -26,7 +26,6 @@ function Receiver (extensions, maxPayload) { extensions = {}; } - // memory pool for fragmented messages var fragmentedPoolPrevUsed = -1; this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 0b6271e3e..83f647633 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -6,8 +6,8 @@ 'use strict'; -var events = require('events') - , util = require('util'); +var events = require('events'), + util = require('util'); /** * Hixie Sender implementation @@ -42,13 +42,13 @@ util.inherits(Sender, events.EventEmitter); Sender.prototype.send = function(data, options, cb) { if (this.isClosed) return; - var isString = typeof data == 'string' - , length = isString ? Buffer.byteLength(data) : data.length - , lengthbytes = (length > 127) ? 2 : 1 // assume less than 2**14 bytes - , writeStartMarker = this.continuationFrame == false - , writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin) - , buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)) - , offset = writeStartMarker ? 1 : 0; + var isString = typeof data == 'string', + length = isString ? Buffer.byteLength(data) : data.length, + lengthbytes = (length > 127) ? 2 : 1, // assume less than 2**14 bytes + writeStartMarker = this.continuationFrame == false, + writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin), + buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)), + offset = writeStartMarker ? 1 : 0; if (writeStartMarker) { if (options && options.binary) { diff --git a/lib/Sender.js b/lib/Sender.js index b9a355be9..8d09c4751 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,11 +6,11 @@ 'use strict'; -var events = require('events') - , util = require('util') - , ErrorCodes = require('./ErrorCodes') - , bufferUtil = require('./BufferUtil').BufferUtil - , PerMessageDeflate = require('./PerMessageDeflate'); +var events = require('events'), + util = require('util'), + ErrorCodes = require('./ErrorCodes'), + bufferUtil = require('./BufferUtil').BufferUtil, + PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Sender implementation @@ -168,9 +168,9 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, } } - var dataLength = data.length - , dataOffset = maskData ? 6 : 2 - , secondByte = dataLength; + var dataLength = data.length, + dataOffset = maskData ? 6 : 2, + secondByte = dataLength; if (dataLength >= 65536) { dataOffset += 8; @@ -305,10 +305,10 @@ function writeUInt32BE(value, offset) { function getArrayBuffer(data) { // data is either an ArrayBuffer or ArrayBufferView. - var array = new Uint8Array(data.buffer || data) - , l = data.byteLength || data.length - , o = data.byteOffset || 0 - , buffer = new Buffer(l); + var array = new Uint8Array(data.buffer || data), + l = data.byteLength || data.length, + o = data.byteOffset || 0, + buffer = new Buffer(l); for (var i = 0; i < l; ++i) { buffer[i] = array[o + i]; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 684a734b8..30f02e0a6 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -6,20 +6,20 @@ 'use strict'; -var url = require('url') - , util = require('util') - , http = require('http') - , https = require('https') - , crypto = require('crypto') - , stream = require('stream') - , Ultron = require('ultron') - , Sender = require('./Sender') - , Receiver = require('./Receiver') - , SenderHixie = require('./Sender.hixie') - , ReceiverHixie = require('./Receiver.hixie') - , Extensions = require('./Extensions') - , PerMessageDeflate = require('./PerMessageDeflate') - , EventEmitter = require('events').EventEmitter; +var url = require('url'), + util = require('util'), + http = require('http'), + https = require('https'), + crypto = require('crypto'), + stream = require('stream'), + Ultron = require('ultron'), + Sender = require('./Sender'), + Receiver = require('./Receiver'), + SenderHixie = require('./Sender.hixie'), + ReceiverHixie = require('./Receiver.hixie'), + Extensions = require('./Extensions'), + PerMessageDeflate = require('./PerMessageDeflate'), + EventEmitter = require('events').EventEmitter; var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; @@ -788,9 +788,9 @@ function initAsClient(address, protocols, options) { } function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { - var ultron = this._ultron = new Ultron(socket) - , called = false - , self = this; + var ultron = this._ultron = new Ultron(socket), + called = false, + self = this; socket.setTimeout(0); socket.setNoDelay(true); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 49e99aa0a..9ffaed6b0 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -6,14 +6,14 @@ 'use strict'; -var util = require('util') - , events = require('events') - , http = require('http') - , crypto = require('crypto') - , WebSocket = require('./WebSocket') - , Extensions = require('./Extensions') - , PerMessageDeflate = require('./PerMessageDeflate') - , url = require('url'); +var util = require('util'), + events = require('events'), + http = require('http'), + crypto = require('crypto'), + WebSocket = require('./WebSocket'), + Extensions = require('./Extensions'), + PerMessageDeflate = require('./PerMessageDeflate'), + url = require('url'); var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; @@ -226,10 +226,10 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; if (typeof protocol != 'undefined') { @@ -360,8 +360,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { return; } - var origin = req.headers['origin'] - , self = this; + var origin = req.headers['origin'], self = this; // setup handshake completion to run after client has been verified var onClientVerified = function() { @@ -369,18 +368,19 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { if (!req.headers['x-forwarded-host']) wshost = req.headers.host; else - wshost = req.headers['x-forwarded-host']; + wshost = req.headers['x-forwarded-host']; + var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = (proto + '://' + wshost + req.url) - , protocol = req.headers['sec-websocket-protocol']; + var location = (proto + '://' + wshost + req.url), + protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer var buildResponseHeader = function() { var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: WebSocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Location: ' + location + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: WebSocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Location: ' + location ]; if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); @@ -411,13 +411,13 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // handshake completion code to run once nonce has been successfully retrieved var completeHandshake = function(nonce, rest, headerBuffer) { // calculate key - var k1 = req.headers['sec-websocket-key1'] - , k2 = req.headers['sec-websocket-key2'] - , md5 = crypto.createHash('md5'); + var k1 = req.headers['sec-websocket-key1'], + k2 = req.headers['sec-websocket-key2'], + md5 = crypto.createHash('md5'); [k1, k2].forEach(function (k) { - var n = parseInt(k.replace(/[^\d]/g, '')) - , spaces = k.replace(/[^ ]/g, '').length; + var n = parseInt(k.replace(/[^\d]/g, '')), + spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0){ abortConnection(socket, 400, 'Bad Request'); return; From 14a8a8a553efcb501a6a6717e2f4a204dca73169 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 10:58:57 +0800 Subject: [PATCH 021/489] use const --- lib/PerMessageDeflate.js | 8 ++++---- lib/Receiver.hixie.js | 4 ++-- lib/Receiver.js | 10 +++++----- lib/Sender.hixie.js | 4 ++-- lib/Sender.js | 10 +++++----- lib/WebSocket.js | 28 ++++++++++++++-------------- lib/WebSocketServer.js | 21 ++++++++++++--------- 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index a55b8cc1c..0be90f2d5 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -1,10 +1,10 @@ 'use strict'; -var zlib = require('zlib'); +const zlib = require('zlib'); -var AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; -var DEFAULT_WINDOW_BITS = 15; -var DEFAULT_MEM_LEVEL = 8; +const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; +const DEFAULT_WINDOW_BITS = 15; +const DEFAULT_MEM_LEVEL = 8; PerMessageDeflate.extensionName = 'permessage-deflate'; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 999fe6c3a..5071b3313 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -10,8 +10,8 @@ * State constants */ -var EMPTY = 0, BODY = 1; -var BINARYLENGTH = 2, BINARYBODY = 3; +const EMPTY = 0, BODY = 1; +const BINARYLENGTH = 2, BINARYBODY = 3; /** * Hixie Receiver implementation diff --git a/lib/Receiver.js b/lib/Receiver.js index da27fe586..8cd54a719 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -6,11 +6,11 @@ 'use strict'; -var Validation = require('./Validation').Validation, - ErrorCodes = require('./ErrorCodes'), - BufferPool = require('./BufferPool'), - bufferUtil = require('./BufferUtil').BufferUtil, - PerMessageDeflate = require('./PerMessageDeflate'); +const Validation = require('./Validation').Validation; +const ErrorCodes = require('./ErrorCodes'); +const BufferPool = require('./BufferPool'); +const bufferUtil = require('./BufferUtil').BufferUtil; +const PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Receiver implementation diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 83f647633..af55b1be6 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -6,8 +6,8 @@ 'use strict'; -var events = require('events'), - util = require('util'); +const events = require('events'); +const util = require('util'); /** * Hixie Sender implementation diff --git a/lib/Sender.js b/lib/Sender.js index 8d09c4751..a6612f07f 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,11 +6,11 @@ 'use strict'; -var events = require('events'), - util = require('util'), - ErrorCodes = require('./ErrorCodes'), - bufferUtil = require('./BufferUtil').BufferUtil, - PerMessageDeflate = require('./PerMessageDeflate'); +const events = require('events'); +const util = require('util'); +const ErrorCodes = require('./ErrorCodes'); +const bufferUtil = require('./BufferUtil').BufferUtil; +const PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Sender implementation diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 30f02e0a6..06cec66b9 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -6,20 +6,20 @@ 'use strict'; -var url = require('url'), - util = require('util'), - http = require('http'), - https = require('https'), - crypto = require('crypto'), - stream = require('stream'), - Ultron = require('ultron'), - Sender = require('./Sender'), - Receiver = require('./Receiver'), - SenderHixie = require('./Sender.hixie'), - ReceiverHixie = require('./Receiver.hixie'), - Extensions = require('./Extensions'), - PerMessageDeflate = require('./PerMessageDeflate'), - EventEmitter = require('events').EventEmitter; +const url = require('url'); +const util = require('util'); +const http = require('http'); +const https = require('https'); +const crypto = require('crypto'); +const stream = require('stream'); +const Ultron = require('ultron'); +const Sender = require('./Sender'); +const Receiver = require('./Receiver'); +const SenderHixie = require('./Sender.hixie'); +const ReceiverHixie = require('./Receiver.hixie'); +const Extensions = require('./Extensions'); +const PerMessageDeflate = require('./PerMessageDeflate'); +const EventEmitter = require('events').EventEmitter; var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 9ffaed6b0..e63f06230 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -6,14 +6,14 @@ 'use strict'; -var util = require('util'), - events = require('events'), - http = require('http'), - crypto = require('crypto'), - WebSocket = require('./WebSocket'), - Extensions = require('./Extensions'), - PerMessageDeflate = require('./PerMessageDeflate'), - url = require('url'); +const util = require('util'); +const events = require('events'); +const http = require('http'); +const crypto = require('crypto'); +const WebSocket = require('./WebSocket'); +const Extensions = require('./Extensions'); +const PerMessageDeflate = require('./PerMessageDeflate'); +const url = require('url'); var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; @@ -61,7 +61,10 @@ function WebSocketServer(options, callback) { }); this._server.allowHalfOpen = false; this._server.listen(options.port, options.host, callback); - this._closeServer = function() { if (self._server) self._server.close(); }; + this._closeServer = function() { + if (self._server) + self._server.close(); + }; } else if (options.server) { this._server = options.server; From 7c57252a7936eee166cd8bf48d9fd467b259a354 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 11:20:53 +0800 Subject: [PATCH 022/489] use EventEmittor = require('events') directly --- lib/Sender.hixie.js | 6 +++--- lib/Sender.js | 6 +++--- lib/WebSocketServer.js | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index af55b1be6..b17853331 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -6,7 +6,7 @@ 'use strict'; -const events = require('events'); +const EventEmitter = require('events'); const util = require('util'); /** @@ -18,7 +18,7 @@ function Sender(socket) { throw new TypeError("Classes can't be function-called"); } - events.EventEmitter.call(this); + EventEmitter.call(this); this.socket = socket; this.continuationFrame = false; @@ -31,7 +31,7 @@ module.exports = Sender; * Inherits from EventEmitter. */ -util.inherits(Sender, events.EventEmitter); +util.inherits(Sender, EventEmitter); /** * Frames and writes data. diff --git a/lib/Sender.js b/lib/Sender.js index a6612f07f..366476cae 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,7 +6,7 @@ 'use strict'; -const events = require('events'); +const EventEmitter = require('events'); const util = require('util'); const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; @@ -21,7 +21,7 @@ function Sender(socket, extensions) { throw new TypeError("Classes can't be function-called"); } - events.EventEmitter.call(this); + EventEmitter.call(this); this._socket = socket; this.extensions = extensions || {}; @@ -35,7 +35,7 @@ function Sender(socket, extensions) { * Inherits from EventEmitter. */ -util.inherits(Sender, events.EventEmitter); +util.inherits(Sender, EventEmitter); /** * Sends a close instruction to the remote party. diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index e63f06230..f8a480fd0 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -7,7 +7,7 @@ 'use strict'; const util = require('util'); -const events = require('events'); +const EventEmitter = require('events'); const http = require('http'); const crypto = require('crypto'); const WebSocket = require('./WebSocket'); @@ -28,7 +28,7 @@ function WebSocketServer(options, callback) { return new WebSocketServer(options, callback); } - events.EventEmitter.call(this); + EventEmitter.call(this); options = Object.assign({ host: '0.0.0.0', @@ -110,7 +110,7 @@ function WebSocketServer(options, callback) { * Inherits from EventEmitter. */ -util.inherits(WebSocketServer, events.EventEmitter); +util.inherits(WebSocketServer, EventEmitter); /** * Immediately shuts down the connection. From 3e00cae77dbee4df2c5045d703cfefc71ab25f6b Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 12:25:39 +0800 Subject: [PATCH 023/489] remove the NODE_PATH environment variable --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 3e8a47c5f..c2e44181c 100644 --- a/Makefile +++ b/Makefile @@ -27,13 +27,13 @@ run-coverage: $(TESTS) test: lint - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-tests integrationtest: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_INTEGRATION)" run-integrationtests + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_INTEGRATION)" run-integrationtests coverage: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-coverage + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-coverage benchmark: @node bench/sender.benchmark.js From 0e6bf5c7a31be9d31150ce34c74775726571dcee Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 14:32:05 +0800 Subject: [PATCH 024/489] Use native writeUInt{16|32}BE method --- lib/Sender.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 366476cae..925bd98c7 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -50,7 +50,7 @@ Sender.prototype.close = function(code, data, mask, cb) { } code = code || 1000; var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - writeUInt16BE.call(dataBuffer, code, 0); + dataBuffer.writeUInt16BE(code, 0); if (dataBuffer.length > 2) dataBuffer.write(data, 2); var self = this; @@ -189,11 +189,11 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, switch (secondByte) { case 126: - writeUInt16BE.call(outputBuffer, dataLength, 2); + outputBuffer.writeUInt16BE(dataLength, 2); break; case 127: - writeUInt32BE.call(outputBuffer, 0, 2); - writeUInt32BE.call(outputBuffer, dataLength, 6); + outputBuffer.writeUInt32BE(0, 2); + outputBuffer.writeUInt32BE(dataLength, 6); } if (maskData) { @@ -291,18 +291,6 @@ Sender.prototype.applyExtensions = function(data, fin, compress, callback) { module.exports = Sender; -function writeUInt16BE(value, offset) { - this[offset] = (value & 0xff00) >> 8; - this[offset + 1] = value & 0xff; -} - -function writeUInt32BE(value, offset) { - this[offset] = (value & 0xff000000) >> 24; - this[offset + 1] = (value & 0xff0000) >> 16; - this[offset + 2] = (value & 0xff00) >> 8; - this[offset + 3] = value & 0xff; -} - function getArrayBuffer(data) { // data is either an ArrayBuffer or ArrayBufferView. var array = new Uint8Array(data.buffer || data), From 4e285c4e91f77e498c7a8ded20dbd8189169d465 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 14 Jul 2016 11:39:23 +0800 Subject: [PATCH 025/489] add max-len rule --- .eslintrc | 3 ++- Makefile | 6 +++--- lib/Receiver.js | 6 ++++-- lib/Sender.hixie.js | 19 ++++++++++++------- lib/Sender.js | 4 +++- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.eslintrc b/.eslintrc index a9f44bb73..e50702170 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,7 +13,8 @@ "comma-dangle": ["error", "never"], "no-unreachable": [2], "comma-spacing": 2, - "comma-style": ["error", "last"] + "comma-style": ["error", "last"], + "max-len": ["error", 120] }, "env": { "es6": true, diff --git a/Makefile b/Makefile index c2e44181c..920bc1ff1 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -ALL_TESTS = $(shell find test/ -name '*.test.js') -ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') +ALL_TESTS = $(shell find test -name '*.test.js') +ALL_INTEGRATION = $(shell find test -name '*.integration.js') lint: - @./node_modules/.bin/eslint lib + @./node_modules/.bin/eslint lib index.js run-tests: @./node_modules/.bin/mocha \ diff --git a/lib/Receiver.js b/lib/Receiver.js index 8cd54a719..87d616922 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -602,8 +602,10 @@ var opcodes = { return self.error(err.message, 1007); } if (buffer != null) { - if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + var length = (self.currentMessageLength + buffer.length); + if (self.maxPayload == 0 || (self.maxPayload > 0 && length < self.maxPayload)) { self.currentMessage.push(buffer); + self.currentMessageLength += length; } else { self.currentMessage = null; @@ -612,8 +614,8 @@ var opcodes = { self.error(new Error('Maximum payload exceeded'), 1009); return; } - self.currentMessageLength += buffer.length; } + if (state.lastFragment) { var messageBuffer = Buffer.concat(self.currentMessage); self.currentMessage = []; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index b17853331..8e5ec6188 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -42,13 +42,18 @@ util.inherits(Sender, EventEmitter); Sender.prototype.send = function(data, options, cb) { if (this.isClosed) return; - var isString = typeof data == 'string', - length = isString ? Buffer.byteLength(data) : data.length, - lengthbytes = (length > 127) ? 2 : 1, // assume less than 2**14 bytes - writeStartMarker = this.continuationFrame == false, - writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin), - buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)), - offset = writeStartMarker ? 1 : 0; + var isString = typeof data == 'string'; + var length = isString ? Buffer.byteLength(data) : data.length; + var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes + var writeStartMarker = this.continuationFrame == false; + var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); + + var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; + bufferLength += length; + bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; + + var buffer = new Buffer(bufferLength); + var offset = writeStartMarker ? 1 : 0; if (writeStartMarker) { if (options && options.binary) { diff --git a/lib/Sender.js b/lib/Sender.js index 925bd98c7..732f855c5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -142,7 +142,9 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, if (!data) { try { - this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb); + var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] + .concat(maskData ? [0, 0, 0, 0] : []); + this._socket.write(new Buffer(buff), 'binary', cb); } catch (e) { if (typeof cb == 'function') cb(e); From ec7ee1ab57b167d70544f4946fe180c0d0ae1260 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 11:13:06 +0800 Subject: [PATCH 026/489] use classes --- lib/BufferPool.js | 74 ++--- lib/PerMessageDeflate.js | 550 ++++++++++++++++---------------- lib/Receiver.hixie.js | 281 ++++++++-------- lib/Receiver.js | 668 +++++++++++++++++++-------------------- lib/Sender.hixie.js | 204 ++++++------ lib/Sender.js | 456 +++++++++++++------------- lib/WebSocketServer.js | 9 +- 7 files changed, 1100 insertions(+), 1142 deletions(-) diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 510d4e956..57517774e 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -6,58 +6,52 @@ 'use strict'; -function BufferPool(initialSize, growStrategy, shrinkStrategy) { - if (this instanceof BufferPool === false) { - throw new TypeError("Classes can't be function-called"); - } - - this._growStrategy = (growStrategy || function(db, size) { - return db.used + size; - }).bind(null, this); +class BufferPool { + constructor(initialSize, growStrategy, shrinkStrategy) { + this._growStrategy = (growStrategy || function(db, size) { + return db.used + size; + }).bind(null, this); - this._shrinkStrategy = (shrinkStrategy || function(db) { - return initialSize; - }).bind(null, this); + this._shrinkStrategy = (shrinkStrategy || function(db) { + return initialSize; + }).bind(null, this); - this._buffer = new Buffer(initialSize); - this._offset = 0; - this._used = 0; - this._changeFactor = 0; -} + this._buffer = new Buffer(initialSize); + this._offset = 0; + this._used = 0; + this._changeFactor = 0; + } -Object.defineProperty(BufferPool.prototype, 'size', { - get: function() { + get size() { return this._buffer.length; } -}); -Object.defineProperty(BufferPool.prototype, 'used', { - get: function() { + get used() { return this._used; } -}); -BufferPool.prototype.get = function(length) { - if (this._buffer == null || this._offset + length > this._buffer.length) { - var newBuf = new Buffer(this._growStrategy(length)); - this._buffer = newBuf; - this._offset = 0; + get(length) { + if (this._buffer == null || this._offset + length > this._buffer.length) { + var newBuf = new Buffer(this._growStrategy(length)); + this._buffer = newBuf; + this._offset = 0; + } + this._used += length; + var buf = this._buffer.slice(this._offset, this._offset + length); + this._offset += length; + return buf; } - this._used += length; - var buf = this._buffer.slice(this._offset, this._offset + length); - this._offset += length; - return buf; -} -BufferPool.prototype.reset = function(forceNewBuffer) { - var len = this._shrinkStrategy(); - if (len < this.size) this._changeFactor -= 1; - if (forceNewBuffer || this._changeFactor < -2) { - this._changeFactor = 0; - this._buffer = new Buffer(len); + reset(forceNewBuffer) { + var len = this._shrinkStrategy(); + if (len < this.size) this._changeFactor -= 1; + if (forceNewBuffer || this._changeFactor < -2) { + this._changeFactor = 0; + this._buffer = new Buffer(len); + } + this._offset = 0; + this._used = 0; } - this._offset = 0; - this._used = 0; } module.exports = BufferPool; diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 0be90f2d5..c61d6ef4f 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -6,333 +6,327 @@ const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; -PerMessageDeflate.extensionName = 'permessage-deflate'; - /** * Per-message Compression Extensions implementation */ - -function PerMessageDeflate(options, isServer, maxPayload) { - if (this instanceof PerMessageDeflate === false) { - throw new TypeError("Classes can't be function-called"); +class PerMessageDeflate { + constructor(options, isServer, maxPayload) { + this._options = options || {}; + this._isServer = !!isServer; + this._inflate = null; + this._deflate = null; + this.params = null; + this._maxPayload = maxPayload || 0; } - this._options = options || {}; - this._isServer = !!isServer; - this._inflate = null; - this._deflate = null; - this.params = null; - this._maxPayload = maxPayload || 0; -} - -/** - * Create extension parameters offer - * - * @api public - */ + /** + * Create extension parameters offer + * + * @api public + */ -PerMessageDeflate.prototype.offer = function() { - var params = {}; - if (this._options.serverNoContextTakeover) { - params.server_no_context_takeover = true; - } - if (this._options.clientNoContextTakeover) { - params.client_no_context_takeover = true; - } - if (this._options.serverMaxWindowBits) { - params.server_max_window_bits = this._options.serverMaxWindowBits; - } - if (this._options.clientMaxWindowBits) { - params.client_max_window_bits = this._options.clientMaxWindowBits; - } else if (this._options.clientMaxWindowBits == null) { - params.client_max_window_bits = true; + offer() { + var params = {}; + if (this._options.serverNoContextTakeover) { + params.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + params.client_no_context_takeover = true; + } + if (this._options.serverMaxWindowBits) { + params.server_max_window_bits = this._options.serverMaxWindowBits; + } + if (this._options.clientMaxWindowBits) { + params.client_max_window_bits = this._options.clientMaxWindowBits; + } else if (this._options.clientMaxWindowBits == null) { + params.client_max_window_bits = true; + } + return params; } - return params; -}; -/** - * Accept extension offer - * - * @api public - */ - -PerMessageDeflate.prototype.accept = function(paramsList) { - paramsList = this.normalizeParams(paramsList); + /** + * Accept extension offer + * + * @api public + */ + accept(paramsList) { + paramsList = this.normalizeParams(paramsList); + + var params; + if (this._isServer) { + params = this.acceptAsServer(paramsList); + } else { + params = this.acceptAsClient(paramsList); + } - var params; - if (this._isServer) { - params = this.acceptAsServer(paramsList); - } else { - params = this.acceptAsClient(paramsList); + this.params = params; + return params; } - this.params = params; - return params; -}; - -/** - * Releases all resources used by the extension - * - * @api public - */ - -PerMessageDeflate.prototype.cleanup = function() { - if (this._inflate) { - if (this._inflate.writeInProgress) { - this._inflate.pendingClose = true; - } else { - if (this._inflate.close) this._inflate.close(); - this._inflate = null; + /** + * Releases all resources used by the extension + * + * @api public + */ + cleanup() { + if (this._inflate) { + if (this._inflate.writeInProgress) { + this._inflate.pendingClose = true; + } else { + if (this._inflate.close) this._inflate.close(); + this._inflate = null; + } } - } - if (this._deflate) { - if (this._deflate.writeInProgress) { - this._deflate.pendingClose = true; - } else { - if (this._deflate.close) this._deflate.close(); - this._deflate = null; + if (this._deflate) { + if (this._deflate.writeInProgress) { + this._deflate.pendingClose = true; + } else { + if (this._deflate.close) this._deflate.close(); + this._deflate = null; + } } } -}; -/** - * Accept extension offer from client - * - * @api private - */ + /** + * Accept extension offer from client + * + * @api private + */ + + acceptAsServer(paramsList) { + var accepted = {}; + var result = paramsList.some(function(params) { + accepted = {}; + if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { + return; + } + if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) { + return; + } + if (typeof this._options.serverMaxWindowBits === 'number' && + typeof params.server_max_window_bits === 'number' && + this._options.serverMaxWindowBits > params.server_max_window_bits) { + return; + } + if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) { + return; + } -PerMessageDeflate.prototype.acceptAsServer = function(paramsList) { - var accepted = {}; - var result = paramsList.some(function(params) { - accepted = {}; - if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { - return; - } - if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) { - return; - } - if (typeof this._options.serverMaxWindowBits === 'number' && - typeof params.server_max_window_bits === 'number' && - this._options.serverMaxWindowBits > params.server_max_window_bits) { - return; - } - if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) { - return; - } + if (this._options.serverNoContextTakeover || params.server_no_context_takeover) { + accepted.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + accepted.client_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) { + accepted.client_no_context_takeover = true; + } + if (typeof this._options.serverMaxWindowBits === 'number') { + accepted.server_max_window_bits = this._options.serverMaxWindowBits; + } else if (typeof params.server_max_window_bits === 'number') { + accepted.server_max_window_bits = params.server_max_window_bits; + } + if (typeof this._options.clientMaxWindowBits === 'number') { + accepted.client_max_window_bits = this._options.clientMaxWindowBits; + } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') { + accepted.client_max_window_bits = params.client_max_window_bits; + } + return true; + }, this); - if (this._options.serverNoContextTakeover || params.server_no_context_takeover) { - accepted.server_no_context_takeover = true; - } - if (this._options.clientNoContextTakeover) { - accepted.client_no_context_takeover = true; - } - if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) { - accepted.client_no_context_takeover = true; - } - if (typeof this._options.serverMaxWindowBits === 'number') { - accepted.server_max_window_bits = this._options.serverMaxWindowBits; - } else if (typeof params.server_max_window_bits === 'number') { - accepted.server_max_window_bits = params.server_max_window_bits; + if (!result) { + throw new Error(`Doesn't support the offered configuration`); } - if (typeof this._options.clientMaxWindowBits === 'number') { - accepted.client_max_window_bits = this._options.clientMaxWindowBits; - } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') { - accepted.client_max_window_bits = params.client_max_window_bits; - } - return true; - }, this); - if (!result) { - throw new Error(`Doesn't support the offered configuration`); + return accepted; } - return accepted; -}; - -/** - * Accept extension response from server - * - * @api privaye - */ - -PerMessageDeflate.prototype.acceptAsClient = function(paramsList) { - var params = paramsList[0]; - if (this._options.clientNoContextTakeover != null) { - if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { - throw new Error('Invalid value for "client_no_context_takeover"'); - } - } - if (this._options.clientMaxWindowBits != null) { - if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) { - throw new Error('Invalid value for "client_max_window_bits"'); + /** + * Accept extension response from server + * + * @api privaye + */ + + acceptAsClient(paramsList) { + var params = paramsList[0]; + if (this._options.clientNoContextTakeover != null) { + if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { + throw new Error('Invalid value for "client_no_context_takeover"'); + } } - if (typeof this._options.clientMaxWindowBits === 'number' && - (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) { - throw new Error('Invalid value for "client_max_window_bits"'); + if (this._options.clientMaxWindowBits != null) { + if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) { + throw new Error('Invalid value for "client_max_window_bits"'); + } + if (typeof this._options.clientMaxWindowBits === 'number' && + (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) { + throw new Error('Invalid value for "client_max_window_bits"'); + } } + return params; } - return params; -}; - -/** - * Normalize extensions parameters - * - * @api private - */ -PerMessageDeflate.prototype.normalizeParams = function(paramsList) { - return paramsList.map(function(params) { - Object.keys(params).forEach(function(key) { - var value = params[key]; - if (value.length > 1) { - throw new Error('Multiple extension parameters for ' + key); - } + /** + * Normalize extensions parameters + * + * @api private + */ + + normalizeParams(paramsList) { + return paramsList.map(function(params) { + Object.keys(params).forEach(function(key) { + var value = params[key]; + if (value.length > 1) { + throw new Error('Multiple extension parameters for ' + key); + } - value = value[0]; + value = value[0]; - switch (key) { - case 'server_no_context_takeover': - case 'client_no_context_takeover': - if (value !== true) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } - params[key] = true; - break; - case 'server_max_window_bits': - case 'client_max_window_bits': - if (typeof value === 'string') { - value = parseInt(value, 10); - if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + switch (key) { + case 'server_no_context_takeover': + case 'client_no_context_takeover': + if (value !== true) { throw new Error(`invalid extension parameter value for ${key} (${value})`); } + params[key] = true; + break; + case 'server_max_window_bits': + case 'client_max_window_bits': + if (typeof value === 'string') { + value = parseInt(value, 10); + if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + throw new Error(`invalid extension parameter value for ${key} (${value})`); + } + } + if (!this._isServer && value === true) { + throw new Error(`Missing extension parameter value for ${key}`); + } + params[key] = value; + break; + default: + throw new Error(`Not defined extension parameter (${key})`); } - if (!this._isServer && value === true) { - throw new Error(`Missing extension parameter value for ${key}`); - } - params[key] = value; - break; - default: - throw new Error(`Not defined extension parameter (${key})`); - } + }, this); + return params; }, this); - return params; - }, this); -}; + } -/** - * Decompress message - * - * @api public - */ + /** + * Decompress message + * + * @api public + */ + decompress(data, fin, callback) { + var endpoint = this._isServer ? 'client' : 'server'; + + if (!this._inflate) { + var maxWindowBits = this.params[endpoint + '_max_window_bits']; + this._inflate = zlib.createInflateRaw({ + windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS + }); + } + this._inflate.writeInProgress = true; -PerMessageDeflate.prototype.decompress = function (data, fin, callback) { - var endpoint = this._isServer ? 'client' : 'server'; + var self = this; + var buffers = []; + var cumulativeBufferLength = 0; - if (!this._inflate) { - var maxWindowBits = this.params[endpoint + '_max_window_bits']; - this._inflate = zlib.createInflateRaw({ - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS + this._inflate.on('error', onError).on('data', onData); + this._inflate.write(data); + if (fin) { + this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); + } + this._inflate.flush(function() { + cleanup(); + callback(null, Buffer.concat(buffers)); }); - } - this._inflate.writeInProgress = true; - var self = this; - var buffers = []; - var cumulativeBufferLength = 0; - - this._inflate.on('error', onError).on('data', onData); - this._inflate.write(data); - if (fin) { - this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); - } - this._inflate.flush(function() { - cleanup(); - callback(null, Buffer.concat(buffers)); - }); - - function onError(err) { - cleanup(); - callback(err); - } + function onError(err) { + cleanup(); + callback(err); + } - function onData(data) { - if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ - cumulativeBufferLength += data.length; - if (cumulativeBufferLength > self._maxPayload){ - buffers = []; - cleanup(); - var err = {type:1009}; - callback(err); - return; + function onData(data) { + if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + cumulativeBufferLength += data.length; + if (cumulativeBufferLength > self._maxPayload){ + buffers = []; + cleanup(); + var err = {type:1009}; + callback(err); + return; + } } + buffers.push(data); } - buffers.push(data); - } - function cleanup() { - if (!self._inflate) return; - self._inflate.removeListener('error', onError); - self._inflate.removeListener('data', onData); - self._inflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { - if (self._inflate.close) self._inflate.close(); - self._inflate = null; + function cleanup() { + if (!self._inflate) return; + self._inflate.removeListener('error', onError); + self._inflate.removeListener('data', onData); + self._inflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { + if (self._inflate.close) self._inflate.close(); + self._inflate = null; + } } } -}; - -/** - * Compress message - * - * @api public - */ - -PerMessageDeflate.prototype.compress = function (data, fin, callback) { - var endpoint = this._isServer ? 'server' : 'client'; - if (!this._deflate) { - var maxWindowBits = this.params[endpoint + '_max_window_bits']; - this._deflate = zlib.createDeflateRaw({ - flush: zlib.Z_SYNC_FLUSH, - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, - memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + /** + * Compress message + * + * @api public + */ + + compress(data, fin, callback) { + var endpoint = this._isServer ? 'server' : 'client'; + + if (!this._deflate) { + var maxWindowBits = this.params[endpoint + '_max_window_bits']; + this._deflate = zlib.createDeflateRaw({ + flush: zlib.Z_SYNC_FLUSH, + windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, + memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + }); + } + this._deflate.writeInProgress = true; + + var self = this; + var buffers = []; + + this._deflate.on('error', onError).on('data', onData); + this._deflate.write(data); + this._deflate.flush(function() { + cleanup(); + var data = Buffer.concat(buffers); + if (fin) { + data = data.slice(0, data.length - 4); + } + callback(null, data); }); - } - this._deflate.writeInProgress = true; - - var self = this; - var buffers = []; - this._deflate.on('error', onError).on('data', onData); - this._deflate.write(data); - this._deflate.flush(function() { - cleanup(); - var data = Buffer.concat(buffers); - if (fin) { - data = data.slice(0, data.length - 4); + function onError(err) { + cleanup(); + callback(err); } - callback(null, data); - }); - - function onError(err) { - cleanup(); - callback(err); - } - function onData(data) { - buffers.push(data); - } + function onData(data) { + buffers.push(data); + } - function cleanup() { - if (!self._deflate) return; - self._deflate.removeListener('error', onError); - self._deflate.removeListener('data', onData); - self._deflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { - if (self._deflate.close) self._deflate.close(); - self._deflate = null; + function cleanup() { + if (!self._deflate) return; + self._deflate.removeListener('error', onError); + self._deflate.removeListener('data', onData); + self._deflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { + if (self._deflate.close) self._deflate.close(); + self._deflate = null; + } } } -}; +} + +PerMessageDeflate.extensionName = 'permessage-deflate'; module.exports = PerMessageDeflate; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 5071b3313..e90422efc 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -17,165 +17,162 @@ const BINARYLENGTH = 2, BINARYBODY = 3; * Hixie Receiver implementation */ -function Receiver () { - if (this instanceof Receiver === false) { - throw new TypeError(`Classes can't be function-called`); +class Receiver { + constructor() { + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; + this.dead = false; + + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; } - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - this.dead = false; - - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; -} - -module.exports = Receiver; - -/** - * Add new data to the parser. - * - * @api public - */ - -Receiver.prototype.add = function(data) { - if (this.dead) return; - var self = this; - function doAdd() { - if (self.state === EMPTY) { - if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { - self.reset(); - self.onclose(); - return; - } - if (data[0] === 0x80) { - self.messageEnd = 0; - self.state = BINARYLENGTH; - data = data.slice(1); - } else { - - if (data[0] !== 0x00) { - self.error('payload must start with 0x00 byte', true); + /** + * Add new data to the parser. + * + * @api public + */ + + add(data) { + if (this.dead) return; + var self = this; + function doAdd() { + if (self.state === EMPTY) { + if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { + self.reset(); + self.onclose(); return; } - data = data.slice(1); - self.state = BODY; + if (data[0] === 0x80) { + self.messageEnd = 0; + self.state = BINARYLENGTH; + data = data.slice(1); + } else { + + if (data[0] !== 0x00) { + self.error('payload must start with 0x00 byte', true); + return; + } + data = data.slice(1); + self.state = BODY; + } } - } - if (self.state === BINARYLENGTH) { - var i = 0; - while ((i < data.length) && (data[i] & 0x80)) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - ++i; - } - if (i < data.length) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - self.state = BINARYBODY; - ++i; + if (self.state === BINARYLENGTH) { + var i = 0; + while ((i < data.length) && (data[i] & 0x80)) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + ++i; + } + if (i < data.length) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + self.state = BINARYBODY; + ++i; + } + if (i > 0) + data = data.slice(i); } - if (i > 0) - data = data.slice(i); - } - if (self.state === BINARYBODY) { - var dataleft = self.messageEnd - self.spanLength; - if (data.length >= dataleft) { - // consume the whole buffer to finish the frame + if (self.state === BINARYBODY) { + var dataleft = self.messageEnd - self.spanLength; + if (data.length >= dataleft) { + // consume the whole buffer to finish the frame + self.buffers.push(data); + self.spanLength += dataleft; + self.messageEnd = dataleft; + return self.parse(); + } + // frame's not done even if we consume it all self.buffers.push(data); - self.spanLength += dataleft; - self.messageEnd = dataleft; - return self.parse(); + self.spanLength += data.length; + return; } - // frame's not done even if we consume it all self.buffers.push(data); - self.spanLength += data.length; - return; - } - self.buffers.push(data); - if ((self.messageEnd = data.indexOf(0xFF)) != -1) { - self.spanLength += self.messageEnd; - return self.parse(); + if ((self.messageEnd = data.indexOf(0xFF)) != -1) { + self.spanLength += self.messageEnd; + return self.parse(); + } + else self.spanLength += data.length; } - else self.spanLength += data.length; + while (data) data = doAdd(); } - while (data) data = doAdd(); -}; -/** - * Releases all resources used by the receiver. - * - * @api public - */ - -Receiver.prototype.cleanup = function() { - this.dead = true; - this.state = EMPTY; - this.buffers = []; -}; - -/** - * Process buffered data. - * - * @api public - */ + /** + * Releases all resources used by the receiver. + * + * @api public + */ -Receiver.prototype.parse = function() { - var output = new Buffer(this.spanLength); - var outputIndex = 0; - for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { - var buffer = this.buffers[bi]; - buffer.copy(output, outputIndex); - outputIndex += buffer.length; - } - var lastBuffer = this.buffers[this.buffers.length - 1]; - if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); - if (this.state !== BODY) --this.messageEnd; - var tail = null; - if (this.messageEnd < lastBuffer.length - 1) { - tail = lastBuffer.slice(this.messageEnd + 1); + cleanup() { + this.dead = true; + this.state = EMPTY; + this.buffers = []; } - this.reset(); - this.ontext(output.toString('utf8')); - return tail; -}; -/** - * Handles an error - * - * @api private - */ - -Receiver.prototype.error = function (reason, terminate) { - if (this.dead) return; - this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), terminate); - } - else if (reason.constructor == Error){ - this.onerror(reason, terminate); + /** + * Process buffered data. + * + * @api public + */ + + parse() { + var output = new Buffer(this.spanLength); + var outputIndex = 0; + for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { + var buffer = this.buffers[bi]; + buffer.copy(output, outputIndex); + outputIndex += buffer.length; + } + var lastBuffer = this.buffers[this.buffers.length - 1]; + if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); + if (this.state !== BODY) --this.messageEnd; + var tail = null; + if (this.messageEnd < lastBuffer.length - 1) { + tail = lastBuffer.slice(this.messageEnd + 1); + } + this.reset(); + this.ontext(output.toString('utf8')); + return tail; } - else { - this.onerror(new Error('An error occured'), terminate); + + /** + * Handles an error + * + * @api private + */ + + error(reason, terminate) { + if (this.dead) return; + this.reset(); + if (typeof reason == 'string'){ + this.onerror(new Error(reason), terminate); + } + else if (reason.constructor == Error){ + this.onerror(reason, terminate); + } + else { + this.onerror(new Error('An error occured'), terminate); + } + return this; } - return this; -}; -/** - * Reset parser state - * - * @api private - */ + /** + * Reset parser state + * + * @api private + */ + reset(reason) { + if (this.dead) return; + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; + } +} -Receiver.prototype.reset = function (reason) { - if (this.dead) return; - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; -}; +module.exports = Receiver; diff --git a/lib/Receiver.js b/lib/Receiver.js index 87d616922..6a4c70638 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -16,382 +16,382 @@ const PerMessageDeflate = require('./PerMessageDeflate'); * HyBi Receiver implementation */ -function Receiver (extensions, maxPayload) { - if (this instanceof Receiver === false) { - throw new TypeError("Classes can't be function-called"); - } - - if (typeof extensions === 'number'){ - maxPayload = extensions; - extensions = {}; - } - - // memory pool for fragmented messages - var fragmentedPoolPrevUsed = -1; - this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { - return db.used + length; - }, function(db) { - return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? - Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : - db.used; - }); - - // memory pool for unfragmented messages - var unfragmentedPoolPrevUsed = -1; - this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { - return db.used + length; - }, function(db) { - return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? - Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : - db.used; - }); - this.extensions = extensions || {}; - this.maxPayload = maxPayload || 0; - this.currentPayloadLength = 0; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0, - fragmentedOperation: false - }; - this.overflow = []; - this.headerBuffer = new Buffer(10); - this.expectOffset = 0; - this.expectBuffer = null; - this.expectHandler = null; - this.currentMessage = []; - this.currentMessageLength = 0; - this.messageHandlers = []; - this.expectHeader(2, this.processPacket); - this.dead = false; - this.processing = false; - - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; -} - -module.exports = Receiver; +class Receiver { + constructor(extensions, maxPayload) { + if (typeof extensions === 'number'){ + maxPayload = extensions; + extensions = {}; + } -/** - * Add new data to the parser. - * - * @api public - */ + // memory pool for fragmented messages + var fragmentedPoolPrevUsed = -1; + this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? + Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : + db.used; + }); -Receiver.prototype.add = function(data) { - if (this.dead) return; - var dataLength = data.length; - if (dataLength == 0) return; - if (this.expectBuffer == null) { - this.overflow.push(data); - return; - } - var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); - fastCopy(toRead, data, this.expectBuffer, this.expectOffset); - this.expectOffset += toRead; - if (toRead < dataLength) { - this.overflow.push(data.slice(toRead)); - } - while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { - var bufferForHandler = this.expectBuffer; - this.expectBuffer = null; + // memory pool for unfragmented messages + var unfragmentedPoolPrevUsed = -1; + this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? + Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : + db.used; + }); + this.extensions = extensions || {}; + this.maxPayload = maxPayload || 0; + this.currentPayloadLength = 0; + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.overflow = []; + this.headerBuffer = new Buffer(10); this.expectOffset = 0; - this.expectHandler.call(this, bufferForHandler); - } -}; - -/** - * Releases all resources used by the receiver. - * - * @api public - */ - -Receiver.prototype.cleanup = function() { - this.dead = true; - this.overflow = null; - this.headerBuffer = null; - this.expectBuffer = null; - this.expectHandler = null; - this.unfragmentedBufferPool = null; - this.fragmentedBufferPool = null; - this.state = null; - this.currentMessage = null; - this.onerror = null; - this.ontext = null; - this.onbinary = null; - this.onclose = null; - this.onping = null; - this.onpong = null; -}; - -/** - * Waits for a certain amount of header bytes to be available, then fires a callback. - * - * @api private - */ + this.expectBuffer = null; + this.expectHandler = null; + this.currentMessage = []; + this.currentMessageLength = 0; + this.messageHandlers = []; + this.expectHeader(2, this.processPacket); + this.dead = false; + this.processing = false; -Receiver.prototype.expectHeader = function(length, handler) { - if (length == 0) { - handler(null); - return; - } - this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); - this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - var read = Math.min(fromOverflow.length, toRead); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; } -}; - -/** - * Waits for a certain amount of data bytes to be available, then fires a callback. - * - * @api private - */ -Receiver.prototype.expectData = function(length, handler) { - if (length == 0) { - handler(null); - return; - } - this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); - this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - var read = Math.min(fromOverflow.length, toRead); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; + /** + * Add new data to the parser. + * + * @api public + */ + + add(data) { + if (this.dead) return; + var dataLength = data.length; + if (dataLength == 0) return; + if (this.expectBuffer == null) { + this.overflow.push(data); + return; + } + var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); + fastCopy(toRead, data, this.expectBuffer, this.expectOffset); + this.expectOffset += toRead; + if (toRead < dataLength) { + this.overflow.push(data.slice(toRead)); + } + while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { + var bufferForHandler = this.expectBuffer; + this.expectBuffer = null; + this.expectOffset = 0; + this.expectHandler.call(this, bufferForHandler); + } } -}; -/** - * Allocates memory from the buffer pool. - * - * @api private - */ + /** + * Releases all resources used by the receiver. + * + * @api public + */ -Receiver.prototype.allocateFromPool = function(length, isFragmented) { - return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); -}; + cleanup() { + this.dead = true; + this.overflow = null; + this.headerBuffer = null; + this.expectBuffer = null; + this.expectHandler = null; + this.unfragmentedBufferPool = null; + this.fragmentedBufferPool = null; + this.state = null; + this.currentMessage = null; + this.onerror = null; + this.ontext = null; + this.onbinary = null; + this.onclose = null; + this.onping = null; + this.onpong = null; + } -/** - * Start processing a new packet. - * - * @api private - */ + /** + * Waits for a certain amount of header bytes to be available, then fires a callback. + * + * @api private + */ -Receiver.prototype.processPacket = function (data) { - if (this.extensions[PerMessageDeflate.extensionName]) { - if ((data[0] & 0x30) != 0) { - this.error('reserved fields (2, 3) must be empty', 1002); + expectHeader(length, handler) { + if (length == 0) { + handler(null); return; } - } else { - if ((data[0] & 0x70) != 0) { - this.error('reserved fields must be empty', 1002); - return; + this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; } } - this.state.lastFragment = (data[0] & 0x80) == 0x80; - this.state.masked = (data[1] & 0x80) == 0x80; - var compressed = (data[0] & 0x40) == 0x40; - var opcode = data[0] & 0xf; - if (opcode === 0) { - if (compressed) { - this.error('continuation frame cannot have the Per-message Compressed bits', 1002); + + /** + * Waits for a certain amount of data bytes to be available, then fires a callback. + * + * @api private + */ + + expectData(length, handler) { + if (length == 0) { + handler(null); return; } - // continuation frame - this.state.fragmentedOperation = true; - this.state.opcode = this.state.activeFragmentedOperation; - if (!(this.state.opcode == 1 || this.state.opcode == 2)) { - this.error('continuation frame cannot follow current opcode', 1002); - return; + this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; } } - else { - if (opcode < 3 && this.state.activeFragmentedOperation != null) { - this.error('data frames after the initial data frame must have opcode 0', 1002); - return; - } - if (opcode >= 8 && compressed) { - this.error('control frames cannot have the Per-message Compressed bits', 1002); - return; + + /** + * Allocates memory from the buffer pool. + * + * @api private + */ + + allocateFromPool(length, isFragmented) { + return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); + } + + /** + * Start processing a new packet. + * + * @api private + */ + + processPacket (data) { + if (this.extensions[PerMessageDeflate.extensionName]) { + if ((data[0] & 0x30) != 0) { + this.error('reserved fields (2, 3) must be empty', 1002); + return; + } + } else { + if ((data[0] & 0x70) != 0) { + this.error('reserved fields must be empty', 1002); + return; + } } - this.state.compressed = compressed; - this.state.opcode = opcode; - if (this.state.lastFragment === false) { + this.state.lastFragment = (data[0] & 0x80) == 0x80; + this.state.masked = (data[1] & 0x80) == 0x80; + var compressed = (data[0] & 0x40) == 0x40; + var opcode = data[0] & 0xf; + if (opcode === 0) { + if (compressed) { + this.error('continuation frame cannot have the Per-message Compressed bits', 1002); + return; + } + // continuation frame this.state.fragmentedOperation = true; - this.state.activeFragmentedOperation = opcode; + this.state.opcode = this.state.activeFragmentedOperation; + if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + this.error('continuation frame cannot follow current opcode', 1002); + return; + } + } + else { + if (opcode < 3 && this.state.activeFragmentedOperation != null) { + this.error('data frames after the initial data frame must have opcode 0', 1002); + return; + } + if (opcode >= 8 && compressed) { + this.error('control frames cannot have the Per-message Compressed bits', 1002); + return; + } + this.state.compressed = compressed; + this.state.opcode = opcode; + if (this.state.lastFragment === false) { + this.state.fragmentedOperation = true; + this.state.activeFragmentedOperation = opcode; + } + else this.state.fragmentedOperation = false; + } + var handler = opcodes[this.state.opcode]; + if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); + else { + handler.start.call(this, data); } - else this.state.fragmentedOperation = false; - } - var handler = opcodes[this.state.opcode]; - if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); - else { - handler.start.call(this, data); } -}; -/** - * Endprocessing a packet. - * - * @api private - */ + /** + * Endprocessing a packet. + * + * @api private + */ -Receiver.prototype.endPacket = function() { - if (this.dead) return; - if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); - else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; - this.expectHandler = null; - if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { - // end current fragmented operation - this.state.activeFragmentedOperation = null; + endPacket() { + if (this.dead) return; + if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); + else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { + // end current fragmented operation + this.state.activeFragmentedOperation = null; + } + this.currentPayloadLength = 0; + this.state.lastFragment = false; + this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; + this.state.masked = false; + this.expectHeader(2, this.processPacket); } - this.currentPayloadLength = 0; - this.state.lastFragment = false; - this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; - this.state.masked = false; - this.expectHeader(2, this.processPacket); -}; - -/** - * Reset the parser state. - * - * @api private - */ - -Receiver.prototype.reset = function() { - if (this.dead) return; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0, - fragmentedOperation: false - }; - this.fragmentedBufferPool.reset(true); - this.unfragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; - this.expectHandler = null; - this.overflow = []; - this.currentMessage = []; - this.currentMessageLength = 0; - this.messageHandlers = []; - this.currentPayloadLength = 0; -}; - -/** - * Unmask received data. - * - * @api private - */ -Receiver.prototype.unmask = function (mask, buf, binary) { - if (mask != null && buf != null) bufferUtil.unmask(buf, mask); - if (binary) return buf; - return buf != null ? buf.toString('utf8') : ''; -}; + /** + * Reset the parser state. + * + * @api private + */ + + reset() { + if (this.dead) return; + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.fragmentedBufferPool.reset(true); + this.unfragmentedBufferPool.reset(true); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.overflow = []; + this.currentMessage = []; + this.currentMessageLength = 0; + this.messageHandlers = []; + this.currentPayloadLength = 0; + } -/** - * Handles an error - * - * @api private - */ + /** + * Unmask received data. + * + * @api private + */ -Receiver.prototype.error = function (reason, protocolErrorCode) { - if (this.dead) return; - this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), protocolErrorCode); - } - else if (reason.constructor == Error){ - this.onerror(reason, protocolErrorCode); + unmask (mask, buf, binary) { + if (mask != null && buf != null) bufferUtil.unmask(buf, mask); + if (binary) return buf; + return buf != null ? buf.toString('utf8') : ''; } - else { - this.onerror(new Error('An error occured'), protocolErrorCode); - } - return this; -}; -/** - * Execute message handler buffers - * - * @api private - */ + /** + * Handles an error + * + * @api private + */ + + error (reason, protocolErrorCode) { + if (this.dead) return; + this.reset(); + if (typeof reason == 'string'){ + this.onerror(new Error(reason), protocolErrorCode); + } + else if (reason.constructor == Error){ + this.onerror(reason, protocolErrorCode); + } + else { + this.onerror(new Error('An error occured'), protocolErrorCode); + } + return this; + } -Receiver.prototype.flush = function() { - if (this.processing || this.dead) return; + /** + * Execute message handler buffers + * + * @api private + */ - var handler = this.messageHandlers.shift(); - if (!handler) return; + flush() { + if (this.processing || this.dead) return; - this.processing = true; + var handler = this.messageHandlers.shift(); + if (!handler) return; - handler(() => { - this.processing = false; - this.flush(); - }); -}; + this.processing = true; -/** - * Apply extensions to message - * - * @api private - */ - -Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) { - if (compressed) { - var extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(messageBuffer, fin, (err, buffer) => { - if (this.dead) return; - if (err) { - callback(new Error('invalid compressed data')); - return; - } - callback(null, buffer); + handler(() => { + this.processing = false; + this.flush(); }); - } else { - callback(null, messageBuffer); } -}; -/** -* Checks payload size, disconnects socket when it exceeds maxPayload -* -* @api private -*/ -Receiver.prototype.maxPayloadExceeded = function(length) { - if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { - return false; + /** + * Apply extensions to message + * + * @api private + */ + + applyExtensions(messageBuffer, fin, compressed, callback) { + if (compressed) { + var extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(messageBuffer, fin, (err, buffer) => { + if (this.dead) return; + if (err) { + callback(new Error('invalid compressed data')); + return; + } + callback(null, buffer); + }); + } else { + callback(null, messageBuffer); + } } - var fullLength = this.currentPayloadLength + length; - if (fullLength < this.maxPayload) { - this.currentPayloadLength = fullLength; - return false; + + /** + * Checks payload size, disconnects socket when it exceeds maxPayload + * + * @api private + */ + maxPayloadExceeded(length) { + if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { + return false; + } + var fullLength = this.currentPayloadLength + length; + if (fullLength < this.maxPayload) { + this.currentPayloadLength = fullLength; + return false; + } + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.messageBuffer = []; + this.cleanup(); + + return true; } - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); - this.messageBuffer = []; - this.cleanup(); +} + +module.exports = Receiver; + - return true; -}; /** * Buffer utilities diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 8e5ec6188..69f4ae053 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -7,124 +7,112 @@ 'use strict'; const EventEmitter = require('events'); -const util = require('util'); /** - * Hixie Sender implementation + * Hixie Sender implementation, Inherits from EventEmitter. */ -function Sender(socket) { - if (this instanceof Sender === false) { - throw new TypeError("Classes can't be function-called"); - } - - EventEmitter.call(this); - - this.socket = socket; - this.continuationFrame = false; - this.isClosed = false; -} - -module.exports = Sender; - -/** - * Inherits from EventEmitter. - */ - -util.inherits(Sender, EventEmitter); - -/** - * Frames and writes data. - * - * @api public - */ - -Sender.prototype.send = function(data, options, cb) { - if (this.isClosed) return; - - var isString = typeof data == 'string'; - var length = isString ? Buffer.byteLength(data) : data.length; - var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes - var writeStartMarker = this.continuationFrame == false; - var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); - - var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; - bufferLength += length; - bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; - - var buffer = new Buffer(bufferLength); - var offset = writeStartMarker ? 1 : 0; - - if (writeStartMarker) { - if (options && options.binary) { - buffer.write('\x80', 'binary'); - // assume length less than 2**14 bytes - if (lengthbytes > 1) - buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); - buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); - } else - buffer.write('\x00', 'binary'); - } - - if (isString) buffer.write(data, offset, 'utf8'); - else data.copy(buffer, offset, 0); +class Sender extends EventEmitter { + constructor(socket) { + super(); - if (writeEndMarker) { - if (options && options.binary) { - // sending binary, not writing end marker - } else - buffer.write('\xff', offset + length, 'binary'); + this.socket = socket; this.continuationFrame = false; + this.isClosed = false; } - else this.continuationFrame = true; - try { - this.socket.write(buffer, 'binary', cb); - } catch (e) { - this.error(e.toString()); + /** + * Frames and writes data. + * + * @api public + */ + send(data, options, cb) { + if (this.isClosed) return; + + var isString = typeof data == 'string'; + var length = isString ? Buffer.byteLength(data) : data.length; + var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes + var writeStartMarker = this.continuationFrame == false; + var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); + + var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; + bufferLength += length; + bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; + + var buffer = new Buffer(bufferLength); + var offset = writeStartMarker ? 1 : 0; + + if (writeStartMarker) { + if (options && options.binary) { + buffer.write('\x80', 'binary'); + // assume length less than 2**14 bytes + if (lengthbytes > 1) + buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); + buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); + } else + buffer.write('\x00', 'binary'); + } + + if (isString) buffer.write(data, offset, 'utf8'); + else data.copy(buffer, offset, 0); + + if (writeEndMarker) { + if (options && options.binary) { + // sending binary, not writing end marker + } else + buffer.write('\xff', offset + length, 'binary'); + this.continuationFrame = false; + } + else this.continuationFrame = true; + + try { + this.socket.write(buffer, 'binary', cb); + } catch (e) { + this.error(e.toString()); + } } -}; -/** - * Sends a close instruction to the remote party. - * - * @api public - */ - -Sender.prototype.close = function(code, data, mask, cb) { - if (this.isClosed) return; - this.isClosed = true; - try { - if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); - this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); - } catch (e) { - this.error(e.toString()); + /** + * Sends a close instruction to the remote party. + * + * @api public + */ + + close(code, data, mask, cb) { + if (this.isClosed) return; + this.isClosed = true; + try { + if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); + this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); + } catch (e) { + this.error(e.toString()); + } } -}; - -/** - * Sends a ping message to the remote party. Not available for hixie. - * - * @api public - */ - -Sender.prototype.ping = function(data, options) {}; - -/** - * Sends a pong message to the remote party. Not available for hixie. - * - * @api public - */ -Sender.prototype.pong = function(data, options) {}; - -/** - * Handles an error - * - * @api private - */ + /** + * Sends a ping message to the remote party. Not available for hixie. + * + * @api public + */ + + ping(data, options) {} + + /** + * Sends a pong message to the remote party. Not available for hixie. + * + * @api public + */ + pong(data, options) {} + + /** + * Handles an error + * + * @api private + */ + error(reason) { + this.emit('error', reason); + return this; + } +} -Sender.prototype.error = function (reason) { - this.emit('error', reason); - return this; -}; +module.exports = Sender; diff --git a/lib/Sender.js b/lib/Sender.js index 732f855c5..ff54e6fe2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -7,289 +7,275 @@ 'use strict'; const EventEmitter = require('events'); -const util = require('util'); const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); /** - * HyBi Sender implementation + * HyBi Sender implementation, Inherits from EventEmitter. */ - -function Sender(socket, extensions) { - if (this instanceof Sender === false) { - throw new TypeError("Classes can't be function-called"); +class Sender extends EventEmitter { + constructor(socket, extensions) { + super(); + + this._socket = socket; + this.extensions = extensions || {}; + this.firstFragment = true; + this.compress = false; + this.messageHandlers = []; + this.processing = false; } - EventEmitter.call(this); - - this._socket = socket; - this.extensions = extensions || {}; - this.firstFragment = true; - this.compress = false; - this.messageHandlers = []; - this.processing = false; -} - -/** - * Inherits from EventEmitter. - */ - -util.inherits(Sender, EventEmitter); - -/** - * Sends a close instruction to the remote party. - * - * @api public - */ - -Sender.prototype.close = function(code, data, mask, cb) { - if (typeof code !== 'undefined') { - if (typeof code !== 'number' || - !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + /** + * Sends a close instruction to the remote party. + * + * @api public + */ + close(code, data, mask, cb) { + if (typeof code !== 'undefined') { + if (typeof code !== 'number' || + !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + } + code = code || 1000; + var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); + dataBuffer.writeUInt16BE(code, 0); + if (dataBuffer.length > 2) dataBuffer.write(data, 2); + + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0x8, dataBuffer, true, mask); + callback(); + if (typeof cb == 'function') cb(); + }); + this.flush(); } - code = code || 1000; - var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - dataBuffer.writeUInt16BE(code, 0); - if (dataBuffer.length > 2) dataBuffer.write(data, 2); - - var self = this; - this.messageHandlers.push(function(callback) { - self.frameAndSend(0x8, dataBuffer, true, mask); - callback(); - if (typeof cb == 'function') cb(); - }); - this.flush(); -}; -/** - * Sends a ping message to the remote party. - * - * @api public - */ - -Sender.prototype.ping = function(data, options) { - var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function(callback) { - self.frameAndSend(0x9, data || '', true, mask); - callback(); - }); - this.flush(); -}; - -/** - * Sends a pong message to the remote party. - * - * @api public - */ - -Sender.prototype.pong = function(data, options) { - var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function(callback) { - self.frameAndSend(0xa, data || '', true, mask); - callback(); - }); - this.flush(); -}; - -/** - * Sends text or binary data to the remote party. - * - * @api public - */ - -Sender.prototype.send = function(data, options, cb) { - var finalFragment = options && options.fin === false ? false : true; - var mask = options && options.mask; - var compress = options && options.compress; - var opcode = options && options.binary ? 2 : 1; - if (this.firstFragment === false) { - opcode = 0; - compress = false; - } else { - this.firstFragment = false; - this.compress = compress; + /** + * Sends a ping message to the remote party. + * + * @api public + */ + ping(data, options) { + var mask = options && options.mask; + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0x9, data || '', true, mask); + callback(); + }); + this.flush(); } - if (finalFragment) this.firstFragment = true - - var compressFragment = this.compress; - var self = this; - this.messageHandlers.push(function(callback) { - self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { - if (err) { - if (typeof cb == 'function') cb(err); - else self.emit('error', err); - return; - } - self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + /** + * Sends a pong message to the remote party. + * + * @api public + */ + pong(data, options) { + var mask = options && options.mask; + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0xa, data || '', true, mask); callback(); }); - }); - this.flush(); -}; - -/** - * Frames and sends a piece of data according to the HyBi WebSocket protocol. - * - * @api private - */ - -Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, compressed, cb) { - var canModifyData = false; - if (!data) { - try { - var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] - .concat(maskData ? [0, 0, 0, 0] : []); - this._socket.write(new Buffer(buff), 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } - return; + this.flush(); } - if (!Buffer.isBuffer(data)) { - canModifyData = true; - if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { - data = getArrayBuffer(data); + /** + * Sends text or binary data to the remote party. + * + * @api public + */ + + send(data, options, cb) { + var finalFragment = options && options.fin === false ? false : true; + var mask = options && options.mask; + var compress = options && options.compress; + var opcode = options && options.binary ? 2 : 1; + if (this.firstFragment === false) { + opcode = 0; + compress = false; } else { - // - // If people want to send a number, this would allocate the number in - // bytes as memory size instead of storing the number as buffer value. So - // we need to transform it to string in order to prevent possible - // vulnerabilities / memory attacks. - // - if (typeof data === 'number') data = data.toString(); - - data = new Buffer(data); + this.firstFragment = false; + this.compress = compress; } + if (finalFragment) this.firstFragment = true + + var compressFragment = this.compress; + + var self = this; + this.messageHandlers.push(function(callback) { + self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { + if (err) { + if (typeof cb == 'function') cb(err); + else self.emit('error', err); + return; + } + self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + callback(); + }); + }); + this.flush(); } - var dataLength = data.length, - dataOffset = maskData ? 6 : 2, - secondByte = dataLength; - - if (dataLength >= 65536) { - dataOffset += 8; - secondByte = 127; - } - else if (dataLength > 125) { - dataOffset += 2; - secondByte = 126; - } + /** + * Frames and sends a piece of data according to the HyBi WebSocket protocol. + * + * @api private + */ + frameAndSend(opcode, data, finalFragment, maskData, compressed, cb) { + var canModifyData = false; - var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); - var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; - var outputBuffer = new Buffer(totalLength); - outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; - if (compressed) outputBuffer[0] |= 0x40; - - switch (secondByte) { - case 126: - outputBuffer.writeUInt16BE(dataLength, 2); - break; - case 127: - outputBuffer.writeUInt32BE(0, 2); - outputBuffer.writeUInt32BE(dataLength, 6); - } + if (!data) { - if (maskData) { - outputBuffer[1] = secondByte | 0x80; - var mask = getRandomMask(); - outputBuffer[dataOffset - 4] = mask[0]; - outputBuffer[dataOffset - 3] = mask[1]; - outputBuffer[dataOffset - 2] = mask[2]; - outputBuffer[dataOffset - 1] = mask[3]; - if (mergeBuffers) { - bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); try { - this._socket.write(outputBuffer, 'binary', cb); + var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] + .concat(maskData ? [0, 0, 0, 0] : []); + this._socket.write(new Buffer(buff), 'binary', cb); } catch (e) { if (typeof cb == 'function') cb(e); else this.emit('error', e); } + return; } - else { - bufferUtil.mask(data, mask, data, 0, dataLength); - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); + + if (!Buffer.isBuffer(data)) { + canModifyData = true; + if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { + data = getArrayBuffer(data); + } else { + // + // If people want to send a number, this would allocate the number in + // bytes as memory size instead of storing the number as buffer value. So + // we need to transform it to string in order to prevent possible + // vulnerabilities / memory attacks. + // + if (typeof data === 'number') data = data.toString(); + + data = new Buffer(data); } } - } - else { - outputBuffer[1] = secondByte; - if (mergeBuffers) { - data.copy(outputBuffer, dataOffset); - try { - this._socket.write(outputBuffer, 'binary', cb); + + var dataLength = data.length, + dataOffset = maskData ? 6 : 2, + secondByte = dataLength; + + if (dataLength >= 65536) { + dataOffset += 8; + secondByte = 127; + } + else if (dataLength > 125) { + dataOffset += 2; + secondByte = 126; + } + + var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); + var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; + var outputBuffer = new Buffer(totalLength); + outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; + if (compressed) outputBuffer[0] |= 0x40; + + switch (secondByte) { + case 126: + outputBuffer.writeUInt16BE(dataLength, 2); + break; + case 127: + outputBuffer.writeUInt32BE(0, 2); + outputBuffer.writeUInt32BE(dataLength, 6); + } + + if (maskData) { + outputBuffer[1] = secondByte | 0x80; + var mask = getRandomMask(); + outputBuffer[dataOffset - 4] = mask[0]; + outputBuffer[dataOffset - 3] = mask[1]; + outputBuffer[dataOffset - 2] = mask[2]; + outputBuffer[dataOffset - 1] = mask[3]; + if (mergeBuffers) { + bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); + else { + bufferUtil.mask(data, mask, data, 0, dataLength); + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } } else { - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); + outputBuffer[1] = secondByte; + if (mergeBuffers) { + data.copy(outputBuffer, dataOffset); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); + else { + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } } } -}; - -/** - * Execute message handler buffers - * - * @api private - */ - -Sender.prototype.flush = function() { - if (this.processing) return; - var handler = this.messageHandlers.shift(); - if (!handler) return; + /** + * Execute message handler buffers + * + * @api private + */ + flush() { + if (this.processing) return; - this.processing = true; + var handler = this.messageHandlers.shift(); + if (!handler) return; - var self = this; + this.processing = true; - handler(function() { - self.processing = false; - self.flush(); - }); -}; + var self = this; -/** - * Apply extensions to message - * - * @api private - */ + handler(function() { + self.processing = false; + self.flush(); + }); + } -Sender.prototype.applyExtensions = function(data, fin, compress, callback) { - if (compress && data) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getArrayBuffer(data); + /** + * Apply extensions to message + * + * @api private + */ + applyExtensions(data, fin, compress, callback) { + if (compress && data) { + if ((data.buffer || data) instanceof ArrayBuffer) { + data = getArrayBuffer(data); + } + this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); + } else { + callback(null, data); } - this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); - } else { - callback(null, data); } -}; +} module.exports = Sender; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index f8a480fd0..9d3f2b900 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -548,11 +548,10 @@ function acceptExtensions(offer) { function abortConnection(socket, code, name) { try { - var response = [ - 'HTTP/1.1 ' + code + ' ' + name, - 'Content-type: text/html' - ]; - socket.write(response.concat('', '').join('\r\n')); + var response = `HTTP/1.1 ${code} ${name}\r\n` + + `Content-type: text/html\r\n` + + `\r\n\r\n`; + socket.write(response); } catch (e) { /* ignore errors - we've aborted this connection */ } finally { From 2b8a484448a1ce6e1daa051c31b825343a6d34af Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 17 Jul 2016 08:52:02 +0200 Subject: [PATCH 027/489] [minor] Remove unnecessary assignment --- lib/Receiver.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 6a4c70638..75c467d2c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -368,10 +368,11 @@ class Receiver { } /** - * Checks payload size, disconnects socket when it exceeds maxPayload - * - * @api private - */ + * Checks payload size, disconnects socket when it exceeds maxPayload + * + * @api private + */ + maxPayloadExceeded(length) { if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; @@ -382,7 +383,6 @@ class Receiver { return false; } this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); - this.messageBuffer = []; this.cleanup(); return true; From 3cbdeff4d195299884f2fece094390db92690f3a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 18 Jul 2016 17:05:27 +0200 Subject: [PATCH 028/489] [minor] Remove some dead code --- lib/PerMessageDeflate.js | 9 +++-- lib/Receiver.js | 81 ++++++++++++++-------------------------- 2 files changed, 33 insertions(+), 57 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c61d6ef4f..515e30e9e 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -247,12 +247,13 @@ class PerMessageDeflate { } function onData(data) { - if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + if (self._maxPayload > 0) { cumulativeBufferLength += data.length; - if (cumulativeBufferLength > self._maxPayload){ - buffers = []; + if (cumulativeBufferLength > self._maxPayload) { + const err = new Error(`payload cannot exceed ${self._maxPayload} bytes`); + err.closeCode = 1009; + buffers.length = 0; cleanup(); - var err = {type:1009}; callback(err); return; } diff --git a/lib/Receiver.js b/lib/Receiver.js index 75c467d2c..b840115ed 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -357,7 +357,7 @@ class Receiver { extension.decompress(messageBuffer, fin, (err, buffer) => { if (this.dead) return; if (err) { - callback(new Error('invalid compressed data')); + callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); return; } callback(null, buffer); @@ -448,19 +448,13 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)){ - this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(firstLength)) return; opcodes['1'].getData.call(this, firstLength); } else if (firstLength == 126) { this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (this.maxPayloadExceeded(length)){ - this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(length)) return; opcodes['1'].getData.call(this, length); }); } @@ -471,11 +465,8 @@ var opcodes = { return; } var length = readUInt32BE.call(data, 4); - if (this.maxPayloadExceeded(length)){ - this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - return; - } - opcodes['1'].getData.call(this, readUInt32BE.call(data, 4)); + if (this.maxPayloadExceeded(length)) return; + opcodes['1'].getData.call(this, length); }); } }, @@ -500,22 +491,19 @@ var opcodes = { this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - if (err.type === 1009){ - return this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - } - return this.error(err.message, 1007); + this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + return; } if (buffer != null) { if (this.maxPayload == 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload) ){ + (this.currentMessageLength + buffer.length) < this.maxPayload)) { this.currentMessage.push(buffer); } else { - this.currentMessage = null; this.currentMessage = []; this.currentMessageLength = 0; - this.error(new Error('Maximum payload exceeded. maxPayload: ' + this.maxPayload), 1009); + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); return; } this.currentMessageLength += buffer.length; @@ -543,19 +531,13 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)){ - this.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(firstLength)) return; opcodes['2'].getData.call(this, firstLength); } else if (firstLength == 126) { this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (this.maxPayloadExceeded(length)){ - this.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); } @@ -566,10 +548,7 @@ var opcodes = { return; } var length = readUInt32BE.call(data, 4, true); - if (this.maxPayloadExceeded(length)){ - this.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); } @@ -590,37 +569,33 @@ var opcodes = { } }, finish: function(mask, data) { - var self = this; var packet = this.unmask(mask, data, true) || new Buffer(0); var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { + this.messageHandlers.push((callback) => { + this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - if (err.type === 1009){ - return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); - } - return self.error(err.message, 1007); + this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + return; } + if (buffer != null) { - var length = (self.currentMessageLength + buffer.length); - if (self.maxPayload == 0 || (self.maxPayload > 0 && length < self.maxPayload)) { - self.currentMessage.push(buffer); - self.currentMessageLength += length; + if (this.maxPayload == 0 || (this.maxPayload > 0 && + (this.currentMessageLength + buffer.length) < this.maxPayload)) { + this.currentMessage.push(buffer); } else { - self.currentMessage = null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded'), 1009); + this.currentMessage = []; + this.currentMessageLength = 0; + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); return; } + this.currentMessageLength += buffer.length; } - if (state.lastFragment) { - var messageBuffer = Buffer.concat(self.currentMessage); - self.currentMessage = []; - self.currentMessageLength = 0; - self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); + var messageBuffer = Buffer.concat(this.currentMessage); + this.currentMessage = []; + this.currentMessageLength = 0; + this.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); } callback(); }); From 7e401e4a4e5d4120aad7556618f124d34a5a5b76 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 20 Jul 2016 17:50:23 +0200 Subject: [PATCH 029/489] [minor] Make sure that no string is emitted as an error --- lib/Receiver.hixie.js | 16 +++----------- lib/Receiver.js | 47 ++++++++++++++++++------------------------ lib/Sender.hixie.js | 14 ++----------- lib/WebSocket.js | 14 ++++++------- test/WebSocket.test.js | 34 ------------------------------ 5 files changed, 32 insertions(+), 93 deletions(-) diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index e90422efc..8123c6e96 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -40,7 +40,6 @@ class Receiver { */ add(data) { - if (this.dead) return; var self = this; function doAdd() { if (self.state === EMPTY) { @@ -56,7 +55,7 @@ class Receiver { } else { if (data[0] !== 0x00) { - self.error('payload must start with 0x00 byte', true); + self.error(new Error('payload must start with 0x00 byte'), true); return; } data = data.slice(1); @@ -146,18 +145,9 @@ class Receiver { * @api private */ - error(reason, terminate) { - if (this.dead) return; + error(err, terminate) { this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), terminate); - } - else if (reason.constructor == Error){ - this.onerror(reason, terminate); - } - else { - this.onerror(new Error('An error occured'), terminate); - } + this.onerror(err, terminate) return this; } diff --git a/lib/Receiver.js b/lib/Receiver.js index b840115ed..2c0181f6b 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -191,12 +191,12 @@ class Receiver { processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) != 0) { - this.error('reserved fields (2, 3) must be empty', 1002); + this.error(new Error('reserved fields (2, 3) must be empty'), 1002); return; } } else { if ((data[0] & 0x70) != 0) { - this.error('reserved fields must be empty', 1002); + this.error(new Error('reserved fields must be empty'), 1002); return; } } @@ -206,24 +206,24 @@ class Receiver { var opcode = data[0] & 0xf; if (opcode === 0) { if (compressed) { - this.error('continuation frame cannot have the Per-message Compressed bits', 1002); + this.error(new Error('continuation frame cannot have the Per-message Compressed bits'), 1002); return; } // continuation frame this.state.fragmentedOperation = true; this.state.opcode = this.state.activeFragmentedOperation; if (!(this.state.opcode == 1 || this.state.opcode == 2)) { - this.error('continuation frame cannot follow current opcode', 1002); + this.error(new Error('continuation frame cannot follow current opcode'), 1002); return; } } else { if (opcode < 3 && this.state.activeFragmentedOperation != null) { - this.error('data frames after the initial data frame must have opcode 0', 1002); + this.error(new Error('data frames after the initial data frame must have opcode 0'), 1002); return; } if (opcode >= 8 && compressed) { - this.error('control frames cannot have the Per-message Compressed bits', 1002); + this.error(new Error('control frames cannot have the Per-message Compressed bits'), 1002); return; } this.state.compressed = compressed; @@ -235,7 +235,9 @@ class Receiver { else this.state.fragmentedOperation = false; } var handler = opcodes[this.state.opcode]; - if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); + if (typeof handler == 'undefined') { + this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); + } else { handler.start.call(this, data); } @@ -298,7 +300,7 @@ class Receiver { * @api private */ - unmask (mask, buf, binary) { + unmask(mask, buf, binary) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); if (binary) return buf; return buf != null ? buf.toString('utf8') : ''; @@ -310,18 +312,9 @@ class Receiver { * @api private */ - error (reason, protocolErrorCode) { - if (this.dead) return; + error(err, protocolErrorCode) { this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), protocolErrorCode); - } - else if (reason.constructor == Error){ - this.onerror(reason, protocolErrorCode); - } - else { - this.onerror(new Error('An error occured'), protocolErrorCode); - } + this.onerror(err, protocolErrorCode); return this; } @@ -382,7 +375,7 @@ class Receiver { this.currentPayloadLength = fullLength; return false; } - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); this.cleanup(); return true; @@ -461,7 +454,7 @@ var opcodes = { else if (firstLength == 127) { this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - this.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } var length = readUInt32BE.call(data, 4); @@ -491,7 +484,7 @@ var opcodes = { this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } @@ -503,7 +496,7 @@ var opcodes = { else { this.currentMessage = []; this.currentMessageLength = 0; - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); return; } this.currentMessageLength += buffer.length; @@ -513,7 +506,7 @@ var opcodes = { this.currentMessage = []; this.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { - this.error('invalid utf8 sequence', 1007); + this.error(new Error('invalid utf8 sequence'), 1007); return; } this.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); @@ -544,7 +537,7 @@ var opcodes = { else if (firstLength == 127) { this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - this.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } var length = readUInt32BE.call(data, 4, true); @@ -574,7 +567,7 @@ var opcodes = { this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } @@ -586,7 +579,7 @@ var opcodes = { else { this.currentMessage = []; this.currentMessageLength = 0; - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); return; } this.currentMessageLength += buffer.length; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 69f4ae053..18235d3a6 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -68,7 +68,7 @@ class Sender extends EventEmitter { try { this.socket.write(buffer, 'binary', cb); } catch (e) { - this.error(e.toString()); + this.emit('error', e) } } @@ -85,7 +85,7 @@ class Sender extends EventEmitter { if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); } catch (e) { - this.error(e.toString()); + this.emit('error', e); } } @@ -103,16 +103,6 @@ class Sender extends EventEmitter { * @api public */ pong(data, options) {} - - /** - * Handles an error - * - * @api private - */ - error(reason) { - this.emit('error', reason); - return this; - } } module.exports = Sender; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 06cec66b9..8daec5a25 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -716,7 +716,7 @@ function initAsClient(address, protocols, options) { var error; if (!self.emit('unexpected-response', req, res)) { - error = new Error('unexpected server response (' + res.statusCode + ')'); + error = new Error(`unexpected server response (${res.statusCode})`); req.abort(); self.emit('error', error); } @@ -735,7 +735,7 @@ function initAsClient(address, protocols, options) { var serverKey = res.headers['sec-websocket-accept']; if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { - self.emit('error', 'invalid server key'); + self.emit('error', new Error('invalid server key')); self.removeAllListeners(); socket.end(); return; @@ -754,7 +754,7 @@ function initAsClient(address, protocols, options) { } if (protError) { - self.emit('error', protError); + self.emit('error', new Error(protError)); self.removeAllListeners(); socket.end(); return; @@ -767,7 +767,7 @@ function initAsClient(address, protocols, options) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); } catch (err) { - self.emit('error', 'invalid extension parameter'); + self.emit('error', new Error('invalid extension parameter')); self.removeAllListeners(); socket.end(); return; @@ -870,10 +870,10 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { self.close(code, data); }; - self._receiver.onerror = function onerror(reason, errorCode) { + self._receiver.onerror = function onerror(error, errorCode) { // close the connection when the receiver reports a HyBi error code - self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, ''); - self.emit('error', (reason instanceof Error) ? reason : (new Error(reason))); + self.close(errorCode, ''); + self.emit('error', error); }; // finalize the client diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 6c6231732..610cf1bb7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -46,40 +46,6 @@ describe('WebSocket', function() { ws.should.be.an.instanceOf(WebSocket); done(); }); - - it('should emit an error object when the receiver throws an error string', function(done) { - - var wss = new WebSocketServer({port: ++port}, function() { - - var ws = new WebSocket('ws://localhost:' + port); - - ws.on('open', function () { - ws._receiver.error('This is an error string', 1002); - }); - - ws.on('error', function (error) { - error.should.be.an.instanceof(Error); - done(); - }); - }); - }); - - it('should emit an error object when the receiver throws an error object', function(done) { - - var wss = new WebSocketServer({port: ++port}, function() { - - var ws = new WebSocket('ws://localhost:' + port); - - ws.on('open', function () { - ws._receiver.error(new Error('This is an error object'), 1002); - }); - - ws.on('error', function (error) { - error.should.be.an.instanceof(Error); - done(); - }); - }); - }); }); describe('options', function() { From df2ebff2574222168bf71c204958216e2a0bfc39 Mon Sep 17 00:00:00 2001 From: Pavel Kriz Date: Wed, 27 Jul 2016 17:55:10 +0200 Subject: [PATCH 030/489] Allow 'backlog' option for listening socket --- lib/WebSocketServer.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 9d3f2b900..4d23bcecc 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -41,7 +41,8 @@ function WebSocketServer(options, callback) { disableHixie: false, clientTracking: true, perMessageDeflate: true, - maxPayload: 100 * 1024 * 1024 + maxPayload: 100 * 1024 * 1024, + backlog: null // use default (511 as implemented in net.js) }, options); if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { @@ -60,7 +61,12 @@ function WebSocketServer(options, callback) { res.end(body); }); this._server.allowHalfOpen = false; - this._server.listen(options.port, options.host, callback); + // maybe use a generic server.listen(options[, callback]) variant here, instead of two overloaded variants? + if (isDefinedAndNonNull(options, 'backlog')) { + this._server.listen(options.port, options.host, options.backlog, callback); + } else { + this._server.listen(options.port, options.host, callback); + } this._closeServer = function() { if (self._server) self._server.close(); From 55c68e76e05c808bad905388e3d11159ca050ab3 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Fri, 19 Aug 2016 23:24:18 +0000 Subject: [PATCH 031/489] Fix stack overflow crash --- lib/Sender.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Sender.js b/lib/Sender.js index ff54e6fe2..5caa90f25 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -256,7 +256,9 @@ class Sender extends EventEmitter { handler(function() { self.processing = false; - self.flush(); + process.nextTick(function() { + self.flush(); + }); }); } From 4d57782a76f28358d56a18a3cdba67405fd30581 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Fri, 19 Aug 2016 23:39:29 -0400 Subject: [PATCH 032/489] increased performance of conversion between Buffers and ArrayBuffers --- lib/Sender.js | 18 +++++++----------- lib/WebSocket.js | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index ff54e6fe2..bca164be7 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -142,8 +142,8 @@ class Sender extends EventEmitter { if (!Buffer.isBuffer(data)) { canModifyData = true; - if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { - data = getArrayBuffer(data); + if (data && (data.buffer || data) instanceof ArrayBuffer) { + data = getBufferFromNative(data); } else { // // If people want to send a number, this would allocate the number in @@ -268,7 +268,7 @@ class Sender extends EventEmitter { applyExtensions(data, fin, compress, callback) { if (compress && data) { if ((data.buffer || data) instanceof ArrayBuffer) { - data = getArrayBuffer(data); + data = getBufferFromNative(data); } this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); } else { @@ -279,16 +279,12 @@ class Sender extends EventEmitter { module.exports = Sender; -function getArrayBuffer(data) { +function getBufferFromNative(data) { // data is either an ArrayBuffer or ArrayBufferView. - var array = new Uint8Array(data.buffer || data), - l = data.byteLength || data.length, - o = data.byteOffset || 0, - buffer = new Buffer(l); - for (var i = 0; i < l; ++i) { - buffer[i] = array[o + i]; + if (data.buffer) { + data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); } - return buffer; + return new Buffer(data); } function getRandomMask() { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 8daec5a25..19abc5e2f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -436,7 +436,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') - data = new Uint8Array(data).buffer; + data = data.buffer.slice(data.byteOffset, data.byteOffset + data.length); listener.call(target, new MessageEvent(data, !!flags.binary, target)); } From bed724a3280027457c8919d6216d0f40f1c97198 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Sat, 20 Aug 2016 01:41:44 -0400 Subject: [PATCH 033/489] improved getBufferFromNative performance --- lib/Sender.js | 7 +++---- lib/WebSocket.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index bca164be7..62e590539 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -281,10 +281,9 @@ module.exports = Sender; function getBufferFromNative(data) { // data is either an ArrayBuffer or ArrayBufferView. - if (data.buffer) { - data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); - } - return new Buffer(data); + return !data.buffer + ? new Buffer(data) + : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength) } function getRandomMask() { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 19abc5e2f..8daec5a25 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -436,7 +436,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') - data = data.buffer.slice(data.byteOffset, data.byteOffset + data.length); + data = new Uint8Array(data).buffer; listener.call(target, new MessageEvent(data, !!flags.binary, target)); } From 9c231998a90b8364189eb21ff409c38a759de914 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Sat, 20 Aug 2016 12:43:35 -0400 Subject: [PATCH 034/489] cleaned up frameAndSend, and improved v8 optimization potential --- lib/Sender.js | 66 ++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 62e590539..f5bbc101c 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -127,24 +127,17 @@ class Sender extends EventEmitter { var canModifyData = false; if (!data) { - - try { - var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] - .concat(maskData ? [0, 0, 0, 0] : []); - this._socket.write(new Buffer(buff), 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } + var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] + .concat(maskData ? [0, 0, 0, 0] : []); + sendFramedData.call(this, new Buffer(buff), null, cb); return; } if (!Buffer.isBuffer(data)) { - canModifyData = true; if (data && (data.buffer || data) instanceof ArrayBuffer) { data = getBufferFromNative(data); } else { + canModifyData = true; // // If people want to send a number, this would allocate the number in // bytes as memory size instead of storing the number as buffer value. So @@ -194,49 +187,17 @@ class Sender extends EventEmitter { outputBuffer[dataOffset - 1] = mask[3]; if (mergeBuffers) { bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); - try { - this._socket.write(outputBuffer, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } - } - else { + } else { bufferUtil.mask(data, mask, data, 0, dataLength); - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } } } else { outputBuffer[1] = secondByte; if (mergeBuffers) { data.copy(outputBuffer, dataOffset); - try { - this._socket.write(outputBuffer, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } - } - else { - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } } } + sendFramedData.call(this, outputBuffer, mergeBuffers ? null : data, cb); } /** @@ -294,3 +255,18 @@ function getRandomMask() { ~~(Math.random() * 255) ]); } + +function sendFramedData(outputBuffer, data, cb) { + try { + if (data) { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } else { + this._socket.write(outputBuffer, 'binary', cb); + } + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } +} From becd825443537d2410c915dd55f47c02798aa213 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Sat, 20 Aug 2016 12:46:35 -0400 Subject: [PATCH 035/489] fixed benchmark syntax --- bench/sender.benchmark.js | 3 ++- bench/util.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 7c059bfec..0fc1d4f63 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -20,6 +20,7 @@ require('./util'); * Setup sender. */ +var sender; suite.on('start', function () { sender = new Sender(); sender._socket = { write: function() {} }; @@ -34,7 +35,7 @@ suite.on('cycle', function () { * Benchmarks */ -framePacket = new Buffer(200*1024); +var framePacket = new Buffer(200*1024); framePacket.fill(99); suite.add('frameAndSend, unmasked (200 kB)', function () { sender.frameAndSend(0x2, framePacket, true, false); diff --git a/bench/util.js b/bench/util.js index 38896d934..adce2c0c0 100644 --- a/bench/util.js +++ b/bench/util.js @@ -10,7 +10,7 @@ * Returns a Buffer from a "ff 00 ff"-type hex string. */ -getBufferFromHexString = function(byteStr) { +global.getBufferFromHexString = function(byteStr) { var bytes = byteStr.split(' '); var buf = new Buffer(bytes.length); for (var i = 0; i < bytes.length; ++i) { @@ -23,7 +23,7 @@ getBufferFromHexString = function(byteStr) { * Returns a hex string from a Buffer. */ -getHexStringFromBuffer = function(data) { +global.getHexStringFromBuffer = function(data) { var s = ''; for (var i = 0; i < data.length; ++i) { s += padl(data[i].toString(16), 2, '0') + ' '; @@ -35,7 +35,7 @@ getHexStringFromBuffer = function(data) { * Splits a buffer in two parts. */ -splitBuffer = function(buffer) { +global.splitBuffer = function(buffer) { var b1 = new Buffer(Math.ceil(buffer.length / 2)); buffer.copy(b1, 0, 0, b1.length); var b2 = new Buffer(Math.floor(buffer.length / 2)); @@ -47,7 +47,7 @@ splitBuffer = function(buffer) { * Performs hybi07+ type masking on a hex string or buffer. */ -mask = function(buf, maskString) { +global.mask = function(buf, maskString) { if (typeof buf == 'string') buf = new Buffer(buf); var mask = getBufferFromHexString(maskString || '34 83 a8 68'); for (var i = 0; i < buf.length; ++i) { @@ -60,7 +60,7 @@ mask = function(buf, maskString) { * Returns a hex string representing the length of a message */ -getHybiLengthAsHexString = function(len, masked) { +global.getHybiLengthAsHexString = function(len, masked) { if (len < 126) { var buf = new Buffer(1); buf[0] = (masked ? 0x80 : 0) | len; @@ -82,7 +82,7 @@ getHybiLengthAsHexString = function(len, masked) { * Unpacks a Buffer into a number. */ -unpack = function(buffer) { +global.unpack = function(buffer) { var n = 0; for (var i = 0; i < buffer.length; ++i) { n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; @@ -94,7 +94,7 @@ unpack = function(buffer) { * Returns a hex string, representing a specific byte count 'length', from a number. */ -pack = function(length, number) { +global.pack = function(length, number) { return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); } @@ -102,6 +102,6 @@ pack = function(length, number) { * Left pads the string 's' to a total length of 'n' with char 'c'. */ -padl = function(s, n, c) { +global.padl = function(s, n, c) { return new Array(1 + n - s.length).join(c) + s; } From 584842a991fad1745ffc82a58b07c14d177c40d6 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Thu, 15 Sep 2016 09:55:14 +0000 Subject: [PATCH 036/489] Adding test case to reproduce crash --- test/Sender.test.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/Sender.test.js b/test/Sender.test.js index 8b5ccc06b..42b23ff7d 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -60,8 +60,26 @@ describe('Sender', function() { }); sender.send('hi', { compress: true }); }); - }); + + it('Should be able to handle many send calls while processing without crashing on flush', function(done) { + var messageCount = 0; + var maxMessages = 5000; + var sender = new Sender({ + write: function(data) { + messageCount++; + if (messageCount > maxMessages) return done(); + } + }); + for (var i = 0; i < maxMessages; i++) { + sender.processing = true; + sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + } + sender.processing = false; + sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + }); + }); + describe('#close', function() { it('should consume all data before closing', function(done) { var perMessageDeflate = new PerMessageDeflate(); From a45c991afd9e0624487773315f3010d69f07a582 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Thu, 15 Sep 2016 16:55:25 -0400 Subject: [PATCH 037/489] Added checkServerIdentity option to WebSocket (#701) * Added checkServerIdentity option to WebSocket --- lib/WebSocket.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 8daec5a25..a4a2e4040 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -583,6 +583,7 @@ function initAsClient(address, protocols, options) { ca: null, ciphers: null, rejectUnauthorized: null, + checkServerIdentity: null, perMessageDeflate: true, localAddress: null }, options); @@ -668,7 +669,8 @@ function initAsClient(address, protocols, options) { || isDefinedAndNonNull(options, 'cert') || isDefinedAndNonNull(options, 'ca') || isDefinedAndNonNull(options, 'ciphers') - || isDefinedAndNonNull(options, 'rejectUnauthorized')) { + || isDefinedAndNonNull(options, 'rejectUnauthorized') + || isDefinedAndNonNull(options, 'checkServerIdentity')) { if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; @@ -678,6 +680,8 @@ function initAsClient(address, protocols, options) { if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; if (isDefinedAndNonNull(options, 'rejectUnauthorized')) requestOptions.rejectUnauthorized = options.rejectUnauthorized; + if (isDefinedAndNonNull(options, 'checkServerIdentity')) + requestOptions.checkServerIdentity = options.checkServerIdentity; if (!agent) { // global agent ignores client side certificates From acce83e6c65f1b14df16da4f6d67232ff0be73a6 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 17 Sep 2016 18:46:44 +0200 Subject: [PATCH 038/489] Avoid using Buffer.concat() on single part messages and provide length otherwise (#826) --- lib/Receiver.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 2c0181f6b..2b9db15cb 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -502,7 +502,9 @@ var opcodes = { this.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = Buffer.concat(this.currentMessage); + var messageBuffer = this.currentMessage.length === 1 ? + this.currentMessage[0] : + Buffer.concat(this.currentMessage, this.currentMessageLength); this.currentMessage = []; this.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { @@ -585,7 +587,9 @@ var opcodes = { this.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = Buffer.concat(this.currentMessage); + var messageBuffer = this.currentMessage.length === 1 ? + this.currentMessage[0] : + Buffer.concat(this.currentMessage, this.currentMessageLength); this.currentMessage = []; this.currentMessageLength = 0; this.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); From e0776e5c0198784cd3c1d27cf7164baba0c3b74f Mon Sep 17 00:00:00 2001 From: Ed S Date: Wed, 21 Sep 2016 13:14:09 -0700 Subject: [PATCH 039/489] Correct spelling of 'occurred' (#834) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 25d08fec8..401688806 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -68,7 +68,7 @@ If a property is empty then either an offered configuration or a default value i ### server.close([callback]) -Close the server and terminate all clients, calls callback when done with an error if one occured. +Close the server and terminate all clients, calls callback when done with an error if one occurred. ### server.handleUpgrade(request, socket, upgradeHead, callback) From fc4edd4f5d946c7dccb3fe49adadcca421c7cdb5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 23 Sep 2016 19:11:51 +0200 Subject: [PATCH 040/489] [benchmark] Fix speed benchmark (#839) --- bench/speed.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index f8ae9a49f..2a50ec79e 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -28,7 +28,12 @@ function generateRandomData(size) { } if (cluster.isMaster) { - var wss = new WebSocketServer({port: 8181}, function() { + var wss = new WebSocketServer({ + perMessageDeflate: false, + clientTracking: false, + maxPayload: Infinity, + port: 8181 + }, function() { cluster.fork(); }); wss.on('connection', function(ws) { @@ -37,7 +42,7 @@ if (cluster.isMaster) { }); ws.on('close', function() {}); }); - cluster.on('death', function(worker) { + cluster.on('exit', function(worker) { wss.close(); }); } From a2e7338b8311f8697a1375a86c3c5a06185fb259 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 25 Sep 2016 20:17:02 +0200 Subject: [PATCH 041/489] [benchmark] Fix and clean up parser and sender benchmarks --- bench/parser.benchmark.js | 132 ++++++++++---------------------------- bench/sender.benchmark.js | 64 ++++-------------- bench/util.js | 100 ++++++++--------------------- package.json | 2 +- 4 files changed, 74 insertions(+), 224 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index b912b1953..a8d245833 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -6,112 +6,50 @@ 'use strict'; -/** - * Benchmark dependencies. - */ - -var benchmark = require('benchmark') - , Receiver = require('../').Receiver - , suite = new benchmark.Suite('Receiver'); -require('tinycolor'); -require('./util'); +const benchmark = require('benchmark') -/** - * Setup receiver. - */ +const Receiver = require('../').Receiver +const util = require('./util'); -suite.on('start', function () { - receiver = new Receiver(); -}); +function createBinaryPacket(length) { + const message = Buffer.alloc(length); -suite.on('cycle', function () { - receiver = new Receiver(); -}); - -/** - * Benchmarks. - */ - -var pingMessage = 'Hello' - , pingPacket1 = getBufferFromHexString('89 ' + (pack(2, 0x80 | pingMessage.length)) + - ' 34 83 a8 68 '+ getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'))); -suite.add('ping message', function () { - receiver.add(pingPacket1); -}); + for (var i = 0; i < length; ++i) message[i] = i % 10; -var pingPacket2 = getBufferFromHexString('89 00') -suite.add('ping with no data', function () { - receiver.add(pingPacket2); -}); + return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + '3483a868' + + util.mask(message, '3483a868').toString('hex'), 'hex'); +} -var closePacket = getBufferFromHexString('88 00'); -suite.add('close message', function () { +const pingMessage = 'Hello' +const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + + '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); +const pingPacket2 = Buffer.from('8900', 'hex'); +const closePacket = Buffer.from('8800', 'hex'); +const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex'); +const binaryDataPacket = createBinaryPacket(125); +const binaryDataPacket2 = createBinaryPacket(65535); +const binaryDataPacket3 = createBinaryPacket(200 * 1024) + +var receiver = new Receiver({}, Infinity); +const suite = new benchmark.Suite(); + +suite.add('ping message', () => receiver.add(pingPacket1)); +suite.add('ping with no data', () => receiver.add(pingPacket2)); +suite.add('close message', () => { receiver.add(closePacket); receiver.endPacket(); +}) +suite.add('masked text message', () => receiver.add(maskedTextPacket)); +suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); +suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); +suite.add('binary data (200 kB)', () => receiver.add(binaryDataPacket3)); +suite.on('cycle', (e) => { + console.log(e.target.toString()); + receiver = new Receiver(); }); -var maskedTextPacket = getBufferFromHexString('81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5'); -suite.add('masked text message', function () { - receiver.add(maskedTextPacket); -}); - -binaryDataPacket = (function() { - var length = 125 - , message = new Buffer(length) - for (var i = 0; i < length; ++i) message[i] = i % 10; - return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' - + getHexStringFromBuffer(mask(message), '34 83 a8 68')); -})(); -suite.add('binary data (125 bytes)', function () { - try { - receiver.add(binaryDataPacket); - - } - catch(e) {console.log(e)} -}); - -binaryDataPacket2 = (function() { - var length = 65535 - , message = new Buffer(length) - for (var i = 0; i < length; ++i) message[i] = i % 10; - return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' - + getHexStringFromBuffer(mask(message), '34 83 a8 68')); -})(); -suite.add('binary data (65535 bytes)', function () { - receiver.add(binaryDataPacket2); -}); - -binaryDataPacket3 = (function() { - var length = 200*1024 - , message = new Buffer(length) - for (var i = 0; i < length; ++i) message[i] = i % 10; - return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' - + getHexStringFromBuffer(mask(message), '34 83 a8 68')); -})(); -suite.add('binary data (200 kB)', function () { - receiver.add(binaryDataPacket3); -}); - -/** - * Output progress. - */ - -suite.on('cycle', function (bench, details) { - console.log('\n ' + suite.name.grey, details.name.white.bold); - console.log(' ' + [ - details.hz.toFixed(2).cyan + ' ops/sec'.grey - , details.count.toString().white + ' times executed'.grey - , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , - ].join(', '.grey)); -}); - -/** - * Run/export benchmarks. - */ - -if (!module.parent) { - suite.run(); +if (require.main === module) { + suite.run({ async: true }); } else { module.exports = suite; } diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 0fc1d4f63..7e9f28728 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -6,64 +6,26 @@ 'use strict'; -/** - * Benchmark dependencies. - */ +const benchmark = require('benchmark') -var benchmark = require('benchmark') - , Sender = require('../').Sender - , suite = new benchmark.Suite('Sender'); -require('tinycolor'); -require('./util'); +const Sender = require('../').Sender -/** - * Setup sender. - */ +const framePacket = Buffer.alloc(200 * 1024).fill(99); -var sender; -suite.on('start', function () { - sender = new Sender(); - sender._socket = { write: function() {} }; -}); +const suite = new benchmark.Suite(); +var sender = new Sender(); +sender._socket = { write() {} }; -suite.on('cycle', function () { +suite.add('frameAndSend, unmasked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, false)); +suite.add('frameAndSend, masked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, true)); +suite.on('cycle', (e) => { + console.log(e.target.toString()); sender = new Sender(); - sender._socket = { write: function() {} }; -}); - -/** - * Benchmarks - */ - -var framePacket = new Buffer(200*1024); -framePacket.fill(99); -suite.add('frameAndSend, unmasked (200 kB)', function () { - sender.frameAndSend(0x2, framePacket, true, false); + sender._socket = { write() {} }; }); -suite.add('frameAndSend, masked (200 kB)', function () { - sender.frameAndSend(0x2, framePacket, true, true); -}); - -/** - * Output progress. - */ - -suite.on('cycle', function (bench, details) { - console.log('\n ' + suite.name.grey, details.name.white.bold); - console.log(' ' + [ - details.hz.toFixed(2).cyan + ' ops/sec'.grey - , details.count.toString().white + ' times executed'.grey - , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , - ].join(', '.grey)); -}); - -/** - * Run/export benchmarks. - */ -if (!module.parent) { - suite.run(); +if (require.main === module) { + suite.run({ async: true }); } else { module.exports = suite; } diff --git a/bench/util.js b/bench/util.js index adce2c0c0..3b94c63ff 100644 --- a/bench/util.js +++ b/bench/util.js @@ -7,101 +7,51 @@ 'use strict'; /** - * Returns a Buffer from a "ff 00 ff"-type hex string. + * Performs hybi07+ type masking on a hex string or buffer. */ +function mask(buf, maskString) { + const _mask = Buffer.from(maskString || '3483a868', 'hex'); + + if (typeof buf === 'string') buf = Buffer.from(buf); -global.getBufferFromHexString = function(byteStr) { - var bytes = byteStr.split(' '); - var buf = new Buffer(bytes.length); - for (var i = 0; i < bytes.length; ++i) { - buf[i] = parseInt(bytes[i], 16); + for (var i = 0; i < buf.length; ++i) { + buf[i] ^= _mask[i % 4]; } + return buf; } /** - * Returns a hex string from a Buffer. + * Left pads the string `s` to a total length of `n` with char `c`. */ - -global.getHexStringFromBuffer = function(data) { - var s = ''; - for (var i = 0; i < data.length; ++i) { - s += padl(data[i].toString(16), 2, '0') + ' '; - } - return s.trim(); +function padl(s, n, c) { + return c.repeat(n - s.length) + s; } /** - * Splits a buffer in two parts. + * Returns a hex string, representing a specific byte count `length`, from a number. */ - -global.splitBuffer = function(buffer) { - var b1 = new Buffer(Math.ceil(buffer.length / 2)); - buffer.copy(b1, 0, 0, b1.length); - var b2 = new Buffer(Math.floor(buffer.length / 2)); - buffer.copy(b2, 0, b1.length, b1.length + b2.length); - return [b1, b2]; +function pack(length, number) { + return padl(number.toString(16), length, '0'); } /** - * Performs hybi07+ type masking on a hex string or buffer. + * Returns a hex string representing the length of a message. */ +function getHybiLengthAsHexString(len, masked) { + var s; -global.mask = function(buf, maskString) { - if (typeof buf == 'string') buf = new Buffer(buf); - var mask = getBufferFromHexString(maskString || '34 83 a8 68'); - for (var i = 0; i < buf.length; ++i) { - buf[i] ^= mask[i % 4]; - } - return buf; -} + masked = masked ? 0x80 : 0; -/** - * Returns a hex string representing the length of a message - */ - -global.getHybiLengthAsHexString = function(len, masked) { if (len < 126) { - var buf = new Buffer(1); - buf[0] = (masked ? 0x80 : 0) | len; - } - else if (len < 65536) { - var buf = new Buffer(3); - buf[0] = (masked ? 0x80 : 0) | 126; - getBufferFromHexString(pack(4, len)).copy(buf, 1); + s = pack(2, masked | len); + } else if (len < 65536) { + s = pack(2, masked | 126) + pack(4, len); + } else { + s = pack(2, masked | 127) + pack(16, len); } - else { - var buf = new Buffer(9); - buf[0] = (masked ? 0x80 : 0) | 127; - getBufferFromHexString(pack(16, len)).copy(buf, 1); - } - return getHexStringFromBuffer(buf); -} - -/** - * Unpacks a Buffer into a number. - */ -global.unpack = function(buffer) { - var n = 0; - for (var i = 0; i < buffer.length; ++i) { - n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; - } - return n; + return s; } -/** - * Returns a hex string, representing a specific byte count 'length', from a number. - */ - -global.pack = function(length, number) { - return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); -} - -/** - * Left pads the string 's' to a total length of 'n' with char 'c'. - */ - -global.padl = function(s, n, c) { - return new Array(1 + n - s.length).join(c) + s; -} +module.exports = { getHybiLengthAsHexString, mask, pack }; diff --git a/package.json b/package.json index 68d9f52e0..80c85195b 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "ansi": "0.3.x", - "benchmark": "0.3.x", + "benchmark": "2.1.x", "bufferutil": "1.2.x", "eslint": "^2.12.0", "expect.js": "0.3.x", From d2175a3a0c1db3aba22ff147906f4fcf1b295e4c Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 26 Sep 2016 03:11:24 +0200 Subject: [PATCH 042/489] Replace bufferSize with highWatermark. (#842) * Replace bufferSize with highWatermark. bufferSize was removed from node in 0.9.12 so this test was ineffective * Set stream encoding in options argument --- test/WebSocket.test.js | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 610cf1bb7..f1c48a0af 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -928,8 +928,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true}, function(error) { assert.equal(null, error); callbackFired = true; @@ -953,9 +952,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, {binary: false}, function(error) { assert.equal(null, error); callbackFired = true; @@ -978,9 +975,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.send('foobar'); ws.send('baz'); @@ -1011,9 +1006,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); var i = 0; ws.stream(function(error, send) { @@ -1044,9 +1037,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.ping('foobar'); }); @@ -1075,9 +1066,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.pong('foobar'); }); @@ -1106,9 +1095,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.close(1000, 'foobar'); }); @@ -1397,9 +1384,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port); var errorGiven = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, function(error) { errorGiven = error != null; }); @@ -2120,8 +2105,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); var callbackFired = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true, compress: true}, function(error) { assert.equal(null, error); callbackFired = true; From 250b1d5e52af8f23987cb8a0cd40262a83d2008c Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 26 Sep 2016 03:13:07 +0200 Subject: [PATCH 043/489] Cleanups (#841) * Remove third argument to unmask as it is always true. * Remove readUInt16BE and readUInt32BE and use the builtin functions. --- lib/Receiver.js | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 2b9db15cb..ccf37ae37 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -300,10 +300,9 @@ class Receiver { * @api private */ - unmask(mask, buf, binary) { + unmask(mask, buf) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); - if (binary) return buf; - return buf != null ? buf.toString('utf8') : ''; + return buf; } /** @@ -390,18 +389,6 @@ module.exports = Receiver; * Buffer utilities */ -function readUInt16BE(start) { - return (this[start] << 8) + - this[start + 1]; -} - -function readUInt32BE(start) { - return (this[start] << 24) + - (this[start + 1] << 16) + - (this[start + 2] << 8) + - this[start + 3]; -} - function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { @@ -446,18 +433,18 @@ var opcodes = { } else if (firstLength == 126) { this.expectHeader(2, (data) => { - var length = readUInt16BE.call(data, 0); + var length = data.readUInt16BE(0, true); if (this.maxPayloadExceeded(length)) return; opcodes['1'].getData.call(this, length); }); } else if (firstLength == 127) { this.expectHeader(8, (data) => { - if (readUInt32BE.call(data, 0) != 0) { + if (data.readUInt32BE(0, true) != 0) { this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = readUInt32BE.call(data, 4); + var length = data.readUInt32BE(4, true); if (this.maxPayloadExceeded(length)) return; opcodes['1'].getData.call(this, length); }); @@ -479,7 +466,7 @@ var opcodes = { } }, finish: function(mask, data) { - var packet = this.unmask(mask, data, true) || new Buffer(0); + var packet = this.unmask(mask, data) || new Buffer(0); var state = clone(this.state); this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { @@ -531,18 +518,18 @@ var opcodes = { } else if (firstLength == 126) { this.expectHeader(2, (data) => { - var length = readUInt16BE.call(data, 0); + var length = data.readUInt16BE(0, true); if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); } else if (firstLength == 127) { this.expectHeader(8, (data) => { - if (readUInt32BE.call(data, 0) != 0) { + if (data.readUInt32BE(0, true) != 0) { this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = readUInt32BE.call(data, 4, true); + var length = data.readUInt32BE(4, true); if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); @@ -564,7 +551,7 @@ var opcodes = { } }, finish: function(mask, data) { - var packet = this.unmask(mask, data, true) || new Buffer(0); + var packet = this.unmask(mask, data) || new Buffer(0); var state = clone(this.state); this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { @@ -637,7 +624,7 @@ var opcodes = { }, finish: function(mask, data) { var self = this; - data = self.unmask(mask, data, true); + data = self.unmask(mask, data); var state = clone(this.state); this.messageHandlers.push(function() { @@ -645,7 +632,7 @@ var opcodes = { self.error('close packets with data must be at least two bytes long', 1002); return; } - var code = data && data.length > 1 ? readUInt16BE.call(data, 0) : 1000; + var code = data && data.length > 1 ? data.readUInt16BE(0, true) : 1000; if (!ErrorCodes.isValidErrorCode(code)) { self.error('invalid error code', 1002); return; @@ -701,7 +688,7 @@ var opcodes = { }, finish: function(mask, data) { var self = this; - data = this.unmask(mask, data, true); + data = this.unmask(mask, data); var state = clone(this.state); this.messageHandlers.push(function(callback) { self.onping(data, {masked: state.masked, binary: true}); @@ -745,7 +732,7 @@ var opcodes = { } }, finish: function(mask, data) { - data = this.unmask(mask, data, true); + data = this.unmask(mask, data); var state = clone(this.state); this.messageHandlers.push((callback) => { this.onpong(data, {masked: state.masked, binary: true}); From cad49db8a825d99c68a9330be86e0aa590ecf53e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 26 Sep 2016 19:06:33 +0200 Subject: [PATCH 044/489] Avoid using `Function.prototype.call()` (#846) * [minor] Avoid using `Function.prototype.call()` * [codestyle] Replace `var` with `const` in lib/Receiver.js --- lib/Receiver.js | 400 +++++++++++++++++++++--------------------------- 1 file changed, 178 insertions(+), 222 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index ccf37ae37..518f0cfe3 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -80,23 +80,23 @@ class Receiver { add(data) { if (this.dead) return; - var dataLength = data.length; + const dataLength = data.length; if (dataLength == 0) return; if (this.expectBuffer == null) { this.overflow.push(data); return; } - var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); + const toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); fastCopy(toRead, data, this.expectBuffer, this.expectOffset); this.expectOffset += toRead; if (toRead < dataLength) { this.overflow.push(data.slice(toRead)); } while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { - var bufferForHandler = this.expectBuffer; + const bufferForHandler = this.expectBuffer; this.expectBuffer = null; this.expectOffset = 0; - this.expectHandler.call(this, bufferForHandler); + this.expectHandler(bufferForHandler); } } @@ -140,8 +140,8 @@ class Receiver { var toRead = length; while (toRead > 0 && this.overflow.length > 0) { var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); var read = Math.min(fromOverflow.length, toRead); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); this.expectOffset += read; toRead -= read; @@ -164,8 +164,8 @@ class Receiver { var toRead = length; while (toRead > 0 && this.overflow.length > 0) { var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); var read = Math.min(fromOverflow.length, toRead); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); this.expectOffset += read; toRead -= read; @@ -188,7 +188,7 @@ class Receiver { * @api private */ - processPacket (data) { + processPacket(data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) != 0) { this.error(new Error('reserved fields (2, 3) must be empty'), 1002); @@ -202,8 +202,8 @@ class Receiver { } this.state.lastFragment = (data[0] & 0x80) == 0x80; this.state.masked = (data[1] & 0x80) == 0x80; - var compressed = (data[0] & 0x40) == 0x40; - var opcode = data[0] & 0xf; + const compressed = (data[0] & 0x40) == 0x40; + const opcode = data[0] & 0xf; if (opcode === 0) { if (compressed) { this.error(new Error('continuation frame cannot have the Per-message Compressed bits'), 1002); @@ -216,8 +216,7 @@ class Receiver { this.error(new Error('continuation frame cannot follow current opcode'), 1002); return; } - } - else { + } else { if (opcode < 3 && this.state.activeFragmentedOperation != null) { this.error(new Error('data frames after the initial data frame must have opcode 0'), 1002); return; @@ -231,15 +230,15 @@ class Receiver { if (this.state.lastFragment === false) { this.state.fragmentedOperation = true; this.state.activeFragmentedOperation = opcode; + } else { + this.state.fragmentedOperation = false; } - else this.state.fragmentedOperation = false; } - var handler = opcodes[this.state.opcode]; + const handler = opcodes[this.state.opcode]; if (typeof handler == 'undefined') { this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); - } - else { - handler.start.call(this, data); + } else { + handler.start(this, data); } } @@ -345,7 +344,7 @@ class Receiver { applyExtensions(messageBuffer, fin, compressed, callback) { if (compressed) { - var extension = this.extensions[PerMessageDeflate.extensionName]; + const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(messageBuffer, fin, (err, buffer) => { if (this.dead) return; if (err) { @@ -369,7 +368,7 @@ class Receiver { if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; } - var fullLength = this.currentPayloadLength + length; + const fullLength = this.currentPayloadLength + length; if (fullLength < this.maxPayload) { this.currentPayloadLength = fullLength; return false; @@ -383,8 +382,6 @@ class Receiver { module.exports = Receiver; - - /** * Buffer utilities */ @@ -421,325 +418,284 @@ function clone(obj) { * Opcode handlers */ -var opcodes = { +const opcodes = { // text '1': { - start: function(data) { + start: (receiver, data) => { // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)) return; - opcodes['1'].getData.call(this, firstLength); - } - else if (firstLength == 126) { - this.expectHeader(2, (data) => { - var length = data.readUInt16BE(0, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['1'].getData.call(this, length); + if (receiver.maxPayloadExceeded(firstLength)) return; + opcodes['1'].getData(receiver, firstLength); + } else if (firstLength == 126) { + receiver.expectHeader(2, (data) => { + const length = data.readUInt16BE(0, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['1'].getData(receiver, length); }); - } - else if (firstLength == 127) { - this.expectHeader(8, (data) => { + } else if (firstLength == 127) { + receiver.expectHeader(8, (data) => { if (data.readUInt32BE(0, true) != 0) { - this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); + receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = data.readUInt32BE(4, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['1'].getData.call(this, length); + const length = data.readUInt32BE(4, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['1'].getData(receiver, length); }); } }, - getData: function(length) { - if (this.state.masked) { - this.expectHeader(4, (data) => { - var mask = data; - this.expectData(length, (data) => { - opcodes['1'].finish.call(this, mask, data); - }); - }); - } - else { - this.expectData(length, (data) => { - opcodes['1'].finish.call(this, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['1'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['1'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var packet = this.unmask(mask, data) || new Buffer(0); - var state = clone(this.state); - this.messageHandlers.push((callback) => { - this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data) || new Buffer(0); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err, err.closeCode === 1009 ? 1009 : 1007); + receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } if (buffer != null) { - if (this.maxPayload == 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload)) { - this.currentMessage.push(buffer); - } - else { - this.currentMessage = []; - this.currentMessageLength = 0; - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); + if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { + receiver.currentMessage.push(buffer); + } else { + receiver.currentMessage = []; + receiver.currentMessageLength = 0; + receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); return; } - this.currentMessageLength += buffer.length; + receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = this.currentMessage.length === 1 ? - this.currentMessage[0] : - Buffer.concat(this.currentMessage, this.currentMessageLength); - this.currentMessage = []; - this.currentMessageLength = 0; + const messageBuffer = receiver.currentMessage.length === 1 ? + receiver.currentMessage[0] : + Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + receiver.currentMessage = []; + receiver.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { - this.error(new Error('invalid utf8 sequence'), 1007); + receiver.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); + receiver.ontext(messageBuffer.toString('utf8'), { + masked: state.masked, + buffer: messageBuffer + }); } callback(); }); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } }, // binary '2': { - start: function(data) { + start: (receiver, data) => { // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)) return; - opcodes['2'].getData.call(this, firstLength); - } - else if (firstLength == 126) { - this.expectHeader(2, (data) => { - var length = data.readUInt16BE(0, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['2'].getData.call(this, length); + if (receiver.maxPayloadExceeded(firstLength)) return; + opcodes['2'].getData(receiver, firstLength); + } else if (firstLength == 126) { + receiver.expectHeader(2, (data) => { + const length = data.readUInt16BE(0, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['2'].getData(receiver, length); }); - } - else if (firstLength == 127) { - this.expectHeader(8, (data) => { + } else if (firstLength == 127) { + receiver.expectHeader(8, (data) => { if (data.readUInt32BE(0, true) != 0) { - this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); + receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = data.readUInt32BE(4, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['2'].getData.call(this, length); + const length = data.readUInt32BE(4, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['2'].getData(receiver, length); }); } }, - getData: function(length) { - if (this.state.masked) { - this.expectHeader(4, (data) => { - var mask = data; - this.expectData(length, (data) => { - opcodes['2'].finish.call(this, mask, data); - }); - }); - } - else { - this.expectData(length, (data) => { - opcodes['2'].finish.call(this, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['2'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['2'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var packet = this.unmask(mask, data) || new Buffer(0); - var state = clone(this.state); - this.messageHandlers.push((callback) => { - this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data) || new Buffer(0); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err, err.closeCode === 1009 ? 1009 : 1007); + receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } if (buffer != null) { - if (this.maxPayload == 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload)) { - this.currentMessage.push(buffer); - } - else { - this.currentMessage = []; - this.currentMessageLength = 0; - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); + if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { + receiver.currentMessage.push(buffer); + } else { + receiver.currentMessage = []; + receiver.currentMessageLength = 0; + receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); return; } - this.currentMessageLength += buffer.length; + receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = this.currentMessage.length === 1 ? - this.currentMessage[0] : - Buffer.concat(this.currentMessage, this.currentMessageLength); - this.currentMessage = []; - this.currentMessageLength = 0; - this.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); + const messageBuffer = receiver.currentMessage.length === 1 ? + receiver.currentMessage[0] : + Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + receiver.currentMessage = []; + receiver.currentMessageLength = 0; + receiver.onbinary(messageBuffer, { + masked: state.masked, + buffer: messageBuffer + }); } callback(); }); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } }, // close '8': { - start: function(data) { - var self = this; - if (self.state.lastFragment == false) { - self.error('fragmented close is not supported', 1002); + start: (receiver, data) => { + if (receiver.state.lastFragment == false) { + receiver.error('fragmented close is not supported', 1002); return; } // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - opcodes['8'].getData.call(self, firstLength); - } - else { - self.error('control frames cannot have more than 125 bytes of data', 1002); + opcodes['8'].getData(receiver, firstLength); + } else { + receiver.error('control frames cannot have more than 125 bytes of data', 1002); } }, - getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { - var mask = data; - self.expectData(length, function(data) { - opcodes['8'].finish.call(self, mask, data); - }); - }); - } - else { - self.expectData(length, function(data) { - opcodes['8'].finish.call(self, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['8'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['8'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var self = this; - data = self.unmask(mask, data); - - var state = clone(this.state); - this.messageHandlers.push(function() { - if (data && data.length == 1) { - self.error('close packets with data must be at least two bytes long', 1002); + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data); + const state = clone(receiver.state); + receiver.messageHandlers.push(() => { + if (packet && packet.length == 1) { + receiver.error('close packets with data must be at least two bytes long', 1002); return; } - var code = data && data.length > 1 ? data.readUInt16BE(0, true) : 1000; + const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; if (!ErrorCodes.isValidErrorCode(code)) { - self.error('invalid error code', 1002); + receiver.error('invalid error code', 1002); return; } var message = ''; - if (data && data.length > 2) { - var messageBuffer = data.slice(2); + if (packet && packet.length > 2) { + const messageBuffer = packet.slice(2); if (!Validation.isValidUTF8(messageBuffer)) { - self.error('invalid utf8 sequence', 1007); + receiver.error('invalid utf8 sequence', 1007); return; } message = messageBuffer.toString('utf8'); } - self.onclose(code, message, {masked: state.masked}); - self.reset(); + receiver.onclose(code, message, { masked: state.masked }); + receiver.reset(); }); - this.flush(); + receiver.flush(); } }, // ping '9': { - start: function(data) { - var self = this; - if (self.state.lastFragment == false) { - self.error('fragmented ping is not supported', 1002); + start: (receiver, data) => { + if (receiver.state.lastFragment == false) { + receiver.error('fragmented ping is not supported', 1002); return; } // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - opcodes['9'].getData.call(self, firstLength); - } - else { - self.error('control frames cannot have more than 125 bytes of data', 1002); + opcodes['9'].getData(receiver, firstLength); + } else { + receiver.error('control frames cannot have more than 125 bytes of data', 1002); } }, - getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { - var mask = data; - self.expectData(length, function(data) { - opcodes['9'].finish.call(self, mask, data); - }); - }); - } - else { - self.expectData(length, function(data) { - opcodes['9'].finish.call(self, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['9'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['9'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var self = this; - data = this.unmask(mask, data); - var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.onping(data, {masked: state.masked, binary: true}); + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.onping(packet, { masked: state.masked, binary: true }); callback(); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } }, // pong '10': { - start: function(data) { - var self = this; - if (self.state.lastFragment == false) { - self.error('fragmented pong is not supported', 1002); + start: (receiver, data) => { + if (receiver.state.lastFragment == false) { + receiver.error('fragmented pong is not supported', 1002); return; } // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - opcodes['10'].getData.call(self, firstLength); - } - else { - self.error('control frames cannot have more than 125 bytes of data', 1002); + opcodes['10'].getData(receiver, firstLength); + } else { + receiver.error('control frames cannot have more than 125 bytes of data', 1002); } }, - getData: function(length) { - if (this.state.masked) { - this.expectHeader(4, (data) => { - var mask = data; - this.expectData(length, (data) => { - opcodes['10'].finish.call(this, mask, data); - }); - }); - } - else { - this.expectData(length, (data) => { - opcodes['10'].finish.call(this, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['10'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['10'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - data = this.unmask(mask, data); - var state = clone(this.state); - this.messageHandlers.push((callback) => { - this.onpong(data, {masked: state.masked, binary: true}); + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.onpong(packet, { masked: state.masked, binary: true }); callback(); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } } } From ded20731dd1c9e5df5a31787a66c561cbf6c0b9f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 27 Sep 2016 17:11:10 +0200 Subject: [PATCH 045/489] [benchmark] Clean up speed benchmark --- bench/speed.js | 146 ++++++++++++++++++++++--------------------------- package.json | 2 - 2 files changed, 66 insertions(+), 82 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index 2a50ec79e..8f23e36b5 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -1,112 +1,98 @@ 'use strict'; -var cluster = require('cluster') - , WebSocket = require('../') - , WebSocketServer = WebSocket.Server - , crypto = require('crypto') - , util = require('util') - , ansi = require('ansi'); -require('tinycolor'); +const cluster = require('cluster'); + +const ws = require('../'); + +const port = 8181; function roundPrec(num, prec) { - var mul = Math.pow(10, prec); + const mul = Math.pow(10, prec); return Math.round(num * mul) / mul; } function humanSize(bytes) { - if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MB'; - if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' kB'; + if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; + if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; return roundPrec(bytes, 2) + ' B'; } function generateRandomData(size) { - var buffer = new Buffer(size); + const buffer = Buffer.alloc(size); for (var i = 0; i < size; ++i) { buffer[i] = ~~(Math.random() * 127); } return buffer; } -if (cluster.isMaster) { - var wss = new WebSocketServer({ - perMessageDeflate: false, - clientTracking: false, - maxPayload: Infinity, - port: 8181 - }, function() { - cluster.fork(); +function runConfig(useBinary, roundtrips, size, randomBytes, cb) { + const data = randomBytes.slice(0, size); + const client = new ws(`ws://localhost:${port}`); + var roundtrip = 0; + var time; + + client.on('error', (err) => { + console.error(err.stack); + cluster.worker.kill(); }); - wss.on('connection', function(ws) { - ws.on('message', function(data, flags) { - ws.send(data, {binary: flags&&flags.binary}); - }); - ws.on('close', function() {}); + client.on('open', () => { + time = process.hrtime(); + client.send(data, { binary: useBinary }); }); - cluster.on('exit', function(worker) { - wss.close(); + client.on('message', () => { + if (++roundtrip !== roundtrips) return client.send(data, { binary: useBinary }); + + var elapsed = process.hrtime(time); + elapsed = elapsed[0] * 1e9 + elapsed[1]; + + console.log( + '%d roundtrips of %s %s data:\t%ss\t%s', + roundtrips, + humanSize(size), + useBinary ? 'binary' : 'text', + roundPrec(elapsed / 1e9, 1), + humanSize(size * roundtrips / elapsed * 1e9) + '/s' + ); + + client.close(); + cb(); }); } -else { - var cursor = ansi(process.stdout); - var configs = [ +if (cluster.isMaster) { + const wss = new ws.Server({ + maxPayload: 600 * 1024 * 1024, + perMessageDeflate: false, + clientTracking: false, + port + }, () => cluster.fork()); + + wss.on('connection', (ws) => { + ws.on('message', (data, flags) => ws.send(data, { binary: flags.binary || false })); + }); + + cluster.on('exit', () => wss.close()); +} else { + const configs = [ [true, 10000, 64], - [true, 5000, 16*1024], - [true, 1000, 128*1024], - [true, 100, 1024*1024], - [true, 1, 500*1024*1024], + [true, 5000, 16 * 1024], + [true, 1000, 128 * 1024], + [true, 100, 1024 * 1024], + [true, 1, 500 * 1024 * 1024], [false, 10000, 64], - [false, 5000, 16*1024], - [false, 1000, 128*1024], - [false, 100, 1024*1024], + [false, 5000, 16 * 1024], + [false, 1000, 128 * 1024], + [false, 100, 1024 * 1024] ]; - var largest = configs[0][1]; - for (var i = 0, l = configs.length; i < l; ++i) { - if (configs[i][2] > largest) largest = configs[i][2]; - } - - console.log('Generating %s of test data ...', humanSize(largest)); - var randomBytes = generateRandomData(largest); - - function roundtrip(useBinary, roundtrips, size, cb) { - var data = randomBytes.slice(0, size); - var prefix = util.format('Running %d roundtrips of %s %s data', roundtrips, humanSize(size), useBinary ? 'binary' : 'text'); - console.log(prefix); - var client = new WebSocket('ws://localhost:' + '8181'); - var dt; - var roundtrip = 0; - function send() { - client.send(data, {binary: useBinary}); - } - client.on('error', function(e) { - console.error(e); - process.exit(); - }); - client.on('open', function() { - dt = Date.now(); - send(); - }); - client.on('message', function(data, flags) { - if (++roundtrip == roundtrips) { - var elapsed = Date.now() - dt; - cursor.up(); - console.log('%s:\t%ss\t%s' - , useBinary ? prefix.green : prefix.cyan - , roundPrec(elapsed / 1000, 1).toString().green.bold - , (humanSize((size * roundtrips) / elapsed * 1000) + '/s').blue.bold); - client.close(); - cb(); - return; - } - process.nextTick(send); - }); - } + const largest = configs.reduce((prev, curr) => curr[2] > prev ? curr[2] : prev, 0); + console.log('Generating %s of test data...', humanSize(largest)); + const randomBytes = generateRandomData(largest); (function run() { - if (configs.length == 0) process.exit(); + if (configs.length === 0) return cluster.worker.kill(); var config = configs.shift(); - config.push(run); - roundtrip.apply(null, config); + config.push(randomBytes, run); + runConfig.apply(null, config); })(); } diff --git a/package.json b/package.json index 80c85195b..734f9e350 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "ultron": "1.0.x" }, "devDependencies": { - "ansi": "0.3.x", "benchmark": "2.1.x", "bufferutil": "1.2.x", "eslint": "^2.12.0", @@ -33,7 +32,6 @@ "istanbul": "^0.4.1", "mocha": "2.3.x", "should": "8.0.x", - "tinycolor": "0.0.x", "utf-8-validate": "1.2.x" }, "gypfile": true From 58c7722fab635c1253d1793632b177e23938fd31 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 27 Sep 2016 19:35:00 +0200 Subject: [PATCH 046/489] [benchmark] Use a sane value for the `maxPayload` argument --- bench/parser.benchmark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index a8d245833..4a73ec25d 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -30,7 +30,7 @@ const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024) -var receiver = new Receiver({}, Infinity); +var receiver = new Receiver({}, 1024 * 1024); const suite = new benchmark.Suite(); suite.add('ping message', () => receiver.add(pingPacket1)); From 87e36b63fa9c491b7b158743721187e293947e57 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 2 Oct 2016 16:19:54 +0200 Subject: [PATCH 047/489] [codestyle] Add mocha env to eslint config --- .eslintrc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index e50702170..fa633f7b4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,13 +18,8 @@ }, "env": { "es6": true, - "node": true - }, - "globals": { - "describe": true, - "it": true, - "before": true, - "after": true + "node": true, + "mocha": true }, "extends": "eslint:recommended" } From 335f1ad578414e7bf84e0bf3707032b8029c701d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 2 Oct 2016 16:33:52 +0200 Subject: [PATCH 048/489] [deps] Bump eslint to version 3.7.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80c85195b..f63bedfec 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "ansi": "0.3.x", "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "^2.12.0", + "eslint": "3.7.x", "expect.js": "0.3.x", "istanbul": "^0.4.1", "mocha": "2.3.x", From 88ec9c941c61d6f2d937de30a4d87b3f8ef4094f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 10 Oct 2016 15:13:55 +0200 Subject: [PATCH 049/489] [minor] Remove redundant check --- lib/Sender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Sender.js b/lib/Sender.js index d048b4fd0..9ddf9a7e2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -134,7 +134,7 @@ class Sender extends EventEmitter { } if (!Buffer.isBuffer(data)) { - if (data && (data.buffer || data) instanceof ArrayBuffer) { + if ((data.buffer || data) instanceof ArrayBuffer) { data = getBufferFromNative(data); } else { canModifyData = true; From 149ad9cd004dc692e95f77807deed516b45dac6a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 10:05:31 +0200 Subject: [PATCH 050/489] [deps] Bump mocha to version 3.1.x --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f63bedfec..6d938b029 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "bufferutil": "1.2.x", "eslint": "3.7.x", "expect.js": "0.3.x", - "istanbul": "^0.4.1", - "mocha": "2.3.x", + "istanbul": "0.4.x", + "mocha": "3.1.x", "should": "8.0.x", "tinycolor": "0.0.x", "utf-8-validate": "1.2.x" From e721cd5354e9b4da5ca6031a549241b20b3a57f5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 10:43:27 +0200 Subject: [PATCH 051/489] [lint] Use semistandard instead of a custom set of rules --- .eslintignore | 3 ++- .eslintrc | 25 ------------------------- .eslintrc.yaml | 3 +++ package.json | 4 ++++ 4 files changed, 9 insertions(+), 26 deletions(-) delete mode 100644 .eslintrc create mode 100644 .eslintrc.yaml diff --git a/.eslintignore b/.eslintignore index 8d87b1d26..25fbf5a1c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -node_modules/* +node_modules/ +coverage/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index fa633f7b4..000000000 --- a/.eslintrc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "rules": { - "no-unused-vars": ["error", { "args": "none" }], - "no-console": 0, - "no-constant-condition": ["error", { "checkLoops": false }], - "no-empty": ["error", { "allowEmptyCatch": true }], - "indent": ["error", 2], - "space-infix-ops": 2, - "strict": 2, - "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], - "keyword-spacing": 2, - "linebreak-style": [2, "unix"], - "comma-dangle": ["error", "never"], - "no-unreachable": [2], - "comma-spacing": 2, - "comma-style": ["error", "last"], - "max-len": ["error", 120] - }, - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "extends": "eslint:recommended" -} diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 000000000..5b53411e0 --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,3 @@ +extends: semistandard +env: + mocha: true diff --git a/package.json b/package.json index 6d938b029..7790e2cdc 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ "benchmark": "2.1.x", "bufferutil": "1.2.x", "eslint": "3.7.x", + "eslint-config-semistandard": "7.0.x", + "eslint-config-standard": "6.2.x", + "eslint-plugin-promise": "3.0.x", + "eslint-plugin-standard": "2.0.x", "expect.js": "0.3.x", "istanbul": "0.4.x", "mocha": "3.1.x", From 9fe7d78f897ea0f3d5c43e25b63e693cb2f6e53a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 10:50:21 +0200 Subject: [PATCH 052/489] [lint] Run eslint --fix on index.js and lib/* --- index.js | 4 +- lib/BufferPool.js | 14 ++--- lib/BufferUtil.fallback.js | 20 +++---- lib/ErrorCodes.js | 2 +- lib/Extensions.js | 16 +++--- lib/PerMessageDeflate.js | 84 ++++++++++++++-------------- lib/Receiver.hixie.js | 30 +++++----- lib/Receiver.js | 90 +++++++++++++++--------------- lib/Sender.hixie.js | 12 ++-- lib/Sender.js | 52 +++++++++--------- lib/Validation.fallback.js | 2 +- lib/WebSocket.js | 109 ++++++++++++++++++------------------- lib/WebSocketServer.js | 86 ++++++++++++++--------------- 13 files changed, 258 insertions(+), 263 deletions(-) diff --git a/index.js b/index.js index a7e8644b9..3cfe33714 100644 --- a/index.js +++ b/index.js @@ -20,7 +20,7 @@ WS.Receiver = require('./lib/Receiver'); * @returns {WS.Server} * @api public */ -WS.createServer = function createServer(options, fn) { +WS.createServer = function createServer (options, fn) { var server = new WS.Server(options); if (typeof fn === 'function') { @@ -38,7 +38,7 @@ WS.createServer = function createServer(options, fn) { * @returns {WS} * @api public */ -WS.connect = WS.createConnection = function connect(address, fn) { +WS.connect = WS.createConnection = function connect (address, fn) { var client = new WS(address); if (typeof fn === 'function') { diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 57517774e..c006512d1 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -7,12 +7,12 @@ 'use strict'; class BufferPool { - constructor(initialSize, growStrategy, shrinkStrategy) { - this._growStrategy = (growStrategy || function(db, size) { + constructor (initialSize, growStrategy, shrinkStrategy) { + this._growStrategy = (growStrategy || function (db, size) { return db.used + size; }).bind(null, this); - this._shrinkStrategy = (shrinkStrategy || function(db) { + this._shrinkStrategy = (shrinkStrategy || function (db) { return initialSize; }).bind(null, this); @@ -22,15 +22,15 @@ class BufferPool { this._changeFactor = 0; } - get size() { + get size () { return this._buffer.length; } - get used() { + get used () { return this._used; } - get(length) { + get (length) { if (this._buffer == null || this._offset + length > this._buffer.length) { var newBuf = new Buffer(this._growStrategy(length)); this._buffer = newBuf; @@ -42,7 +42,7 @@ class BufferPool { return buf; } - reset(forceNewBuffer) { + reset (forceNewBuffer) { var len = this._shrinkStrategy(); if (len < this.size) this._changeFactor -= 1; if (forceNewBuffer || this._changeFactor < -2) { diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index d81b5f7ab..a1f9ed41b 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -7,7 +7,7 @@ 'use strict'; exports.BufferUtil = { - merge: function(mergedBuffer, buffers) { + merge: function (mergedBuffer, buffers) { var offset = 0; for (var i = 0, l = buffers.length; i < l; ++i) { var buf = buffers[i]; @@ -15,7 +15,7 @@ exports.BufferUtil = { offset += buf.length; } }, - mask: function(source, mask, output, offset, length) { + mask: function (source, mask, output, offset, length) { var maskNum = mask.readUInt32LE(0, true); var i = 0; for (; i < length - 3; i += 4) { @@ -25,13 +25,13 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; - case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; - case 1: output[offset + i] = source[i] ^ mask[0]; + case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; + case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; + case 1: output[offset + i] = source[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ }, - unmask: function(data, mask) { + unmask: function (data, mask) { var maskNum = mask.readUInt32LE(0, true); var length = data.length; var i = 0; @@ -42,10 +42,10 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: data[i + 2] = data[i + 2] ^ mask[2]; - case 2: data[i + 1] = data[i + 1] ^ mask[1]; - case 1: data[i] = data[i] ^ mask[0]; + case 3: data[i + 2] = data[i + 2] ^ mask[2]; + case 2: data[i + 1] = data[i + 1] ^ mask[1]; + case 1: data[i] = data[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ } -} +}; diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 335efb4e8..3e587a755 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -7,7 +7,7 @@ 'use strict'; module.exports = { - isValidErrorCode: function(code) { + isValidErrorCode: function (code) { return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || (code >= 3000 && code <= 4999); }, diff --git a/lib/Extensions.js b/lib/Extensions.js index 2b7f41593..574677129 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -11,18 +11,18 @@ exports.format = format; * Parse extensions header value */ -function parse(value) { +function parse (value) { value = value || ''; var extensions = {}; - value.split(',').forEach(function(v) { + value.split(',').forEach(function (v) { var params = v.split(';'); var token = params.shift().trim(); var paramsList = extensions[token] = extensions[token] || []; var parsedParams = {}; - params.forEach(function(param) { + params.forEach(function (param) { var parts = param.trim().split('='); var key = parts[0]; var value = parts[1]; @@ -50,17 +50,17 @@ function parse(value) { * Format extensions header value */ -function format(value) { - return Object.keys(value).map(function(token) { +function format (value) { + return Object.keys(value).map(function (token) { var paramsList = value[token]; if (!Array.isArray(paramsList)) { paramsList = [paramsList]; } - return paramsList.map(function(params) { - return [token].concat(Object.keys(params).map(function(k) { + return paramsList.map(function (params) { + return [token].concat(Object.keys(params).map(function (k) { var p = params[k]; if (!Array.isArray(p)) p = [p]; - return p.map(function(v) { + return p.map(function (v) { return v === true ? k : k + '=' + v; }).join('; '); })).join('; '); diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 515e30e9e..4efae7971 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -10,7 +10,7 @@ const DEFAULT_MEM_LEVEL = 8; * Per-message Compression Extensions implementation */ class PerMessageDeflate { - constructor(options, isServer, maxPayload) { + constructor (options, isServer, maxPayload) { this._options = options || {}; this._isServer = !!isServer; this._inflate = null; @@ -25,7 +25,7 @@ class PerMessageDeflate { * @api public */ - offer() { + offer () { var params = {}; if (this._options.serverNoContextTakeover) { params.server_no_context_takeover = true; @@ -49,7 +49,7 @@ class PerMessageDeflate { * * @api public */ - accept(paramsList) { + accept (paramsList) { paramsList = this.normalizeParams(paramsList); var params; @@ -68,7 +68,7 @@ class PerMessageDeflate { * * @api public */ - cleanup() { + cleanup () { if (this._inflate) { if (this._inflate.writeInProgress) { this._inflate.pendingClose = true; @@ -93,9 +93,9 @@ class PerMessageDeflate { * @api private */ - acceptAsServer(paramsList) { + acceptAsServer (paramsList) { var accepted = {}; - var result = paramsList.some(function(params) { + var result = paramsList.some(function (params) { accepted = {}; if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { return; @@ -147,7 +147,7 @@ class PerMessageDeflate { * @api privaye */ - acceptAsClient(paramsList) { + acceptAsClient (paramsList) { var params = paramsList[0]; if (this._options.clientNoContextTakeover != null) { if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { @@ -172,9 +172,9 @@ class PerMessageDeflate { * @api private */ - normalizeParams(paramsList) { - return paramsList.map(function(params) { - Object.keys(params).forEach(function(key) { + normalizeParams (paramsList) { + return paramsList.map(function (params) { + Object.keys(params).forEach(function (key) { var value = params[key]; if (value.length > 1) { throw new Error('Multiple extension parameters for ' + key); @@ -183,28 +183,28 @@ class PerMessageDeflate { value = value[0]; switch (key) { - case 'server_no_context_takeover': - case 'client_no_context_takeover': - if (value !== true) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } - params[key] = true; - break; - case 'server_max_window_bits': - case 'client_max_window_bits': - if (typeof value === 'string') { - value = parseInt(value, 10); - if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + case 'server_no_context_takeover': + case 'client_no_context_takeover': + if (value !== true) { throw new Error(`invalid extension parameter value for ${key} (${value})`); } - } - if (!this._isServer && value === true) { - throw new Error(`Missing extension parameter value for ${key}`); - } - params[key] = value; - break; - default: - throw new Error(`Not defined extension parameter (${key})`); + params[key] = true; + break; + case 'server_max_window_bits': + case 'client_max_window_bits': + if (typeof value === 'string') { + value = parseInt(value, 10); + if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + throw new Error(`invalid extension parameter value for ${key} (${value})`); + } + } + if (!this._isServer && value === true) { + throw new Error(`Missing extension parameter value for ${key}`); + } + params[key] = value; + break; + default: + throw new Error(`Not defined extension parameter (${key})`); } }, this); return params; @@ -216,13 +216,13 @@ class PerMessageDeflate { * * @api public */ - decompress(data, fin, callback) { + decompress (data, fin, callback) { var endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._inflate = zlib.createInflateRaw({ - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS + windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS }); } this._inflate.writeInProgress = true; @@ -236,17 +236,17 @@ class PerMessageDeflate { if (fin) { this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); } - this._inflate.flush(function() { + this._inflate.flush(function () { cleanup(); callback(null, Buffer.concat(buffers)); }); - function onError(err) { + function onError (err) { cleanup(); callback(err); } - function onData(data) { + function onData (data) { if (self._maxPayload > 0) { cumulativeBufferLength += data.length; if (cumulativeBufferLength > self._maxPayload) { @@ -261,7 +261,7 @@ class PerMessageDeflate { buffers.push(data); } - function cleanup() { + function cleanup () { if (!self._inflate) return; self._inflate.removeListener('error', onError); self._inflate.removeListener('data', onData); @@ -279,14 +279,14 @@ class PerMessageDeflate { * @api public */ - compress(data, fin, callback) { + compress (data, fin, callback) { var endpoint = this._isServer ? 'server' : 'client'; if (!this._deflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._deflate = zlib.createDeflateRaw({ flush: zlib.Z_SYNC_FLUSH, - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, + windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL }); } @@ -297,7 +297,7 @@ class PerMessageDeflate { this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(function() { + this._deflate.flush(function () { cleanup(); var data = Buffer.concat(buffers); if (fin) { @@ -306,16 +306,16 @@ class PerMessageDeflate { callback(null, data); }); - function onError(err) { + function onError (err) { cleanup(); callback(err); } - function onData(data) { + function onData (data) { buffers.push(data); } - function cleanup() { + function cleanup () { if (!self._deflate) return; self._deflate.removeListener('error', onError); self._deflate.removeListener('data', onData); diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 8123c6e96..d64ea746b 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -18,19 +18,19 @@ const BINARYLENGTH = 2, BINARYBODY = 3; */ class Receiver { - constructor() { + constructor () { this.state = EMPTY; this.buffers = []; this.messageEnd = -1; this.spanLength = 0; this.dead = false; - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; + this.onerror = function () {}; + this.ontext = function () {}; + this.onbinary = function () {}; + this.onclose = function () {}; + this.onping = function () {}; + this.onpong = function () {}; } /** @@ -39,9 +39,9 @@ class Receiver { * @api public */ - add(data) { + add (data) { var self = this; - function doAdd() { + function doAdd () { if (self.state === EMPTY) { if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { self.reset(); @@ -53,14 +53,12 @@ class Receiver { self.state = BINARYLENGTH; data = data.slice(1); } else { - if (data[0] !== 0x00) { self.error(new Error('payload must start with 0x00 byte'), true); return; } data = data.slice(1); self.state = BODY; - } } if (self.state === BINARYLENGTH) { @@ -107,7 +105,7 @@ class Receiver { * @api public */ - cleanup() { + cleanup () { this.dead = true; this.state = EMPTY; this.buffers = []; @@ -119,7 +117,7 @@ class Receiver { * @api public */ - parse() { + parse () { var output = new Buffer(this.spanLength); var outputIndex = 0; for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { @@ -145,9 +143,9 @@ class Receiver { * @api private */ - error(err, terminate) { + error (err, terminate) { this.reset(); - this.onerror(err, terminate) + this.onerror(err, terminate); return this; } @@ -156,7 +154,7 @@ class Receiver { * * @api private */ - reset(reason) { + reset (reason) { if (this.dead) return; this.state = EMPTY; this.buffers = []; diff --git a/lib/Receiver.js b/lib/Receiver.js index 518f0cfe3..d49378a0c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -17,17 +17,17 @@ const PerMessageDeflate = require('./PerMessageDeflate'); */ class Receiver { - constructor(extensions, maxPayload) { - if (typeof extensions === 'number'){ + constructor (extensions, maxPayload) { + if (typeof extensions === 'number') { maxPayload = extensions; extensions = {}; } // memory pool for fragmented messages var fragmentedPoolPrevUsed = -1; - this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { + this.fragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; - }, function(db) { + }, function (db) { return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : db.used; @@ -35,9 +35,9 @@ class Receiver { // memory pool for unfragmented messages var unfragmentedPoolPrevUsed = -1; - this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { + this.unfragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; - }, function(db) { + }, function (db) { return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : db.used; @@ -64,12 +64,12 @@ class Receiver { this.dead = false; this.processing = false; - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; + this.onerror = function () {}; + this.ontext = function () {}; + this.onbinary = function () {}; + this.onclose = function () {}; + this.onping = function () {}; + this.onpong = function () {}; } /** @@ -78,7 +78,7 @@ class Receiver { * @api public */ - add(data) { + add (data) { if (this.dead) return; const dataLength = data.length; if (dataLength == 0) return; @@ -106,7 +106,7 @@ class Receiver { * @api public */ - cleanup() { + cleanup () { this.dead = true; this.overflow = null; this.headerBuffer = null; @@ -130,7 +130,7 @@ class Receiver { * @api private */ - expectHeader(length, handler) { + expectHeader (length, handler) { if (length == 0) { handler(null); return; @@ -154,7 +154,7 @@ class Receiver { * @api private */ - expectData(length, handler) { + expectData (length, handler) { if (length == 0) { handler(null); return; @@ -178,7 +178,7 @@ class Receiver { * @api private */ - allocateFromPool(length, isFragmented) { + allocateFromPool (length, isFragmented) { return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); } @@ -188,7 +188,7 @@ class Receiver { * @api private */ - processPacket(data) { + processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) != 0) { this.error(new Error('reserved fields (2, 3) must be empty'), 1002); @@ -248,7 +248,7 @@ class Receiver { * @api private */ - endPacket() { + endPacket () { if (this.dead) return; if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); @@ -272,7 +272,7 @@ class Receiver { * @api private */ - reset() { + reset () { if (this.dead) return; this.state = { activeFragmentedOperation: null, @@ -299,7 +299,7 @@ class Receiver { * @api private */ - unmask(mask, buf) { + unmask (mask, buf) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); return buf; } @@ -310,7 +310,7 @@ class Receiver { * @api private */ - error(err, protocolErrorCode) { + error (err, protocolErrorCode) { this.reset(); this.onerror(err, protocolErrorCode); return this; @@ -322,7 +322,7 @@ class Receiver { * @api private */ - flush() { + flush () { if (this.processing || this.dead) return; var handler = this.messageHandlers.shift(); @@ -342,7 +342,7 @@ class Receiver { * @api private */ - applyExtensions(messageBuffer, fin, compressed, callback) { + applyExtensions (messageBuffer, fin, compressed, callback) { if (compressed) { const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(messageBuffer, fin, (err, buffer) => { @@ -364,7 +364,7 @@ class Receiver { * @api private */ - maxPayloadExceeded(length) { + maxPayloadExceeded (length) { if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; } @@ -386,31 +386,31 @@ module.exports = Receiver; * Buffer utilities */ -function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { +function fastCopy (length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { - default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; - case 1: dstBuffer[dstOffset] = srcBuffer[0]; + default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; + case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; + case 1: dstBuffer[dstOffset] = srcBuffer[0]; } /* eslint-enable no-fallthrough */ } -function clone(obj) { +function clone (obj) { return Object.assign({}, obj); } @@ -698,4 +698,4 @@ const opcodes = { receiver.endPacket(); } } -} +}; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 18235d3a6..e7ad36d7b 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -13,7 +13,7 @@ const EventEmitter = require('events'); */ class Sender extends EventEmitter { - constructor(socket) { + constructor (socket) { super(); this.socket = socket; @@ -26,7 +26,7 @@ class Sender extends EventEmitter { * * @api public */ - send(data, options, cb) { + send (data, options, cb) { if (this.isClosed) return; var isString = typeof data == 'string'; @@ -68,7 +68,7 @@ class Sender extends EventEmitter { try { this.socket.write(buffer, 'binary', cb); } catch (e) { - this.emit('error', e) + this.emit('error', e); } } @@ -78,7 +78,7 @@ class Sender extends EventEmitter { * @api public */ - close(code, data, mask, cb) { + close (code, data, mask, cb) { if (this.isClosed) return; this.isClosed = true; try { @@ -95,14 +95,14 @@ class Sender extends EventEmitter { * @api public */ - ping(data, options) {} + ping (data, options) {} /** * Sends a pong message to the remote party. Not available for hixie. * * @api public */ - pong(data, options) {} + pong (data, options) {} } module.exports = Sender; diff --git a/lib/Sender.js b/lib/Sender.js index d048b4fd0..d29f31bc5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -15,7 +15,7 @@ const PerMessageDeflate = require('./PerMessageDeflate'); * HyBi Sender implementation, Inherits from EventEmitter. */ class Sender extends EventEmitter { - constructor(socket, extensions) { + constructor (socket, extensions) { super(); this._socket = socket; @@ -31,7 +31,7 @@ class Sender extends EventEmitter { * * @api public */ - close(code, data, mask, cb) { + close (code, data, mask, cb) { if (typeof code !== 'undefined') { if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); @@ -42,7 +42,7 @@ class Sender extends EventEmitter { if (dataBuffer.length > 2) dataBuffer.write(data, 2); var self = this; - this.messageHandlers.push(function(callback) { + this.messageHandlers.push(function (callback) { self.frameAndSend(0x8, dataBuffer, true, mask); callback(); if (typeof cb == 'function') cb(); @@ -55,10 +55,10 @@ class Sender extends EventEmitter { * * @api public */ - ping(data, options) { + ping (data, options) { var mask = options && options.mask; var self = this; - this.messageHandlers.push(function(callback) { + this.messageHandlers.push(function (callback) { self.frameAndSend(0x9, data || '', true, mask); callback(); }); @@ -70,10 +70,10 @@ class Sender extends EventEmitter { * * @api public */ - pong(data, options) { + pong (data, options) { var mask = options && options.mask; var self = this; - this.messageHandlers.push(function(callback) { + this.messageHandlers.push(function (callback) { self.frameAndSend(0xa, data || '', true, mask); callback(); }); @@ -87,7 +87,7 @@ class Sender extends EventEmitter { * @api public */ - send(data, options, cb) { + send (data, options, cb) { var finalFragment = options && options.fin === false ? false : true; var mask = options && options.mask; var compress = options && options.compress; @@ -99,13 +99,13 @@ class Sender extends EventEmitter { this.firstFragment = false; this.compress = compress; } - if (finalFragment) this.firstFragment = true + if (finalFragment) this.firstFragment = true; var compressFragment = this.compress; var self = this; - this.messageHandlers.push(function(callback) { - self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { + this.messageHandlers.push(function (callback) { + self.applyExtensions(data, finalFragment, compressFragment, function (err, data) { if (err) { if (typeof cb == 'function') cb(err); else self.emit('error', err); @@ -123,7 +123,7 @@ class Sender extends EventEmitter { * * @api private */ - frameAndSend(opcode, data, finalFragment, maskData, compressed, cb) { + frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { var canModifyData = false; if (!data) { @@ -170,12 +170,12 @@ class Sender extends EventEmitter { if (compressed) outputBuffer[0] |= 0x40; switch (secondByte) { - case 126: - outputBuffer.writeUInt16BE(dataLength, 2); - break; - case 127: - outputBuffer.writeUInt32BE(0, 2); - outputBuffer.writeUInt32BE(dataLength, 6); + case 126: + outputBuffer.writeUInt16BE(dataLength, 2); + break; + case 127: + outputBuffer.writeUInt32BE(0, 2); + outputBuffer.writeUInt32BE(dataLength, 6); } if (maskData) { @@ -205,7 +205,7 @@ class Sender extends EventEmitter { * * @api private */ - flush() { + flush () { if (this.processing) return; var handler = this.messageHandlers.shift(); @@ -215,9 +215,9 @@ class Sender extends EventEmitter { var self = this; - handler(function() { + handler(function () { self.processing = false; - process.nextTick(function() { + process.nextTick(function () { self.flush(); }); }); @@ -228,7 +228,7 @@ class Sender extends EventEmitter { * * @api private */ - applyExtensions(data, fin, compress, callback) { + applyExtensions (data, fin, compress, callback) { if (compress && data) { if ((data.buffer || data) instanceof ArrayBuffer) { data = getBufferFromNative(data); @@ -242,14 +242,14 @@ class Sender extends EventEmitter { module.exports = Sender; -function getBufferFromNative(data) { +function getBufferFromNative (data) { // data is either an ArrayBuffer or ArrayBufferView. return !data.buffer ? new Buffer(data) - : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength) + : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength); } -function getRandomMask() { +function getRandomMask () { return new Buffer([ ~~(Math.random() * 255), ~~(Math.random() * 255), @@ -258,7 +258,7 @@ function getRandomMask() { ]); } -function sendFramedData(outputBuffer, data, cb) { +function sendFramedData (outputBuffer, data, cb) { try { if (data) { this._socket.write(outputBuffer, 'binary'); diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js index b160d07a3..1b1432463 100644 --- a/lib/Validation.fallback.js +++ b/lib/Validation.fallback.js @@ -7,7 +7,7 @@ 'use strict'; exports.Validation = { - isValidUTF8: function(buffer) { + isValidUTF8: function (buffer) { return true; } }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a4a2e4040..a6ab62173 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -46,20 +46,20 @@ var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cl * @param {Object} options Additional connection options. * @api public */ -function WebSocket(address, protocols, options) { +function WebSocket (address, protocols, options) { if (this instanceof WebSocket === false) { return new WebSocket(address, protocols, options); } EventEmitter.call(this); - if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) { + if (protocols && !Array.isArray(protocols) && typeof protocols === 'object') { // accept the "options" Object as the 2nd argument options = protocols; protocols = null; } - if ('string' === typeof protocols) { + if (typeof protocols === 'string') { protocols = [ protocols ]; } @@ -91,7 +91,7 @@ util.inherits(WebSocket, EventEmitter); /** * Ready States */ -['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each(state, index) { +['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each (state, index) { WebSocket.prototype[state] = WebSocket[state] = index; }); @@ -101,7 +101,7 @@ util.inherits(WebSocket, EventEmitter); * @param {Object} data to be sent to the server * @api public */ -WebSocket.prototype.close = function close(code, data) { +WebSocket.prototype.close = function close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { @@ -122,7 +122,7 @@ WebSocket.prototype.close = function close(code, data) { this._closeCode = code; this._closeMessage = data; var mask = !this._isServer; - this._sender.close(code, data, mask, function(err) { + this._sender.close(code, data, mask, function (err) { if (err) self.emit('error', err); if (self._closeReceived && self._isServer) { @@ -143,7 +143,7 @@ WebSocket.prototype.close = function close(code, data) { * * @api public */ -WebSocket.prototype.pause = function pauser() { +WebSocket.prototype.pause = function pauser () { if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); return this._socket.pause(); @@ -157,7 +157,7 @@ WebSocket.prototype.pause = function pauser() { * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open * @api public */ -WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) { +WebSocket.prototype.ping = function ping (data, options, dontFailWhenClosed) { if (this.readyState !== WebSocket.OPEN) { if (dontFailWhenClosed === true) return; throw new Error('not opened'); @@ -178,7 +178,7 @@ WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) { * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open * @api public */ -WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { +WebSocket.prototype.pong = function (data, options, dontFailWhenClosed) { if (this.readyState !== WebSocket.OPEN) { if (dontFailWhenClosed === true) return; throw new Error('not opened'); @@ -196,7 +196,7 @@ WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { * * @api public */ -WebSocket.prototype.resume = function resume() { +WebSocket.prototype.resume = function resume () { if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); return this._socket.resume(); @@ -211,7 +211,7 @@ WebSocket.prototype.resume = function resume() { * @api public */ -WebSocket.prototype.send = function send(data, options, cb) { +WebSocket.prototype.send = function send (data, options, cb) { if (typeof options === 'function') { cb = options; options = {}; @@ -228,7 +228,7 @@ WebSocket.prototype.send = function send(data, options, cb) { var self = this; if (this._queue) { - this._queue.push(function() { self.send(data, options, cb); }); + this._queue.push(function () { self.send(data, options, cb); }); return; } @@ -253,8 +253,8 @@ WebSocket.prototype.send = function send(data, options, cb) { if (data instanceof readable) { startQueue(this); - sendStream(this, data, options, function send(error) { - process.nextTick(function tock() { + sendStream(this, data, options, function send (error) { + process.nextTick(function tock () { executeQueueSends(self); }); @@ -273,7 +273,7 @@ WebSocket.prototype.send = function send(data, options, cb) { * ticks of which send is 'function (data, final)'. * @api public */ -WebSocket.prototype.stream = function stream(options, cb) { +WebSocket.prototype.stream = function stream (options, cb) { if (typeof options === 'function') { cb = options; options = {}; @@ -304,7 +304,7 @@ WebSocket.prototype.stream = function stream(options, cb) { startQueue(this); - function send(data, final) { + function send (data, final) { try { if (self.readyState !== WebSocket.OPEN) throw new Error('not opened'); options.fin = final === true; @@ -328,7 +328,7 @@ WebSocket.prototype.stream = function stream(options, cb) { * * @api public */ -WebSocket.prototype.terminate = function terminate() { +WebSocket.prototype.terminate = function terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this._socket) { @@ -361,7 +361,7 @@ WebSocket.prototype.terminate = function terminate() { * @api public */ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { - get: function get() { + get: function get () { var amount = 0; if (this._socket) { amount = this._socket.bufferSize || 0; @@ -380,10 +380,10 @@ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { * @api public */ Object.defineProperty(WebSocket.prototype, 'binaryType', { - get: function get() { + get: function get () { return this._binaryType; }, - set: function set(type) { + set: function set (type) { if (type === 'arraybuffer' || type === 'nodebuffer') this._binaryType = type; else @@ -397,7 +397,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @see http://dev.w3.org/html5/websockets/#the-websocket-interface * @api public */ -['open', 'error', 'close', 'message'].forEach(function(method) { +['open', 'error', 'close', 'message'].forEach(function (method) { Object.defineProperty(WebSocket.prototype, 'on' + method, { /** * Returns the current listener @@ -405,7 +405,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @returns {Mixed} the set function or undefined * @api public */ - get: function get() { + get: function get () { var listener = this.listeners(method)[0]; return listener ? (listener._listener ? listener._listener : listener) : undefined; }, @@ -417,7 +417,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @returns {Mixed} the set function or undefined * @api public */ - set: function set(listener) { + set: function set (listener) { this.removeAllListeners(method); this.addEventListener(method, listener); } @@ -431,7 +431,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @see http://dev.w3.org/html5/websockets/#the-websocket-interface * @api public */ -WebSocket.prototype.addEventListener = function(method, listener) { +WebSocket.prototype.addEventListener = function (method, listener) { var target = this; function onMessage (data, flags) { @@ -482,7 +482,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { }; module.exports = WebSocket; -module.exports.buildHostHeader = buildHostHeader +module.exports.buildHostHeader = buildHostHeader; /** * W3C MessageEvent @@ -491,7 +491,7 @@ module.exports.buildHostHeader = buildHostHeader * @constructor * @api private */ -function MessageEvent(dataArg, isBinary, target) { +function MessageEvent (dataArg, isBinary, target) { this.type = 'message'; this.data = dataArg; this.target = target; @@ -505,7 +505,7 @@ function MessageEvent(dataArg, isBinary, target) { * @constructor * @api private */ -function CloseEvent(code, reason, target) { +function CloseEvent (code, reason, target) { this.type = 'close'; this.wasClean = (typeof code === 'undefined' || code === 1000); this.code = code; @@ -520,17 +520,17 @@ function CloseEvent(code, reason, target) { * @constructor * @api private */ -function OpenEvent(target) { +function OpenEvent (target) { this.type = 'open'; this.target = target; } // Append port number to Host header, only if specified in the url // and non-default -function buildHostHeader(isSecure, hostname, port) { +function buildHostHeader (isSecure, hostname, port) { var headerHost = hostname; if (hostname) { - if ((isSecure && (port != 443)) || (!isSecure && (port != 80))){ + if ((isSecure && (port != 443)) || (!isSecure && (port != 80))) { headerHost = headerHost + ':' + port; } } @@ -541,7 +541,7 @@ function buildHostHeader(isSecure, hostname, port) { * Entirely private apis, * which may or may not be bound to a sepcific WebSocket instance. */ -function initAsServerClient(req, socket, upgradeHead, options) { +function initAsServerClient (req, socket, upgradeHead, options) { options = Object.assign({ protocolVersion: protocolVersion, protocol: null, @@ -566,7 +566,7 @@ function initAsServerClient(req, socket, upgradeHead, options) { } } -function initAsClient(address, protocols, options) { +function initAsClient (address, protocols, options) { options = Object.assign({ origin: null, protocolVersion: protocolVersion, @@ -624,7 +624,7 @@ function initAsClient(address, protocols, options) { var agent = options.agent; - var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port) + var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port); var requestOptions = { port: port, @@ -671,7 +671,6 @@ function initAsClient(address, protocols, options) { || isDefinedAndNonNull(options, 'ciphers') || isDefinedAndNonNull(options, 'rejectUnauthorized') || isDefinedAndNonNull(options, 'checkServerIdentity')) { - if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; @@ -711,12 +710,12 @@ function initAsClient(address, protocols, options) { var self = this; var req = httpObj.request(requestOptions); - req.on('error', function onerror(error) { + req.on('error', function onerror (error) { self.emit('error', error); cleanupWebsocketResources.call(self, error); }); - req.once('response', function response(res) { + req.once('response', function response (res) { var error; if (!self.emit('unexpected-response', req, res)) { @@ -728,7 +727,7 @@ function initAsClient(address, protocols, options) { cleanupWebsocketResources.call(self, error); }); - req.once('upgrade', function upgrade(res, socket, upgradeHead) { + req.once('upgrade', function upgrade (res, socket, upgradeHead) { if (self.readyState === WebSocket.CLOSED) { // client closed before server accepted connection self.emit('close'); @@ -791,7 +790,7 @@ function initAsClient(address, protocols, options) { this.readyState = WebSocket.CONNECTING; } -function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { +function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket), called = false, self = this; @@ -808,7 +807,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver - function firstHandler(data) { + function firstHandler (data) { if (called || self.readyState === WebSocket.CLOSED) return; called = true; @@ -824,7 +823,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { } // subsequent packets are pushed straight to the receiver - function realHandler(data) { + function realHandler (data) { self.bytesReceived += data.length; self._receiver.add(data); } @@ -839,20 +838,20 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { process.nextTick(firstHandler); // receiver event handlers - self._receiver.ontext = function ontext(data, flags) { + self._receiver.ontext = function ontext (data, flags) { flags = flags || {}; self.emit('message', data, flags); }; - self._receiver.onbinary = function onbinary(data, flags) { + self._receiver.onbinary = function onbinary (data, flags) { flags = flags || {}; flags.binary = true; self.emit('message', data, flags); }; - self._receiver.onping = function onping(data, flags) { + self._receiver.onping = function onping (data, flags) { flags = flags || {}; self.pong(data, { @@ -863,18 +862,18 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { self.emit('ping', data, flags); }; - self._receiver.onpong = function onpong(data, flags) { + self._receiver.onpong = function onpong (data, flags) { self.emit('pong', data, flags || {}); }; - self._receiver.onclose = function onclose(code, data, flags) { + self._receiver.onclose = function onclose (code, data, flags) { flags = flags || {}; self._closeReceived = true; self.close(code, data); }; - self._receiver.onerror = function onerror(error, errorCode) { + self._receiver.onerror = function onerror (error, errorCode) { // close the connection when the receiver reports a HyBi error code self.close(errorCode, ''); self.emit('error', error); @@ -882,7 +881,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { // finalize the client this._sender = new SenderClass(socket, this.extensions); - this._sender.on('error', function onerror(error) { + this._sender.on('error', function onerror (error) { self.close(1002, ''); self.emit('error', error); }); @@ -891,11 +890,11 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { this.emit('open'); } -function startQueue(instance) { +function startQueue (instance) { instance._queue = instance._queue || []; } -function executeQueueSends(instance) { +function executeQueueSends (instance) { var queue = instance._queue; if (typeof queue === 'undefined') return; @@ -905,8 +904,8 @@ function executeQueueSends(instance) { } } -function sendStream(instance, stream, options, cb) { - stream.on('data', function incoming(data) { +function sendStream (instance, stream, options, cb) { + stream.on('data', function incoming (data) { if (instance.readyState !== WebSocket.OPEN) { if (typeof cb === 'function') cb(new Error('not opened')); else { @@ -920,7 +919,7 @@ function sendStream(instance, stream, options, cb) { instance._sender.send(data, options); }); - stream.on('end', function end() { + stream.on('end', function end () { if (instance.readyState !== WebSocket.OPEN) { if (typeof cb === 'function') cb(new Error('not opened')); else { @@ -937,7 +936,7 @@ function sendStream(instance, stream, options, cb) { }); } -function cleanupWebsocketResources(error) { +function cleanupWebsocketResources (error) { if (this.readyState === WebSocket.CLOSED) return; this.readyState = WebSocket.CLOSED; @@ -955,7 +954,7 @@ function cleanupWebsocketResources(error) { if (this._socket) { if (this._ultron) this._ultron.destroy(); - this._socket.on('error', function onerror() { + this._socket.on('error', function onerror () { try { this.destroy(); } catch (e) {} }); @@ -986,6 +985,6 @@ function cleanupWebsocketResources(error) { this.extensions = null; this.removeAllListeners(); - this.on('error', function onerror() {}); // catch all errors after this + this.on('error', function onerror () {}); // catch all errors after this delete this._queue; } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4d23bcecc..0c0f98c2a 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -23,7 +23,7 @@ var isDefinedAndNonNull = function (options, key) { * WebSocket Server implementation */ -function WebSocketServer(options, callback) { +function WebSocketServer (options, callback) { if (this instanceof WebSocketServer === false) { return new WebSocketServer(options, callback); } @@ -67,7 +67,7 @@ function WebSocketServer(options, callback) { } else { this._server.listen(options.port, options.host, callback); } - this._closeServer = function() { + this._closeServer = function () { if (self._server) self._server.close(); }; @@ -87,19 +87,19 @@ function WebSocketServer(options, callback) { } } if (this._server) { - this._onceServerListening = function() { self.emit('listening'); }; + this._onceServerListening = function () { self.emit('listening'); }; this._server.once('listening', this._onceServerListening); } if (typeof this._server != 'undefined') { - this._onServerError = function(error) { self.emit('error', error) }; + this._onServerError = function (error) { self.emit('error', error); }; this._server.on('error', this._onServerError); - this._onServerUpgrade = function(req, socket, upgradeHead) { - //copy upgradeHead to avoid retention of large slab buffers used in node core + this._onServerUpgrade = function (req, socket, upgradeHead) { + // copy upgradeHead to avoid retention of large slab buffers used in node core var head = new Buffer(upgradeHead.length); upgradeHead.copy(head); - self.handleUpgrade(req, socket, head, function(client) { + self.handleUpgrade(req, socket, head, function (client) { self.emit('connection' + req.url, client); self.emit('connection', client); }); @@ -124,7 +124,7 @@ util.inherits(WebSocketServer, EventEmitter); * @api public */ -WebSocketServer.prototype.close = function(callback) { +WebSocketServer.prototype.close = function (callback) { // terminate all associated clients var error = null; try { @@ -162,7 +162,7 @@ WebSocketServer.prototype.close = function(callback) { callback(error); else if (error) throw error; -} +}; /** * Handle a HTTP Upgrade request. @@ -170,7 +170,7 @@ WebSocketServer.prototype.close = function(callback) { * @api public */ -WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) { +WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb) { // check for wrong path if (this.options.path) { var u = url.parse(req.url); @@ -184,7 +184,7 @@ WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); else handleHybiUpgrade.apply(this, arguments); -} +}; module.exports = WebSocketServer; @@ -193,11 +193,11 @@ module.exports = WebSocketServer; * which may or may not be bound to a specific WebSocket instance. */ -function handleHybiUpgrade(req, socket, upgradeHead, cb) { +function handleHybiUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function() { + var errorHandler = function () { try { socket.destroy(); } catch (e) {} - } + }; socket.on('error', errorHandler); // verify key presence @@ -226,8 +226,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // handler to call when the connection sequence completes var self = this; - var completeHybiUpgrade2 = function(protocol) { - + var completeHybiUpgrade2 = function (protocol) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); @@ -255,8 +254,8 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { if (Object.keys(extensions).length) { var serverExtensions = {}; - Object.keys(extensions).forEach(function(token) { - serverExtensions[token] = [extensions[token].params] + Object.keys(extensions).forEach(function (token) { + serverExtensions[token] = [extensions[token].params]; }); headers.push('Sec-WebSocket-Extensions: ' + Extensions.format(serverExtensions)); } @@ -284,7 +283,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { if (self.options.clientTracking) { self.clients.push(client); - client.on('close', function() { + client.on('close', function () { var index = self.clients.indexOf(client); if (index != -1) { self.clients.splice(index, 1); @@ -295,16 +294,16 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // signal upgrade complete socket.removeListener('error', errorHandler); cb(client); - } + }; // optionally call external protocol selection handler before // calling completeHybiUpgrade2 - var completeHybiUpgrade1 = function() { + var completeHybiUpgrade1 = function () { // choose from the sub-protocols if (typeof self.options.handleProtocols == 'function') { var protList = (protocols || '').split(/, */); var callbackCalled = false; - self.options.handleProtocols(protList, function(result, protocol) { + self.options.handleProtocols(protList, function (result, protocol) { callbackCalled = true; if (!result) abortConnection(socket, 401, 'Unauthorized'); else completeHybiUpgrade2(protocol); @@ -322,7 +321,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { completeHybiUpgrade2(); } } - } + }; // optionally call external client verification handler if (typeof this.options.verifyClient == 'function') { @@ -332,7 +331,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length == 2) { - this.options.verifyClient(info, function(result, code, name) { + this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -350,11 +349,11 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { completeHybiUpgrade1(); } -function handleHixieUpgrade(req, socket, upgradeHead, cb) { +function handleHixieUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function() { + var errorHandler = function () { try { socket.destroy(); } catch (e) {} - } + }; socket.on('error', errorHandler); // bail if options prevent hixie @@ -372,7 +371,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { var origin = req.headers['origin'], self = this; // setup handshake completion to run after client has been verified - var onClientVerified = function() { + var onClientVerified = function () { var wshost; if (!req.headers['x-forwarded-host']) wshost = req.headers.host; @@ -384,7 +383,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer - var buildResponseHeader = function() { + var buildResponseHeader = function () { var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: WebSocket', @@ -398,15 +397,14 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { }; // send handshake response before receiving the nonce - var handshakeResponse = function() { - + var handshakeResponse = function () { socket.setTimeout(0); socket.setNoDelay(true); var headerBuffer = buildResponseHeader(); try { - socket.write(headerBuffer, 'binary', function(err) { + socket.write(headerBuffer, 'binary', function (err) { // remove listener if there was an error if (err) socket.removeListener('data', handler); return; @@ -418,7 +416,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { }; // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = function(nonce, rest, headerBuffer) { + var completeHandshake = function (nonce, rest, headerBuffer) { // calculate key var k1 = req.headers['sec-websocket-key1'], k2 = req.headers['sec-websocket-key2'], @@ -427,7 +425,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { [k1, k2].forEach(function (k) { var n = parseInt(k.replace(/[^\d]/g, '')), spaces = k.replace(/[^ ]/g, '').length; - if (spaces === 0 || n % spaces !== 0){ + if (spaces === 0 || n % spaces !== 0) { abortConnection(socket, 400, 'Bad Request'); return; } @@ -435,8 +433,8 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { md5.update(String.fromCharCode( n >> 24 & 0xFF, n >> 16 & 0xFF, - n >> 8 & 0xFF, - n & 0xFF), 'binary'); + n >> 8 & 0xFF, + n & 0xFF), 'binary'); }); md5.update(nonce.toString('binary'), 'binary'); @@ -450,7 +448,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { hashBuffer.copy(handshakeBuffer, headerBuffer.length); // do a single write, which - upon success - causes a new client websocket to be setup - socket.write(handshakeBuffer, 'binary', function(err) { + socket.write(handshakeBuffer, 'binary', function (err) { if (err) return; // do not create client if an error happens var client = new WebSocket([req, socket, rest], { protocolVersion: 'hixie-76', @@ -458,7 +456,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { }); if (self.options.clientTracking) { self.clients.push(client); - client.on('close', function() { + client.on('close', function () { var index = self.clients.indexOf(client); if (index != -1) { self.clients.splice(index, 1); @@ -475,7 +473,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { try { socket.destroy(); } catch (e) {} return; } - } + }; // retrieve nonce var nonceLength = 8; @@ -503,7 +501,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // complete the handshake but send empty buffer for headers since they have already been sent completeHandshake.call(self, nonce, rest, new Buffer(0)); } - } + }; // handle additional data as we receive it socket.on('data', handler); @@ -511,7 +509,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // send header response before we have the nonce to fix haproxy buffering handshakeResponse(); } - } + }; // verify client if (typeof this.options.verifyClient == 'function') { @@ -521,7 +519,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length == 2) { - this.options.verifyClient(info, function(result, code, name) { + this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -540,7 +538,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { onClientVerified(); } -function acceptExtensions(offer) { +function acceptExtensions (offer) { var extensions = {}; var options = this.options.perMessageDeflate; var maxPayload = this.options.maxPayload; @@ -552,7 +550,7 @@ function acceptExtensions(offer) { return extensions; } -function abortConnection(socket, code, name) { +function abortConnection (socket, code, name) { try { var response = `HTTP/1.1 ${code} ${name}\r\n` + `Content-type: text/html\r\n` + From 9330da4465aa2ecea0017487b0b7c917861c16c5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 14:22:24 +0200 Subject: [PATCH 053/489] [lint] Fix lint issues not fixed by eslint --fix --- lib/ErrorCodes.js | 4 +- lib/Receiver.hixie.js | 16 ++++--- lib/Receiver.js | 72 ++++++++++++++++--------------- lib/Sender.hixie.js | 18 +++++--- lib/Sender.js | 24 +++++------ lib/WebSocket.js | 52 ++++++++++++---------- lib/WebSocketServer.js | 97 ++++++++++++++++++++---------------------- 7 files changed, 145 insertions(+), 138 deletions(-) diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 3e587a755..dfd9d8644 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -8,8 +8,8 @@ module.exports = { isValidErrorCode: function (code) { - return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || - (code >= 3000 && code <= 4999); + return (code >= 1000 && code <= 1011 && code !== 1004 && code !== 1005 && code !== 1006) || + (code >= 3000 && code <= 4999); }, 1000: 'normal', 1001: 'going away', diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index d64ea746b..cda7ecdb6 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -10,8 +10,10 @@ * State constants */ -const EMPTY = 0, BODY = 1; -const BINARYLENGTH = 2, BINARYBODY = 3; +const EMPTY = 0; +const BODY = 1; +const BINARYLENGTH = 2; +const BINARYBODY = 3; /** * Hixie Receiver implementation @@ -43,7 +45,7 @@ class Receiver { var self = this; function doAdd () { if (self.state === EMPTY) { - if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { + if (data.length === 2 && data[0] === 0xFF && data[1] === 0x00) { self.reset(); self.onclose(); return; @@ -72,8 +74,9 @@ class Receiver { self.state = BINARYBODY; ++i; } - if (i > 0) + if (i > 0) { data = data.slice(i); + } } if (self.state === BINARYBODY) { var dataleft = self.messageEnd - self.spanLength; @@ -90,11 +93,12 @@ class Receiver { return; } self.buffers.push(data); - if ((self.messageEnd = data.indexOf(0xFF)) != -1) { + if ((self.messageEnd = data.indexOf(0xFF)) !== -1) { self.spanLength += self.messageEnd; return self.parse(); + } else { + self.spanLength += data.length; } - else self.spanLength += data.length; } while (data) data = doAdd(); } diff --git a/lib/Receiver.js b/lib/Receiver.js index d49378a0c..354565047 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -28,9 +28,10 @@ class Receiver { this.fragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; }, function (db) { - return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? - Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : - db.used; + fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 + ? Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) + : db.used; + return fragmentedPoolPrevUsed; }); // memory pool for unfragmented messages @@ -38,9 +39,10 @@ class Receiver { this.unfragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; }, function (db) { - return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? - Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : - db.used; + unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 + ? Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) + : db.used; + return unfragmentedPoolPrevUsed; }); this.extensions = extensions || {}; this.maxPayload = maxPayload || 0; @@ -81,7 +83,7 @@ class Receiver { add (data) { if (this.dead) return; const dataLength = data.length; - if (dataLength == 0) return; + if (dataLength === 0) return; if (this.expectBuffer == null) { this.overflow.push(data); return; @@ -92,7 +94,7 @@ class Receiver { if (toRead < dataLength) { this.overflow.push(data.slice(toRead)); } - while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { + while (this.expectBuffer && this.expectOffset === this.expectBuffer.length) { const bufferForHandler = this.expectBuffer; this.expectBuffer = null; this.expectOffset = 0; @@ -131,7 +133,7 @@ class Receiver { */ expectHeader (length, handler) { - if (length == 0) { + if (length === 0) { handler(null); return; } @@ -155,7 +157,7 @@ class Receiver { */ expectData (length, handler) { - if (length == 0) { + if (length === 0) { handler(null); return; } @@ -190,19 +192,19 @@ class Receiver { processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { - if ((data[0] & 0x30) != 0) { + if ((data[0] & 0x30) !== 0) { this.error(new Error('reserved fields (2, 3) must be empty'), 1002); return; } } else { - if ((data[0] & 0x70) != 0) { + if ((data[0] & 0x70) !== 0) { this.error(new Error('reserved fields must be empty'), 1002); return; } } - this.state.lastFragment = (data[0] & 0x80) == 0x80; - this.state.masked = (data[1] & 0x80) == 0x80; - const compressed = (data[0] & 0x40) == 0x40; + this.state.lastFragment = (data[0] & 0x80) === 0x80; + this.state.masked = (data[1] & 0x80) === 0x80; + const compressed = (data[0] & 0x40) === 0x40; const opcode = data[0] & 0xf; if (opcode === 0) { if (compressed) { @@ -212,7 +214,7 @@ class Receiver { // continuation frame this.state.fragmentedOperation = true; this.state.opcode = this.state.activeFragmentedOperation; - if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + if (!(this.state.opcode === 1 || this.state.opcode === 2)) { this.error(new Error('continuation frame cannot follow current opcode'), 1002); return; } @@ -235,7 +237,7 @@ class Receiver { } } const handler = opcodes[this.state.opcode]; - if (typeof handler == 'undefined') { + if (typeof handler === 'undefined') { this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); } else { handler.start(this, data); @@ -427,15 +429,15 @@ const opcodes = { if (firstLength < 126) { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['1'].getData(receiver, firstLength); - } else if (firstLength == 126) { + } else if (firstLength === 126) { receiver.expectHeader(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['1'].getData(receiver, length); }); - } else if (firstLength == 127) { + } else if (firstLength === 127) { receiver.expectHeader(8, (data) => { - if (data.readUInt32BE(0, true) != 0) { + if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } @@ -465,7 +467,7 @@ const opcodes = { } if (buffer != null) { - if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { receiver.currentMessage.push(buffer); } else { @@ -477,9 +479,9 @@ const opcodes = { receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 ? - receiver.currentMessage[0] : - Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + const messageBuffer = receiver.currentMessage.length === 1 + ? receiver.currentMessage[0] + : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); receiver.currentMessage = []; receiver.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { @@ -506,15 +508,15 @@ const opcodes = { if (firstLength < 126) { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['2'].getData(receiver, firstLength); - } else if (firstLength == 126) { + } else if (firstLength === 126) { receiver.expectHeader(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['2'].getData(receiver, length); }); - } else if (firstLength == 127) { + } else if (firstLength === 127) { receiver.expectHeader(8, (data) => { - if (data.readUInt32BE(0, true) != 0) { + if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } @@ -544,7 +546,7 @@ const opcodes = { } if (buffer != null) { - if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { receiver.currentMessage.push(buffer); } else { @@ -556,9 +558,9 @@ const opcodes = { receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 ? - receiver.currentMessage[0] : - Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + const messageBuffer = receiver.currentMessage.length === 1 + ? receiver.currentMessage[0] + : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); receiver.currentMessage = []; receiver.currentMessageLength = 0; receiver.onbinary(messageBuffer, { @@ -576,7 +578,7 @@ const opcodes = { // close '8': { start: (receiver, data) => { - if (receiver.state.lastFragment == false) { + if (receiver.state.lastFragment === false) { receiver.error('fragmented close is not supported', 1002); return; } @@ -602,7 +604,7 @@ const opcodes = { const packet = receiver.unmask(mask, data); const state = clone(receiver.state); receiver.messageHandlers.push(() => { - if (packet && packet.length == 1) { + if (packet && packet.length === 1) { receiver.error('close packets with data must be at least two bytes long', 1002); return; } @@ -629,7 +631,7 @@ const opcodes = { // ping '9': { start: (receiver, data) => { - if (receiver.state.lastFragment == false) { + if (receiver.state.lastFragment === false) { receiver.error('fragmented ping is not supported', 1002); return; } @@ -665,7 +667,7 @@ const opcodes = { // pong '10': { start: (receiver, data) => { - if (receiver.state.lastFragment == false) { + if (receiver.state.lastFragment === false) { receiver.error('fragmented pong is not supported', 1002); return; } diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index e7ad36d7b..439eb302f 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -29,11 +29,11 @@ class Sender extends EventEmitter { send (data, options, cb) { if (this.isClosed) return; - var isString = typeof data == 'string'; + var isString = typeof data === 'string'; var length = isString ? Buffer.byteLength(data) : data.length; var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes - var writeStartMarker = this.continuationFrame == false; - var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); + var writeStartMarker = this.continuationFrame === false; + var writeEndMarker = !options || !(typeof options.fin !== 'undefined' && !options.fin); var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; bufferLength += length; @@ -46,11 +46,13 @@ class Sender extends EventEmitter { if (options && options.binary) { buffer.write('\x80', 'binary'); // assume length less than 2**14 bytes - if (lengthbytes > 1) + if (lengthbytes > 1) { buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); + } buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); - } else + } else { buffer.write('\x00', 'binary'); + } } if (isString) buffer.write(data, offset, 'utf8'); @@ -59,11 +61,13 @@ class Sender extends EventEmitter { if (writeEndMarker) { if (options && options.binary) { // sending binary, not writing end marker - } else + } else { buffer.write('\xff', offset + length, 'binary'); + } this.continuationFrame = false; + } else { + this.continuationFrame = true; } - else this.continuationFrame = true; try { this.socket.write(buffer, 'binary', cb); diff --git a/lib/Sender.js b/lib/Sender.js index d29f31bc5..a128feb9e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -45,7 +45,7 @@ class Sender extends EventEmitter { this.messageHandlers.push(function (callback) { self.frameAndSend(0x8, dataBuffer, true, mask); callback(); - if (typeof cb == 'function') cb(); + if (typeof cb === 'function') cb(); }); this.flush(); } @@ -86,9 +86,8 @@ class Sender extends EventEmitter { * * @api public */ - send (data, options, cb) { - var finalFragment = options && options.fin === false ? false : true; + var finalFragment = !options || options.fin !== false; var mask = options && options.mask; var compress = options && options.compress; var opcode = options && options.binary ? 2 : 1; @@ -107,7 +106,7 @@ class Sender extends EventEmitter { this.messageHandlers.push(function (callback) { self.applyExtensions(data, finalFragment, compressFragment, function (err, data) { if (err) { - if (typeof cb == 'function') cb(err); + if (typeof cb === 'function') cb(err); else self.emit('error', err); return; } @@ -150,15 +149,14 @@ class Sender extends EventEmitter { } } - var dataLength = data.length, - dataOffset = maskData ? 6 : 2, - secondByte = dataLength; + var dataLength = data.length; + var dataOffset = maskData ? 6 : 2; + var secondByte = dataLength; if (dataLength >= 65536) { dataOffset += 8; secondByte = 127; - } - else if (dataLength > 125) { + } else if (dataLength > 125) { dataOffset += 2; secondByte = 126; } @@ -190,8 +188,7 @@ class Sender extends EventEmitter { } else { bufferUtil.mask(data, mask, data, 0, dataLength); } - } - else { + } else { outputBuffer[1] = secondByte; if (mergeBuffers) { data.copy(outputBuffer, dataOffset); @@ -266,9 +263,8 @@ function sendFramedData (outputBuffer, data, cb) { } else { this._socket.write(outputBuffer, 'binary', cb); } - } - catch (e) { - if (typeof cb == 'function') cb(e); + } catch (e) { + if (typeof cb === 'function') cb(e); else this.emit('error', e); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a6ab62173..ca89e9b90 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -22,7 +22,7 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const EventEmitter = require('events').EventEmitter; var isDefinedAndNonNull = function (options, key) { - return typeof options[key] != 'undefined' && options[key] !== null; + return options[key] !== undefined && options[key] !== null; }; /** @@ -335,8 +335,9 @@ WebSocket.prototype.terminate = function terminate () { this.readyState = WebSocket.CLOSING; // End the connection - try { this._socket.end(); } - catch (e) { + try { + this._socket.end(); + } catch (e) { // Socket error during end() call, so just destroy it right now cleanupWebsocketResources.call(this, true); return; @@ -384,10 +385,11 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { return this._binaryType; }, set: function set (type) { - if (type === 'arraybuffer' || type === 'nodebuffer') + if (type === 'arraybuffer' || type === 'nodebuffer') { this._binaryType = type; - else + } else { throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); + } } }); @@ -435,8 +437,9 @@ WebSocket.prototype.addEventListener = function (method, listener) { var target = this; function onMessage (data, flags) { - if (flags.binary && this.binaryType === 'arraybuffer') + if (flags.binary && this.binaryType === 'arraybuffer') { data = new Uint8Array(data).buffer; + } listener.call(target, new MessageEvent(data, !!flags.binary, target)); } @@ -530,7 +533,7 @@ function OpenEvent (target) { function buildHostHeader (isSecure, hostname, port) { var headerHost = hostname; if (hostname) { - if ((isSecure && (port != 443)) || (!isSecure && (port != 80))) { + if ((isSecure && (port !== 443)) || (!isSecure && (port !== 80))) { headerHost = headerHost + ':' + port; } } @@ -663,27 +666,29 @@ function initAsClient (address, protocols, options) { requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); } - if (isDefinedAndNonNull(options, 'pfx') - || isDefinedAndNonNull(options, 'key') - || isDefinedAndNonNull(options, 'passphrase') - || isDefinedAndNonNull(options, 'cert') - || isDefinedAndNonNull(options, 'ca') - || isDefinedAndNonNull(options, 'ciphers') - || isDefinedAndNonNull(options, 'rejectUnauthorized') - || isDefinedAndNonNull(options, 'checkServerIdentity')) { + if (isDefinedAndNonNull(options, 'pfx') || + isDefinedAndNonNull(options, 'key') || + isDefinedAndNonNull(options, 'passphrase') || + isDefinedAndNonNull(options, 'cert') || + isDefinedAndNonNull(options, 'ca') || + isDefinedAndNonNull(options, 'ciphers') || + isDefinedAndNonNull(options, 'rejectUnauthorized') || + isDefinedAndNonNull(options, 'checkServerIdentity')) { if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; - if (isDefinedAndNonNull(options, 'rejectUnauthorized')) + if (isDefinedAndNonNull(options, 'rejectUnauthorized')) { requestOptions.rejectUnauthorized = options.rejectUnauthorized; - if (isDefinedAndNonNull(options, 'checkServerIdentity')) + } + if (isDefinedAndNonNull(options, 'checkServerIdentity')) { requestOptions.checkServerIdentity = options.checkServerIdentity; + } if (!agent) { - // global agent ignores client side certificates + // global agent ignores client side certificates agent = new httpObj.Agent(requestOptions); } } @@ -791,9 +796,9 @@ function initAsClient (address, protocols, options) { } function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { - var ultron = this._ultron = new Ultron(socket), - called = false, - self = this; + var ultron = this._ultron = new Ultron(socket); + var called = false; + var self = this; socket.setTimeout(0); socket.setNoDelay(true); @@ -955,8 +960,9 @@ function cleanupWebsocketResources (error) { if (this._socket) { if (this._ultron) this._ultron.destroy(); this._socket.on('error', function onerror () { - try { this.destroy(); } - catch (e) {} + try { + this.destroy(); + } catch (e) {} }); try { diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 0c0f98c2a..899a05f74 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -16,7 +16,7 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const url = require('url'); var isDefinedAndNonNull = function (options, key) { - return typeof options[key] != 'undefined' && options[key] !== null; + return options[key] !== undefined && options[key] !== null; }; /** @@ -68,11 +68,11 @@ function WebSocketServer (options, callback) { this._server.listen(options.port, options.host, callback); } this._closeServer = function () { - if (self._server) + if (self._server) { self._server.close(); + } }; - } - else if (options.server) { + } else if (options.server) { this._server = options.server; if (options.path) { // take note of the path, to avoid collisions when multiple websocket servers are @@ -91,7 +91,7 @@ function WebSocketServer (options, callback) { this._server.once('listening', this._onceServerListening); } - if (typeof this._server != 'undefined') { + if (typeof this._server !== 'undefined') { this._onServerError = function (error) { self.emit('error', error); }; this._server.on('error', this._onServerError); this._onServerUpgrade = function (req, socket, upgradeHead) { @@ -131,15 +131,14 @@ WebSocketServer.prototype.close = function (callback) { for (var i = 0, l = this.clients.length; i < l; ++i) { this.clients[i].terminate(); } - } - catch (e) { + } catch (e) { error = e; } // remove path descriptor, if any if (this.path && this._server._webSocketPaths) { delete this._server._webSocketPaths[this.path]; - if (Object.keys(this._server._webSocketPaths).length == 0) { + if (Object.keys(this._server._webSocketPaths).length === 0) { delete this._server._webSocketPaths; } } @@ -149,8 +148,7 @@ WebSocketServer.prototype.close = function (callback) { if (typeof this._closeServer !== 'undefined') { this._closeServer(); } - } - finally { + } finally { if (this._server) { this._server.removeListener('listening', this._onceServerListening); this._server.removeListener('error', this._onServerError); @@ -158,10 +156,11 @@ WebSocketServer.prototype.close = function (callback) { } delete this._server; } - if (callback) + if (callback) { callback(error); - else if (error) + } else if (error) { throw error; + } }; /** @@ -217,9 +216,9 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var protocols = req.headers['sec-websocket-protocol']; // verify client - var origin = version < 13 ? - req.headers['sec-websocket-origin'] : - req.headers['origin']; + var origin = version < 13 + ? req.headers['sec-websocket-origin'] + : req.headers['origin']; // handle extensions offer var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']); @@ -240,7 +239,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { 'Sec-WebSocket-Accept: ' + key ]; - if (typeof protocol != 'undefined') { + if (typeof protocol !== 'undefined') { headers.push('Sec-WebSocket-Protocol: ' + protocol); } @@ -267,8 +266,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { socket.setNoDelay(true); try { socket.write(headers.concat('', '').join('\r\n')); - } - catch (e) { + } catch (e) { // if the upgrade write fails, shut the connection down hard try { socket.destroy(); } catch (e) {} return; @@ -285,7 +283,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { self.clients.push(client); client.on('close', function () { var index = self.clients.indexOf(client); - if (index != -1) { + if (index !== -1) { self.clients.splice(index, 1); } }); @@ -300,7 +298,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // calling completeHybiUpgrade2 var completeHybiUpgrade1 = function () { // choose from the sub-protocols - if (typeof self.options.handleProtocols == 'function') { + if (typeof self.options.handleProtocols === 'function') { var protList = (protocols || '').split(/, */); var callbackCalled = false; self.options.handleProtocols(protList, function (result, protocol) { @@ -316,21 +314,20 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } else { if (typeof protocols !== 'undefined') { completeHybiUpgrade2(protocols.split(/, */)[0]); - } - else { + } else { completeHybiUpgrade2(); } } }; // optionally call external client verification handler - if (typeof this.options.verifyClient == 'function') { + if (typeof this.options.verifyClient === 'function') { var info = { origin: origin, secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; - if (this.options.verifyClient.length == 2) { + if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -339,8 +336,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { else completeHybiUpgrade1(); }); return; - } - else if (!this.options.verifyClient(info)) { + } else if (!this.options.verifyClient(info)) { abortConnection(socket, 401, 'Unauthorized'); return; } @@ -368,19 +364,21 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { return; } - var origin = req.headers['origin'], self = this; + var origin = req.headers['origin']; + var self = this; // setup handshake completion to run after client has been verified var onClientVerified = function () { var wshost; - if (!req.headers['x-forwarded-host']) + if (!req.headers['x-forwarded-host']) { wshost = req.headers.host; - else + } else { wshost = req.headers['x-forwarded-host']; + } var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = (proto + '://' + wshost + req.url), - protocol = req.headers['sec-websocket-protocol']; + var location = proto + '://' + wshost + req.url; + var protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer var buildResponseHeader = function () { @@ -390,8 +388,8 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { 'Connection: Upgrade', 'Sec-WebSocket-Location: ' + location ]; - if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); - if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); + if (typeof protocol !== 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); + if (typeof origin !== 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); return new Buffer(headers.concat('', '').join('\r\n')); }; @@ -418,13 +416,13 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { // handshake completion code to run once nonce has been successfully retrieved var completeHandshake = function (nonce, rest, headerBuffer) { // calculate key - var k1 = req.headers['sec-websocket-key1'], - k2 = req.headers['sec-websocket-key2'], - md5 = crypto.createHash('md5'); + var k1 = req.headers['sec-websocket-key1']; + var k2 = req.headers['sec-websocket-key2']; + var md5 = crypto.createHash('md5'); [k1, k2].forEach(function (k) { - var n = parseInt(k.replace(/[^\d]/g, '')), - spaces = k.replace(/[^ ]/g, '').length; + var n = parseInt(k.replace(/[^\d]/g, '')); + var spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0) { abortConnection(socket, 400, 'Bad Request'); return; @@ -458,7 +456,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { self.clients.push(client); client.on('close', function () { var index = self.clients.indexOf(client); - if (index != -1) { + if (index !== -1) { self.clients.splice(index, 1); } }); @@ -468,8 +466,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { socket.removeListener('error', errorHandler); cb(client); }); - } - catch (e) { + } catch (e) { try { socket.destroy(); } catch (e) {} return; } @@ -482,8 +479,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { nonce = upgradeHead.slice(0, nonceLength); rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; completeHandshake.call(self, nonce, rest, buildResponseHeader()); - } - else { + } else { // nonce not present in upgradeHead nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); @@ -494,7 +490,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { if (toRead === 0) return; data.copy(nonce, received, 0, toRead); received += toRead; - if (received == nonceLength) { + if (received === nonceLength) { socket.removeListener('data', handler); if (toRead < data.length) rest = data.slice(toRead); @@ -512,13 +508,13 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { }; // verify client - if (typeof this.options.verifyClient == 'function') { + if (typeof this.options.verifyClient === 'function') { var info = { origin: origin, secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; - if (this.options.verifyClient.length == 2) { + if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -527,8 +523,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { else onClientVerified.apply(self); }); return; - } - else if (!this.options.verifyClient(info)) { + } else if (!this.options.verifyClient(info)) { abortConnection(socket, 401, 'Unauthorized'); return; } @@ -556,9 +551,9 @@ function abortConnection (socket, code, name) { `Content-type: text/html\r\n` + `\r\n\r\n`; socket.write(response); - } - catch (e) { /* ignore errors - we've aborted this connection */ } - finally { + } catch (e) { + // ignore errors - we've aborted this connection + } finally { // ensure that an early aborted connection is shut down completely try { socket.destroy(); } catch (e) {} } From d2c5bb831530108038df94a241aebc4feeebc898 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 14 Aug 2016 12:16:20 +0200 Subject: [PATCH 054/489] [major] Use a set instead of an array to track clients --- lib/WebSocketServer.js | 33 +++++++++++++++------------------ test/WebSocketServer.test.js | 34 +++++++++++++++++----------------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 899a05f74..38b7dd117 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -107,9 +107,9 @@ function WebSocketServer (options, callback) { this._server.on('upgrade', this._onServerUpgrade); } + if (options.clientTracking) this.clients = new Set(); this.options = options; this.path = options.path; - this.clients = []; } /** @@ -127,12 +127,15 @@ util.inherits(WebSocketServer, EventEmitter); WebSocketServer.prototype.close = function (callback) { // terminate all associated clients var error = null; - try { - for (var i = 0, l = this.clients.length; i < l; ++i) { - this.clients[i].terminate(); + + if (this.clients) { + for (const client of this.clients) { + try { + client.terminate(); + } catch (e) { + error = e; + } } - } catch (e) { - error = e; } // remove path descriptor, if any @@ -279,13 +282,10 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { maxPayload: self.options.maxPayload }); - if (self.options.clientTracking) { - self.clients.push(client); + if (self.clients) { + self.clients.add(client); client.on('close', function () { - var index = self.clients.indexOf(client); - if (index !== -1) { - self.clients.splice(index, 1); - } + self.clients.delete(client); }); } @@ -452,13 +452,10 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { protocolVersion: 'hixie-76', protocol: protocol }); - if (self.options.clientTracking) { - self.clients.push(client); + if (self.clients) { + self.clients.add(client); client.on('close', function () { - var index = self.clients.indexOf(client); - if (index !== -1) { - self.clients.splice(index, 1); - } + self.clients.delete(client); }); } diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 5dce73cae..4c42a3219 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -31,7 +31,7 @@ describe('WebSocketServer', function() { ws.should.be.an.instanceOf(WebSocketServer); done(); }); - + it('throws an error if no option object is passed', function() { var gotException = false; try { @@ -199,7 +199,7 @@ describe('WebSocketServer', function() { socket._socket.write(new Buffer([5])); socket.send(''); }; - }); + }); }); describe('#close', function() { @@ -279,11 +279,11 @@ describe('WebSocketServer', function() { describe('#clients', function() { it('returns a list of connected clients', function(done) { var wss = new WebSocketServer({port: ++port}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); + wss.clients.size.should.eql(1); wss.close(); done(); }); @@ -291,11 +291,11 @@ describe('WebSocketServer', function() { it('can be disabled', function(done) { var wss = new WebSocketServer({port: ++port, clientTracking: false}, function() { - wss.clients.length.should.eql(0); + wss.should.not.have.property('clients'); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(0); + wss.should.not.have.property('clients'); wss.close(); done(); }); @@ -308,7 +308,7 @@ describe('WebSocketServer', function() { }); wss.on('connection', function(client) { client.on('close', function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); wss.close(); done(); }); @@ -323,7 +323,7 @@ describe('WebSocketServer', function() { }); wss.on('connection', function(client) { client.on('close', function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); wss.close(); done(); }); @@ -346,12 +346,12 @@ describe('WebSocketServer', function() { it('maxpayload is passed on to clients,', function(done) { var _maxPayload = 20480; var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); - wss.clients[0].maxPayload.should.eql(_maxPayload); + wss.clients.size.should.eql(1); + client.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); @@ -359,12 +359,12 @@ describe('WebSocketServer', function() { it('maxpayload is passed on to hybi receivers', function(done) { var _maxPayload = 20480; var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); - wss.clients[0]._receiver.maxPayload.should.eql(_maxPayload); + wss.clients.size.should.eql(1); + client._receiver.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); @@ -373,12 +373,12 @@ describe('WebSocketServer', function() { var PerMessageDeflate = require('../lib/PerMessageDeflate'); var _maxPayload = 20480; var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); - wss.clients[0]._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); + wss.clients.size.should.eql(1); + client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); wss.close(); done(); }); From 03847d34c8ec35e18debbb65c3858cf60c4f392f Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 12 Oct 2016 15:08:02 +0200 Subject: [PATCH 055/489] [perf] Improve performance of fallback mask and unmask functions (#856) * Fix lint --- lib/BufferUtil.fallback.js | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index a1f9ed41b..a9607e7a0 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -16,36 +16,15 @@ exports.BufferUtil = { } }, mask: function (source, mask, output, offset, length) { - var maskNum = mask.readUInt32LE(0, true); - var i = 0; - for (; i < length - 3; i += 4) { - var num = maskNum ^ source.readUInt32LE(i, true); - if (num < 0) num = 4294967296 + num; - output.writeUInt32LE(num, offset + i, true); + for (var i = 0; i < length; i++) { + output[offset + i] = source[i] ^ mask[i & 3]; } - /* eslint-disable no-fallthrough */ - switch (length % 4) { - case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; - case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; - case 1: output[offset + i] = source[i] ^ mask[0]; - } - /* eslint-enable no-fallthrough */ }, unmask: function (data, mask) { - var maskNum = mask.readUInt32LE(0, true); + // required until https://github.com/nodejs/node/issues/9006 is resolved var length = data.length; - var i = 0; - for (; i < length - 3; i += 4) { - var num = maskNum ^ data.readUInt32LE(i, true); - if (num < 0) num = 4294967296 + num; - data.writeUInt32LE(num, i, true); - } - /* eslint-disable no-fallthrough */ - switch (length % 4) { - case 3: data[i + 2] = data[i + 2] ^ mask[2]; - case 2: data[i + 1] = data[i + 1] ^ mask[1]; - case 1: data[i] = data[i] ^ mask[0]; + for (var i = 0; i < length; i++) { + data[i] ^= mask[i & 3]; } - /* eslint-enable no-fallthrough */ } }; From da953df605f375822ca7b72b722a5e1720ca2208 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 12 Oct 2016 15:30:19 +0200 Subject: [PATCH 056/489] [minor] Simplify upgradeHead handling by using socket.unshift() (#854) --- lib/WebSocket.js | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ca89e9b90..1ed78fc32 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -797,7 +797,6 @@ function initAsClient (address, protocols, options) { function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket); - var called = false; var self = this; socket.setTimeout(0); @@ -812,35 +811,17 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver - function firstHandler (data) { - if (called || self.readyState === WebSocket.CLOSED) return; - called = true; - socket.removeListener('data', firstHandler); - ultron.on('data', realHandler); - - if (upgradeHead && upgradeHead.length > 0) { - realHandler(upgradeHead); - upgradeHead = null; - } - - if (data) realHandler(data); + if (upgradeHead && upgradeHead.length > 0) { + socket.unshift(upgradeHead); + upgradeHead = null; } - // subsequent packets are pushed straight to the receiver - function realHandler (data) { + // subsequent packets are pushed to the receiver + ultron.on('data', (data) => { self.bytesReceived += data.length; self._receiver.add(data); - } - - ultron.on('data', firstHandler); - - // if data was passed along with the http upgrade, - // this will schedule a push of that on to the receiver. - // this has to be done on next tick, since the caller - // hasn't had a chance to set event handlers on this client - // object yet. - process.nextTick(firstHandler); + }); // receiver event handlers self._receiver.ontext = function ontext (data, flags) { From 00cd45add4ab551b8ae3ed6d361741b908e2ce54 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 16:01:59 +0200 Subject: [PATCH 057/489] [lint] Fix lint issues on bench/* --- bench/parser.benchmark.js | 12 ++++++------ bench/sender.benchmark.js | 8 ++++---- bench/speed.js | 16 ++++++++-------- bench/util.js | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 4a73ec25d..3d795d338 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -6,12 +6,12 @@ 'use strict'; -const benchmark = require('benchmark') +const benchmark = require('benchmark'); -const Receiver = require('../').Receiver +const Receiver = require('../').Receiver; const util = require('./util'); -function createBinaryPacket(length) { +function createBinaryPacket (length) { const message = Buffer.alloc(length); for (var i = 0; i < length; ++i) message[i] = i % 10; @@ -20,7 +20,7 @@ function createBinaryPacket(length) { util.mask(message, '3483a868').toString('hex'), 'hex'); } -const pingMessage = 'Hello' +const pingMessage = 'Hello'; const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); const pingPacket2 = Buffer.from('8900', 'hex'); @@ -28,7 +28,7 @@ const closePacket = Buffer.from('8800', 'hex'); const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex'); const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); -const binaryDataPacket3 = createBinaryPacket(200 * 1024) +const binaryDataPacket3 = createBinaryPacket(200 * 1024); var receiver = new Receiver({}, 1024 * 1024); const suite = new benchmark.Suite(); @@ -38,7 +38,7 @@ suite.add('ping with no data', () => receiver.add(pingPacket2)); suite.add('close message', () => { receiver.add(closePacket); receiver.endPacket(); -}) +}); suite.add('masked text message', () => receiver.add(maskedTextPacket)); suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 7e9f28728..e0ac8d31b 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -6,22 +6,22 @@ 'use strict'; -const benchmark = require('benchmark') +const benchmark = require('benchmark'); -const Sender = require('../').Sender +const Sender = require('../').Sender; const framePacket = Buffer.alloc(200 * 1024).fill(99); const suite = new benchmark.Suite(); var sender = new Sender(); -sender._socket = { write() {} }; +sender._socket = { write () {} }; suite.add('frameAndSend, unmasked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, false)); suite.add('frameAndSend, masked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, true)); suite.on('cycle', (e) => { console.log(e.target.toString()); sender = new Sender(); - sender._socket = { write() {} }; + sender._socket = { write () {} }; }); if (require.main === module) { diff --git a/bench/speed.js b/bench/speed.js index 8f23e36b5..b61cb2c8d 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -2,22 +2,22 @@ const cluster = require('cluster'); -const ws = require('../'); +const WebSocket = require('../'); const port = 8181; -function roundPrec(num, prec) { +function roundPrec (num, prec) { const mul = Math.pow(10, prec); return Math.round(num * mul) / mul; } -function humanSize(bytes) { +function humanSize (bytes) { if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; return roundPrec(bytes, 2) + ' B'; } -function generateRandomData(size) { +function generateRandomData (size) { const buffer = Buffer.alloc(size); for (var i = 0; i < size; ++i) { buffer[i] = ~~(Math.random() * 127); @@ -25,9 +25,9 @@ function generateRandomData(size) { return buffer; } -function runConfig(useBinary, roundtrips, size, randomBytes, cb) { +function runConfig (useBinary, roundtrips, size, randomBytes, cb) { const data = randomBytes.slice(0, size); - const client = new ws(`ws://localhost:${port}`); + const client = new WebSocket(`ws://localhost:${port}`); var roundtrip = 0; var time; @@ -60,7 +60,7 @@ function runConfig(useBinary, roundtrips, size, randomBytes, cb) { } if (cluster.isMaster) { - const wss = new ws.Server({ + const wss = new WebSocket.Server({ maxPayload: 600 * 1024 * 1024, perMessageDeflate: false, clientTracking: false, @@ -89,7 +89,7 @@ if (cluster.isMaster) { console.log('Generating %s of test data...', humanSize(largest)); const randomBytes = generateRandomData(largest); - (function run() { + (function run () { if (configs.length === 0) return cluster.worker.kill(); var config = configs.shift(); config.push(randomBytes, run); diff --git a/bench/util.js b/bench/util.js index 3b94c63ff..7363e8b29 100644 --- a/bench/util.js +++ b/bench/util.js @@ -9,7 +9,7 @@ /** * Performs hybi07+ type masking on a hex string or buffer. */ -function mask(buf, maskString) { +function mask (buf, maskString) { const _mask = Buffer.from(maskString || '3483a868', 'hex'); if (typeof buf === 'string') buf = Buffer.from(buf); @@ -24,21 +24,21 @@ function mask(buf, maskString) { /** * Left pads the string `s` to a total length of `n` with char `c`. */ -function padl(s, n, c) { +function padl (s, n, c) { return c.repeat(n - s.length) + s; } /** * Returns a hex string, representing a specific byte count `length`, from a number. */ -function pack(length, number) { +function pack (length, number) { return padl(number.toString(16), length, '0'); } /** * Returns a hex string representing the length of a message. */ -function getHybiLengthAsHexString(len, masked) { +function getHybiLengthAsHexString (len, masked) { var s; masked = masked ? 0x80 : 0; From 3c8acc1f78bff777852e4ab57a1be689b1fc480d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 16:42:50 +0200 Subject: [PATCH 058/489] [benchmark] Move functions only used by the worker in the worker branch --- bench/speed.js | 106 ++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index b61cb2c8d..40c3f30cf 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -6,59 +6,6 @@ const WebSocket = require('../'); const port = 8181; -function roundPrec (num, prec) { - const mul = Math.pow(10, prec); - return Math.round(num * mul) / mul; -} - -function humanSize (bytes) { - if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; - if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; - return roundPrec(bytes, 2) + ' B'; -} - -function generateRandomData (size) { - const buffer = Buffer.alloc(size); - for (var i = 0; i < size; ++i) { - buffer[i] = ~~(Math.random() * 127); - } - return buffer; -} - -function runConfig (useBinary, roundtrips, size, randomBytes, cb) { - const data = randomBytes.slice(0, size); - const client = new WebSocket(`ws://localhost:${port}`); - var roundtrip = 0; - var time; - - client.on('error', (err) => { - console.error(err.stack); - cluster.worker.kill(); - }); - client.on('open', () => { - time = process.hrtime(); - client.send(data, { binary: useBinary }); - }); - client.on('message', () => { - if (++roundtrip !== roundtrips) return client.send(data, { binary: useBinary }); - - var elapsed = process.hrtime(time); - elapsed = elapsed[0] * 1e9 + elapsed[1]; - - console.log( - '%d roundtrips of %s %s data:\t%ss\t%s', - roundtrips, - humanSize(size), - useBinary ? 'binary' : 'text', - roundPrec(elapsed / 1e9, 1), - humanSize(size * roundtrips / elapsed * 1e9) + '/s' - ); - - client.close(); - cb(); - }); -} - if (cluster.isMaster) { const wss = new WebSocket.Server({ maxPayload: 600 * 1024 * 1024, @@ -85,14 +32,63 @@ if (cluster.isMaster) { [false, 100, 1024 * 1024] ]; + const roundPrec = (num, prec) => { + const mul = Math.pow(10, prec); + return Math.round(num * mul) / mul; + }; + + const humanSize = (bytes) => { + if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; + if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; + return roundPrec(bytes, 2) + ' B'; + }; + const largest = configs.reduce((prev, curr) => curr[2] > prev ? curr[2] : prev, 0); console.log('Generating %s of test data...', humanSize(largest)); - const randomBytes = generateRandomData(largest); + const randomBytes = Buffer.allocUnsafe(largest); + + for (var i = 0; i < largest; ++i) { + randomBytes[i] = ~~(Math.random() * 127); + } + + const runConfig = (useBinary, roundtrips, size, cb) => { + const data = randomBytes.slice(0, size); + const ws = new WebSocket(`ws://localhost:${port}`); + var roundtrip = 0; + var time; + + ws.on('error', (err) => { + console.error(err.stack); + cluster.worker.kill(); + }); + ws.on('open', () => { + time = process.hrtime(); + ws.send(data, { binary: useBinary }); + }); + ws.on('message', () => { + if (++roundtrip !== roundtrips) return ws.send(data, { binary: useBinary }); + + var elapsed = process.hrtime(time); + elapsed = elapsed[0] * 1e9 + elapsed[1]; + + console.log( + '%d roundtrips of %s %s data:\t%ss\t%s', + roundtrips, + humanSize(size), + useBinary ? 'binary' : 'text', + roundPrec(elapsed / 1e9, 1), + humanSize(size * roundtrips / elapsed * 1e9) + '/s' + ); + + ws.close(); + cb(); + }); + }; (function run () { if (configs.length === 0) return cluster.worker.kill(); var config = configs.shift(); - config.push(randomBytes, run); + config.push(run); runConfig.apply(null, config); })(); } From 73298bf278a59f663be03873469ca7d983bc0c6a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 13 Oct 2016 11:02:02 +0200 Subject: [PATCH 059/489] [minor] Use arrow functions for lexical `this` in lib/WebSocket.js --- lib/WebSocket.js | 137 +++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 77 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 1ed78fc32..f6c276a7e 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -19,7 +19,7 @@ const SenderHixie = require('./Sender.hixie'); const ReceiverHixie = require('./Receiver.hixie'); const Extensions = require('./Extensions'); const PerMessageDeflate = require('./PerMessageDeflate'); -const EventEmitter = require('events').EventEmitter; +const EventEmitter = require('events'); var isDefinedAndNonNull = function (options, key) { return options[key] !== undefined && options[key] !== null; @@ -116,21 +116,20 @@ WebSocket.prototype.close = function close (code, data) { return; } - var self = this; try { this.readyState = WebSocket.CLOSING; this._closeCode = code; this._closeMessage = data; var mask = !this._isServer; - this._sender.close(code, data, mask, function (err) { - if (err) self.emit('error', err); + this._sender.close(code, data, mask, (err) => { + if (err) this.emit('error', err); - if (self._closeReceived && self._isServer) { - self.terminate(); + if (this._closeReceived && this._isServer) { + this.terminate(); } else { // ensure that the connection is cleaned up even when no response of closing handshake. - clearTimeout(self._closeTimer); - self._closeTimer = setTimeout(cleanupWebsocketResources.bind(self, true), closeTimeout); + clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); } }); } catch (e) { @@ -225,10 +224,8 @@ WebSocket.prototype.send = function send (data, options, cb) { if (!data) data = ''; - var self = this; - if (this._queue) { - this._queue.push(function () { self.send(data, options, cb); }); + this._queue.push(() => this.send(data, options, cb)); return; } @@ -253,11 +250,8 @@ WebSocket.prototype.send = function send (data, options, cb) { if (data instanceof readable) { startQueue(this); - sendStream(this, data, options, function send (error) { - process.nextTick(function tock () { - executeQueueSends(self); - }); - + sendStream(this, data, options, (error) => { + process.nextTick(() => executeQueueSends(this)); if (typeof cb === 'function') cb(error); }); } else { @@ -279,8 +273,6 @@ WebSocket.prototype.stream = function stream (options, cb) { options = {}; } - var self = this; - if (typeof cb !== 'function') throw new Error('callback must be provided'); if (this.readyState !== WebSocket.OPEN) { @@ -290,7 +282,7 @@ WebSocket.prototype.stream = function stream (options, cb) { } if (this._queue) { - this._queue.push(function () { self.stream(options, cb); }); + this._queue.push(() => this.stream(options, cb)); return; } @@ -304,23 +296,23 @@ WebSocket.prototype.stream = function stream (options, cb) { startQueue(this); - function send (data, final) { + const send = (data, final) => { try { - if (self.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); options.fin = final === true; - self._sender.send(data, options); - if (!final) process.nextTick(cb.bind(null, null, send)); - else executeQueueSends(self); + this._sender.send(data, options); + if (!final) process.nextTick(cb, null, send); + else executeQueueSends(this); } catch (e) { if (typeof cb === 'function') cb(e); else { - delete self._queue; - self.emit('error', e); + delete this._queue; + this.emit('error', e); } } - } + }; - process.nextTick(cb.bind(null, null, send)); + process.nextTick(cb, null, send); }; /** @@ -712,39 +704,38 @@ function initAsClient (address, protocols, options) { else requestOptions.headers.Origin = options.origin; } - var self = this; var req = httpObj.request(requestOptions); - req.on('error', function onerror (error) { - self.emit('error', error); - cleanupWebsocketResources.call(self, error); + req.on('error', (error) => { + this.emit('error', error); + cleanupWebsocketResources.call(this, error); }); - req.once('response', function response (res) { + req.once('response', (res) => { var error; - if (!self.emit('unexpected-response', req, res)) { + if (!this.emit('unexpected-response', req, res)) { error = new Error(`unexpected server response (${res.statusCode})`); req.abort(); - self.emit('error', error); + this.emit('error', error); } - cleanupWebsocketResources.call(self, error); + cleanupWebsocketResources.call(this, error); }); - req.once('upgrade', function upgrade (res, socket, upgradeHead) { - if (self.readyState === WebSocket.CLOSED) { + req.once('upgrade', (res, socket, upgradeHead) => { + if (this.readyState === WebSocket.CLOSED) { // client closed before server accepted connection - self.emit('close'); - self.removeAllListeners(); + this.emit('close'); + this.removeAllListeners(); socket.end(); return; } var serverKey = res.headers['sec-websocket-accept']; if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { - self.emit('error', new Error('invalid server key')); - self.removeAllListeners(); + this.emit('error', new Error('invalid server key')); + this.removeAllListeners(); socket.end(); return; } @@ -762,12 +753,12 @@ function initAsClient (address, protocols, options) { } if (protError) { - self.emit('error', new Error(protError)); - self.removeAllListeners(); + this.emit('error', new Error(protError)); + this.removeAllListeners(); socket.end(); return; } else if (serverProt) { - self.protocol = serverProt; + this.protocol = serverProt; } var serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); @@ -775,15 +766,15 @@ function initAsClient (address, protocols, options) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); } catch (err) { - self.emit('error', new Error('invalid extension parameter')); - self.removeAllListeners(); + this.emit('error', new Error('invalid extension parameter')); + this.removeAllListeners(); socket.end(); return; } - self.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - establishConnection.call(self, Receiver, Sender, socket, upgradeHead); + establishConnection.call(this, Receiver, Sender, socket, upgradeHead); // perform cleanup on http resources req.removeAllListeners(); @@ -797,7 +788,6 @@ function initAsClient (address, protocols, options) { function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket); - var self = this; socket.setTimeout(0); socket.setNoDelay(true); @@ -811,7 +801,6 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver - if (upgradeHead && upgradeHead.length > 0) { socket.unshift(upgradeHead); upgradeHead = null; @@ -819,57 +808,51 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { // subsequent packets are pushed to the receiver ultron.on('data', (data) => { - self.bytesReceived += data.length; - self._receiver.add(data); + this.bytesReceived += data.length; + this._receiver.add(data); }); // receiver event handlers - self._receiver.ontext = function ontext (data, flags) { - flags = flags || {}; - - self.emit('message', data, flags); - }; + this._receiver.ontext = (data, flags) => this.emit('message', data, flags || {}); - self._receiver.onbinary = function onbinary (data, flags) { + this._receiver.onbinary = (data, flags) => { flags = flags || {}; - flags.binary = true; - self.emit('message', data, flags); + + this.emit('message', data, flags); }; - self._receiver.onping = function onping (data, flags) { + this._receiver.onping = (data, flags) => { flags = flags || {}; - self.pong(data, { - mask: !self._isServer, + this.pong(data, { + mask: !this._isServer, binary: flags.binary === true }, true); - self.emit('ping', data, flags); + this.emit('ping', data, flags); }; - self._receiver.onpong = function onpong (data, flags) { - self.emit('pong', data, flags || {}); - }; + this._receiver.onpong = (data, flags) => this.emit('pong', data, flags || {}); - self._receiver.onclose = function onclose (code, data, flags) { + this._receiver.onclose = (code, data, flags) => { flags = flags || {}; - self._closeReceived = true; - self.close(code, data); + this._closeReceived = true; + this.close(code, data); }; - self._receiver.onerror = function onerror (error, errorCode) { + this._receiver.onerror = (error, errorCode) => { // close the connection when the receiver reports a HyBi error code - self.close(errorCode, ''); - self.emit('error', error); + this.close(errorCode, ''); + this.emit('error', error); }; // finalize the client this._sender = new SenderClass(socket, this.extensions); - this._sender.on('error', function onerror (error) { - self.close(1002, ''); - self.emit('error', error); + this._sender.on('error', (error) => { + this.close(1002, ''); + this.emit('error', error); }); this.readyState = WebSocket.OPEN; From 98a9121533dc63249624c8453eeb557d828c112b Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Thu, 13 Oct 2016 12:45:13 +0200 Subject: [PATCH 060/489] [perf] Use messageHandlers only if permessage-deflate is in use (#853) --- lib/Sender.js | 136 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 42 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index ffb903ef7..4a4cfd980 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -41,13 +41,24 @@ class Sender extends EventEmitter { dataBuffer.writeUInt16BE(code, 0); if (dataBuffer.length > 2) dataBuffer.write(data, 2); - var self = this; - this.messageHandlers.push(function (callback) { - self.frameAndSend(0x8, dataBuffer, true, mask); - callback(); - if (typeof cb === 'function') cb(); - }); - this.flush(); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.doClose, [dataBuffer, mask, cb]]); + } else { + this.doClose(dataBuffer, mask, cb); + } + } + + /** + * Sends a close frame. + * + * @api private + */ + doClose (data, mask, cb) { + this.frameAndSend(0x8, data, true, mask); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.messageHandlerCallback(); + } + if (cb) cb(); } /** @@ -56,13 +67,24 @@ class Sender extends EventEmitter { * @api public */ ping (data, options) { + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.doPing, [data, options]]); + } else { + this.doPing(data, options); + } + } + + /** + * Sends a ping frame. + * + * @api private + */ + doPing (data, options) { var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function (callback) { - self.frameAndSend(0x9, data || '', true, mask); - callback(); - }); - this.flush(); + this.frameAndSend(0x9, data || '', true, mask); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.messageHandlerCallback(); + } } /** @@ -71,14 +93,24 @@ class Sender extends EventEmitter { * @api public */ pong (data, options) { - var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function (callback) { - self.frameAndSend(0xa, data || '', true, mask); - callback(); - }); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.doPong, [data, options]]); + } else { + this.doPong(data, options); + } + } - this.flush(); + /** + * Sends a pong frame. + * + * @api private + */ + doPong (data, options) { + var mask = options && options.mask; + this.frameAndSend(0xa, data || '', true, mask); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.messageHandlerCallback(); + } } /** @@ -100,21 +132,28 @@ class Sender extends EventEmitter { } if (finalFragment) this.firstFragment = true; - var compressFragment = this.compress; - - var self = this; - this.messageHandlers.push(function (callback) { - self.applyExtensions(data, finalFragment, compressFragment, function (err, data) { - if (err) { - if (typeof cb === 'function') cb(err); - else self.emit('error', err); - return; - } - self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); - callback(); - }); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); + } else { + this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + } + } + + /** + * Sends compressed data. + * + * @api private + */ + sendCompressed (opcode, data, finalFragment, mask, compress, cb) { + this.applyExtensions(data, finalFragment, this.compress, (err, data) => { + if (err) { + if (cb) cb(err); + else this.emit('error', err); + return; + } + this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + this.messageHandlerCallback(); }); - this.flush(); } /** @@ -210,14 +249,27 @@ class Sender extends EventEmitter { this.processing = true; - var self = this; + handler[0].apply(this, handler[1]); + } - handler(function () { - self.processing = false; - process.nextTick(function () { - self.flush(); - }); - }); + /** + * Callback to indicate message handler completion. + * + * @api private + */ + messageHandlerCallback () { + this.processing = false; + process.nextTick(() => this.flush()); + } + + /** + * Enqueues a send frame operation. + * + * @api private + */ + enqueue (params) { + this.messageHandlers.push(params); + this.flush(); } /** @@ -264,7 +316,7 @@ function sendFramedData (outputBuffer, data, cb) { this._socket.write(outputBuffer, 'binary', cb); } } catch (e) { - if (typeof cb === 'function') cb(e); + if (cb) cb(e); else this.emit('error', e); } } From 5a0b6f5e168d87c75fd1a8c06609851825ddea39 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 14 Oct 2016 07:56:03 +0200 Subject: [PATCH 061/489] [major] Remove Sender inheritance from EventEmitter (#861) --- lib/Sender.js | 27 ++++++++++++--------------- lib/WebSocket.js | 7 +++---- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 4a4cfd980..f68036a58 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,7 +6,6 @@ 'use strict'; -const EventEmitter = require('events'); const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); @@ -14,16 +13,15 @@ const PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Sender implementation, Inherits from EventEmitter. */ -class Sender extends EventEmitter { +class Sender { constructor (socket, extensions) { - super(); - this._socket = socket; this.extensions = extensions || {}; this.firstFragment = true; this.compress = false; this.messageHandlers = []; this.processing = false; + this.onerror = null; } /** @@ -32,9 +30,8 @@ class Sender extends EventEmitter { * @api public */ close (code, data, mask, cb) { - if (typeof code !== 'undefined') { - if (typeof code !== 'number' || - !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { + throw new Error('first argument must be a valid error code number'); } code = code || 1000; var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); @@ -148,7 +145,7 @@ class Sender extends EventEmitter { this.applyExtensions(data, finalFragment, this.compress, (err, data) => { if (err) { if (cb) cb(err); - else this.emit('error', err); + else this.onerror(err); return; } this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); @@ -167,7 +164,7 @@ class Sender extends EventEmitter { if (!data) { var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] .concat(maskData ? [0, 0, 0, 0] : []); - sendFramedData.call(this, new Buffer(buff), null, cb); + sendFramedData(this, new Buffer(buff), null, cb); return; } @@ -233,7 +230,7 @@ class Sender extends EventEmitter { data.copy(outputBuffer, dataOffset); } } - sendFramedData.call(this, outputBuffer, mergeBuffers ? null : data, cb); + sendFramedData(this, outputBuffer, mergeBuffers ? null : data, cb); } /** @@ -307,16 +304,16 @@ function getRandomMask () { ]); } -function sendFramedData (outputBuffer, data, cb) { +function sendFramedData (sender, outputBuffer, data, cb) { try { if (data) { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); + sender._socket.write(outputBuffer, 'binary'); + sender._socket.write(data, 'binary', cb); } else { - this._socket.write(outputBuffer, 'binary', cb); + sender._socket.write(outputBuffer, 'binary', cb); } } catch (e) { if (cb) cb(e); - else this.emit('error', e); + else sender.onerror(e); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f6c276a7e..286925797 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -850,10 +850,10 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { // finalize the client this._sender = new SenderClass(socket, this.extensions); - this._sender.on('error', (error) => { + this._sender.onerror = (error) => { this.close(1002, ''); this.emit('error', error); - }); + }; this.readyState = WebSocket.OPEN; this.emit('open'); @@ -939,8 +939,7 @@ function cleanupWebsocketResources (error) { } if (this._sender) { - this._sender.removeAllListeners(); - this._sender = null; + this._sender = this._sender.onerror = null; } if (this._receiver) { From 73ab3701987774005a3a4c17df6e239bd1380fd1 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 14 Oct 2016 12:26:09 +0200 Subject: [PATCH 062/489] Replace BufferPool with a buffer list --- lib/BufferPool.js | 57 ----------- lib/Receiver.js | 209 ++++++++++++++-------------------------- test/BufferPool.test.js | 72 -------------- 3 files changed, 75 insertions(+), 263 deletions(-) delete mode 100644 lib/BufferPool.js delete mode 100644 test/BufferPool.test.js diff --git a/lib/BufferPool.js b/lib/BufferPool.js deleted file mode 100644 index c006512d1..000000000 --- a/lib/BufferPool.js +++ /dev/null @@ -1,57 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -class BufferPool { - constructor (initialSize, growStrategy, shrinkStrategy) { - this._growStrategy = (growStrategy || function (db, size) { - return db.used + size; - }).bind(null, this); - - this._shrinkStrategy = (shrinkStrategy || function (db) { - return initialSize; - }).bind(null, this); - - this._buffer = new Buffer(initialSize); - this._offset = 0; - this._used = 0; - this._changeFactor = 0; - } - - get size () { - return this._buffer.length; - } - - get used () { - return this._used; - } - - get (length) { - if (this._buffer == null || this._offset + length > this._buffer.length) { - var newBuf = new Buffer(this._growStrategy(length)); - this._buffer = newBuf; - this._offset = 0; - } - this._used += length; - var buf = this._buffer.slice(this._offset, this._offset + length); - this._offset += length; - return buf; - } - - reset (forceNewBuffer) { - var len = this._shrinkStrategy(); - if (len < this.size) this._changeFactor -= 1; - if (forceNewBuffer || this._changeFactor < -2) { - this._changeFactor = 0; - this._buffer = new Buffer(len); - } - this._offset = 0; - this._used = 0; - } -} - -module.exports = BufferPool; diff --git a/lib/Receiver.js b/lib/Receiver.js index 354565047..5e51ebda5 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -8,7 +8,6 @@ const Validation = require('./Validation').Validation; const ErrorCodes = require('./ErrorCodes'); -const BufferPool = require('./BufferPool'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); @@ -23,27 +22,6 @@ class Receiver { extensions = {}; } - // memory pool for fragmented messages - var fragmentedPoolPrevUsed = -1; - this.fragmentedBufferPool = new BufferPool(1024, function (db, length) { - return db.used + length; - }, function (db) { - fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 - ? Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) - : db.used; - return fragmentedPoolPrevUsed; - }); - - // memory pool for unfragmented messages - var unfragmentedPoolPrevUsed = -1; - this.unfragmentedBufferPool = new BufferPool(1024, function (db, length) { - return db.used + length; - }, function (db) { - unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 - ? Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) - : db.used; - return unfragmentedPoolPrevUsed; - }); this.extensions = extensions || {}; this.maxPayload = maxPayload || 0; this.currentPayloadLength = 0; @@ -54,15 +32,12 @@ class Receiver { opcode: 0, fragmentedOperation: false }; - this.overflow = []; - this.headerBuffer = new Buffer(10); - this.expectOffset = 0; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; this.currentMessage = []; this.currentMessageLength = 0; this.messageHandlers = []; - this.expectHeader(2, this.processPacket); + this.expectData(2, this.processPacket); this.dead = false; this.processing = false; @@ -72,6 +47,9 @@ class Receiver { this.onclose = function () {}; this.onping = function () {}; this.onpong = function () {}; + + this.buffers = []; + this.bufferedBytes = 0; } /** @@ -82,24 +60,61 @@ class Receiver { add (data) { if (this.dead) return; - const dataLength = data.length; - if (dataLength === 0) return; - if (this.expectBuffer == null) { - this.overflow.push(data); - return; + if (!this.expectBytes) return; + + this.buffers.push(data); + this.bufferedBytes += data.length; + + while (this.expectBytes && this.expectBytes <= this.bufferedBytes) { + var bufferForHandler = this.readBuffer(this.expectBytes); + this.expectBytes = 0; + this.expectHandler(bufferForHandler); } - const toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); - fastCopy(toRead, data, this.expectBuffer, this.expectOffset); - this.expectOffset += toRead; - if (toRead < dataLength) { - this.overflow.push(data.slice(toRead)); + } + + /** + * Consume bytes from the available buffered data. + * + * @api private + */ + + readBuffer (bytes) { + var dst; + var l; + var bufoff = 0; + + if (bytes === this.buffers[0].length) { + this.bufferedBytes -= bytes; + return this.buffers.shift(); } - while (this.expectBuffer && this.expectOffset === this.expectBuffer.length) { - const bufferForHandler = this.expectBuffer; - this.expectBuffer = null; - this.expectOffset = 0; - this.expectHandler(bufferForHandler); + + if (bytes < this.buffers[0].length) { + dst = this.buffers[0].slice(0, bytes); + this.buffers[0] = this.buffers[0].slice(bytes); + this.bufferedBytes -= bytes; + return dst; + } + + dst = new Buffer(bytes); + + while (bytes > 0) { + l = this.buffers[0].length; + + if (bytes > l) { + this.buffers[0].copy(dst, bufoff); + bufoff += l; + this.buffers.shift(); + this.bufferedBytes -= l; + } else { + this.buffers[0].copy(dst, bufoff, 0, bytes); + this.buffers[0] = this.buffers[0].slice(bytes); + this.bufferedBytes -= bytes; + } + + bytes -= l; } + + return dst; } /** @@ -110,12 +125,10 @@ class Receiver { cleanup () { this.dead = true; - this.overflow = null; - this.headerBuffer = null; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; - this.unfragmentedBufferPool = null; - this.fragmentedBufferPool = null; + this.buffers = []; + this.bufferedBytes = 0; this.state = null; this.currentMessage = null; this.onerror = null; @@ -126,30 +139,6 @@ class Receiver { this.onpong = null; } - /** - * Waits for a certain amount of header bytes to be available, then fires a callback. - * - * @api private - */ - - expectHeader (length, handler) { - if (length === 0) { - handler(null); - return; - } - this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); - this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - var read = Math.min(fromOverflow.length, toRead); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; - } - } - /** * Waits for a certain amount of data bytes to be available, then fires a callback. * @@ -161,27 +150,8 @@ class Receiver { handler(null); return; } - this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); + this.expectBytes = length; this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - var read = Math.min(fromOverflow.length, toRead); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; - } - } - - /** - * Allocates memory from the buffer pool. - * - * @api private - */ - - allocateFromPool (length, isFragmented) { - return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); } /** @@ -252,10 +222,7 @@ class Receiver { endPacket () { if (this.dead) return; - if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); - else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { // end current fragmented operation @@ -265,7 +232,7 @@ class Receiver { this.state.lastFragment = false; this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; this.state.masked = false; - this.expectHeader(2, this.processPacket); + this.expectData(2, this.processPacket); } /** @@ -283,12 +250,10 @@ class Receiver { opcode: 0, fragmentedOperation: false }; - this.fragmentedBufferPool.reset(true); - this.unfragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; - this.overflow = []; + this.buffers = []; + this.bufferedBytes = 0; this.currentMessage = []; this.currentMessageLength = 0; this.messageHandlers = []; @@ -388,30 +353,6 @@ module.exports = Receiver; * Buffer utilities */ -function fastCopy (length, srcBuffer, dstBuffer, dstOffset) { - /* eslint-disable no-fallthrough */ - switch (length) { - default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; - case 1: dstBuffer[dstOffset] = srcBuffer[0]; - } - /* eslint-enable no-fallthrough */ -} - function clone (obj) { return Object.assign({}, obj); } @@ -430,13 +371,13 @@ const opcodes = { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['1'].getData(receiver, firstLength); } else if (firstLength === 126) { - receiver.expectHeader(2, (data) => { + receiver.expectData(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['1'].getData(receiver, length); }); } else if (firstLength === 127) { - receiver.expectHeader(8, (data) => { + receiver.expectData(8, (data) => { if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; @@ -449,7 +390,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['1'].finish(receiver, mask, data)); }); } else { @@ -509,13 +450,13 @@ const opcodes = { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['2'].getData(receiver, firstLength); } else if (firstLength === 126) { - receiver.expectHeader(2, (data) => { + receiver.expectData(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['2'].getData(receiver, length); }); } else if (firstLength === 127) { - receiver.expectHeader(8, (data) => { + receiver.expectData(8, (data) => { if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; @@ -528,7 +469,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['2'].finish(receiver, mask, data)); }); } else { @@ -593,7 +534,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['8'].finish(receiver, mask, data)); }); } else { @@ -646,7 +587,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['9'].finish(receiver, mask, data)); }); } else { @@ -682,7 +623,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['10'].finish(receiver, mask, data)); }); } else { diff --git a/test/BufferPool.test.js b/test/BufferPool.test.js deleted file mode 100644 index ccd087ecb..000000000 --- a/test/BufferPool.test.js +++ /dev/null @@ -1,72 +0,0 @@ -var BufferPool = require('../lib/BufferPool'); -require('should'); - -describe('BufferPool', function() { - describe('#ctor', function() { - it('allocates pool', function() { - var db = new BufferPool(1000); - db.size.should.eql(1000); - }); - it('throws TypeError when called without new', function(done) { - try { - var db = BufferPool(1000); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - describe('#get', function() { - it('grows the pool if necessary', function() { - var db = new BufferPool(1000); - var buf = db.get(2000); - db.size.should.be.above(1000); - db.used.should.eql(2000); - buf.length.should.eql(2000); - }); - it('grows the pool after the first call, if necessary', function() { - var db = new BufferPool(1000); - var buf = db.get(1000); - db.used.should.eql(1000); - db.size.should.eql(1000); - buf.length.should.eql(1000); - var buf2 = db.get(1000); - db.used.should.eql(2000); - db.size.should.be.above(1000); - buf2.length.should.eql(1000); - }); - it('grows the pool according to the growStrategy if necessary', function() { - var db = new BufferPool(1000, function(db, length) { - return db.size + 2345; - }); - var buf = db.get(2000); - db.size.should.eql(3345); - buf.length.should.eql(2000); - }); - it('doesnt grow the pool if theres enough room available', function() { - var db = new BufferPool(1000); - var buf = db.get(1000); - db.size.should.eql(1000); - buf.length.should.eql(1000); - }); - }); - describe('#reset', function() { - it('shinks the pool', function() { - var db = new BufferPool(1000); - var buf = db.get(2000); - db.reset(true); - db.size.should.eql(1000); - }); - it('shrinks the pool according to the shrinkStrategy', function() { - var db = new BufferPool(1000, function(db, length) { - return db.used + length; - }, function(db) { - return 0; - }); - var buf = db.get(2000); - db.reset(true); - db.size.should.eql(0); - }); - }); -}); From a432e95fe954ab6eb198cfa3a7dbcab2dcb42709 Mon Sep 17 00:00:00 2001 From: i8alery <8alery.gmail.com> Date: Mon, 12 Sep 2016 13:53:50 +0300 Subject: [PATCH 063/489] [fix] Add leading slash to requestOptions.path if missing Fixes #821 --- lib/WebSocket.js | 10 +++++++++- test/WebSocket.test.js | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 286925797..9d7c332c3 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -624,6 +624,7 @@ function initAsClient (address, protocols, options) { var requestOptions = { port: port, host: serverUrl.hostname, + path: '/', headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -685,7 +686,14 @@ function initAsClient (address, protocols, options) { } } - requestOptions.path = serverUrl.path || '/'; + // make sure that path starts with `/` + if (serverUrl.path) { + if (serverUrl.path.charAt(0) !== '/') { + requestOptions.path = `/${serverUrl.path}`; + } else { + requestOptions.path = serverUrl.path; + } + } if (agent) { requestOptions.agent = agent; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index f1c48a0af..65c8e80e7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -494,6 +494,26 @@ describe('WebSocket', function() { }); }); + describe('connection with query string', function () { + it('connects when pathname is not null', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); + ws.on('open', function () { + wss.close(done); + }); + }); + }); + + it('connects when pathname is null', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); + ws.on('open', function () { + wss.close(done); + }); + }); + }); + }); + describe('#pause and #resume', function() { it('pauses the underlying stream', function(done) { // this test is sort-of racecondition'y, since an unlikely slow connection From b58f688bf0fa0e87a3cef87c2f5d01bf43590668 Mon Sep 17 00:00:00 2001 From: SEAPUNK Date: Fri, 15 Apr 2016 12:26:58 -0500 Subject: [PATCH 064/489] [fix] Allow close codes 1012 and 1013 IANA has them assigned as "Service Restart", and "Try Again Later", respectively. http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent --- lib/ErrorCodes.js | 6 ++++-- test/WebSocket.test.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index dfd9d8644..f51557162 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -8,7 +8,7 @@ module.exports = { isValidErrorCode: function (code) { - return (code >= 1000 && code <= 1011 && code !== 1004 && code !== 1005 && code !== 1006) || + return (code >= 1000 && code <= 1013 && code !== 1004 && code !== 1005 && code !== 1006) || (code >= 3000 && code <= 4999); }, 1000: 'normal', @@ -22,5 +22,7 @@ module.exports = { 1008: 'policy violation', 1009: 'message too big', 1010: 'extension handshake missing', - 1011: 'an unexpected condition prevented the request from being fulfilled' + 1011: 'an unexpected condition prevented the request from being fulfilled', + 1012: 'service restart', + 1013: 'try again later' }; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 65c8e80e7..f6937833d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1538,6 +1538,20 @@ describe('WebSocket', function() { }); }); }); + + it('allows close code 1013', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var ws = new WebSocket(`ws://localhost:${port}`); + ws.on('close', function (code) { + assert.strictEqual(code, 1013); + wss.close(done); + }); + }); + + wss.on('connection', function (ws) { + ws.close(1013); + }); + }); }); describe('W3C API emulation', function() { From 192a9bd2ea179cf78379a4144ed82735e987f2f6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 16 Oct 2016 13:28:00 +0200 Subject: [PATCH 065/489] [deps] Bump eslint to version 3.8.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93ae72c2b..29cf55b19 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "devDependencies": { "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "3.7.x", + "eslint": "3.8.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.0.x", From 0cfa5cc94d50bebd6f85b78ec1094dd9a7a17d62 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 16 Oct 2016 13:26:19 +0200 Subject: [PATCH 066/489] [minor] Use arrow functions for lexical `this` in lib/WebSocketServer.js --- lib/WebSocketServer.js | 111 ++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 38b7dd117..f65c953b0 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -49,10 +49,8 @@ function WebSocketServer (options, callback) { throw new TypeError('`port` or a `server` must be provided'); } - var self = this; - if (isDefinedAndNonNull(options, 'port')) { - this._server = http.createServer(function (req, res) { + this._server = http.createServer((req, res) => { var body = http.STATUS_CODES[426]; res.writeHead(426, { 'Content-Length': body.length, @@ -67,11 +65,7 @@ function WebSocketServer (options, callback) { } else { this._server.listen(options.port, options.host, callback); } - this._closeServer = function () { - if (self._server) { - self._server.close(); - } - }; + this._closeServer = () => this._server && this._server.close(); } else if (options.server) { this._server = options.server; if (options.path) { @@ -86,22 +80,20 @@ function WebSocketServer (options, callback) { this._server._webSocketPaths[options.path] = 1; } } + if (this._server) { - this._onceServerListening = function () { self.emit('listening'); }; + this._onceServerListening = () => this.emit('listening'); this._server.once('listening', this._onceServerListening); - } - - if (typeof this._server !== 'undefined') { - this._onServerError = function (error) { self.emit('error', error); }; + this._onServerError = (error) => this.emit('error', error); this._server.on('error', this._onServerError); - this._onServerUpgrade = function (req, socket, upgradeHead) { + this._onServerUpgrade = (req, socket, upgradeHead) => { // copy upgradeHead to avoid retention of large slab buffers used in node core var head = new Buffer(upgradeHead.length); upgradeHead.copy(head); - self.handleUpgrade(req, socket, head, function (client) { - self.emit('connection' + req.url, client); - self.emit('connection', client); + this.handleUpgrade(req, socket, head, (client) => { + this.emit(`connection${req.url}`, client); + this.emit('connection', client); }); }; this._server.on('upgrade', this._onServerUpgrade); @@ -197,7 +189,7 @@ module.exports = WebSocketServer; function handleHybiUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function () { + var errorHandler = () => { try { socket.destroy(); } catch (e) {} }; socket.on('error', errorHandler); @@ -209,8 +201,8 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } // verify version - var version = parseInt(req.headers['sec-websocket-version']); - if ([8, 13].indexOf(version) === -1) { + var version = +req.headers['sec-websocket-version']; + if (version !== 8 && version !== 13) { abortConnection(socket, 400, 'Bad Request'); return; } @@ -219,7 +211,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var protocols = req.headers['sec-websocket-protocol']; // verify client - var origin = version < 13 + var origin = version !== 13 ? req.headers['sec-websocket-origin'] : req.headers['origin']; @@ -227,28 +219,26 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']); // handler to call when the connection sequence completes - var self = this; - var completeHybiUpgrade2 = function (protocol) { + var completeHybiUpgrade2 = (protocol) => { // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - key = shasum.digest('base64'); + var key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .digest('base64'); var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key + `Sec-WebSocket-Accept: ${key}` ]; if (typeof protocol !== 'undefined') { - headers.push('Sec-WebSocket-Protocol: ' + protocol); + headers.push(`Sec-WebSocket-Protocol: ${protocol}`); } var extensions = {}; try { - extensions = acceptExtensions.call(self, extensionsOffer); + extensions = acceptExtensions.call(this, extensionsOffer); } catch (err) { abortConnection(socket, 400, 'Bad Request'); return; @@ -256,14 +246,14 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { if (Object.keys(extensions).length) { var serverExtensions = {}; - Object.keys(extensions).forEach(function (token) { + Object.keys(extensions).forEach((token) => { serverExtensions[token] = [extensions[token].params]; }); - headers.push('Sec-WebSocket-Extensions: ' + Extensions.format(serverExtensions)); + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); } // allows external modification/inspection of handshake headers - self.emit('headers', headers); + this.emit('headers', headers); socket.setTimeout(0); socket.setNoDelay(true); @@ -279,14 +269,12 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { protocolVersion: version, protocol: protocol, extensions: extensions, - maxPayload: self.options.maxPayload + maxPayload: this.options.maxPayload }); - if (self.clients) { - self.clients.add(client); - client.on('close', function () { - self.clients.delete(client); - }); + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); } // signal upgrade complete @@ -296,18 +284,18 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // optionally call external protocol selection handler before // calling completeHybiUpgrade2 - var completeHybiUpgrade1 = function () { + var completeHybiUpgrade1 = () => { // choose from the sub-protocols - if (typeof self.options.handleProtocols === 'function') { + if (typeof this.options.handleProtocols === 'function') { var protList = (protocols || '').split(/, */); var callbackCalled = false; - self.options.handleProtocols(protList, function (result, protocol) { + this.options.handleProtocols(protList, (result, protocol) => { callbackCalled = true; if (!result) abortConnection(socket, 401, 'Unauthorized'); else completeHybiUpgrade2(protocol); }); if (!callbackCalled) { - // the handleProtocols handler never called our callback + // the handleProtocols handler never called our callback abortConnection(socket, 501, 'Could not process protocols'); } return; @@ -328,7 +316,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, function (result, code, name) { + this.options.verifyClient(info, (result, code, name) => { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -347,7 +335,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { function handleHixieUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function () { + var errorHandler = () => { try { socket.destroy(); } catch (e) {} }; socket.on('error', errorHandler); @@ -365,10 +353,9 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { } var origin = req.headers['origin']; - var self = this; // setup handshake completion to run after client has been verified - var onClientVerified = function () { + var onClientVerified = () => { var wshost; if (!req.headers['x-forwarded-host']) { wshost = req.headers.host; @@ -381,7 +368,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { var protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer - var buildResponseHeader = function () { + var buildResponseHeader = () => { var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: WebSocket', @@ -395,14 +382,14 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { }; // send handshake response before receiving the nonce - var handshakeResponse = function () { + var handshakeResponse = () => { socket.setTimeout(0); socket.setNoDelay(true); var headerBuffer = buildResponseHeader(); try { - socket.write(headerBuffer, 'binary', function (err) { + socket.write(headerBuffer, 'binary', (err) => { // remove listener if there was an error if (err) socket.removeListener('data', handler); return; @@ -414,13 +401,13 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { }; // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = function (nonce, rest, headerBuffer) { + var completeHandshake = (nonce, rest, headerBuffer) => { // calculate key var k1 = req.headers['sec-websocket-key1']; var k2 = req.headers['sec-websocket-key2']; var md5 = crypto.createHash('md5'); - [k1, k2].forEach(function (k) { + [k1, k2].forEach((k) => { var n = parseInt(k.replace(/[^\d]/g, '')); var spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0) { @@ -446,17 +433,15 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { hashBuffer.copy(handshakeBuffer, headerBuffer.length); // do a single write, which - upon success - causes a new client websocket to be setup - socket.write(handshakeBuffer, 'binary', function (err) { + socket.write(handshakeBuffer, 'binary', (err) => { if (err) return; // do not create client if an error happens var client = new WebSocket([req, socket, rest], { protocolVersion: 'hixie-76', protocol: protocol }); - if (self.clients) { - self.clients.add(client); - client.on('close', function () { - self.clients.delete(client); - }); + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); } // signal upgrade complete @@ -475,14 +460,14 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { if (upgradeHead && upgradeHead.length >= nonceLength) { nonce = upgradeHead.slice(0, nonceLength); rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; - completeHandshake.call(self, nonce, rest, buildResponseHeader()); + completeHandshake(nonce, rest, buildResponseHeader()); } else { // nonce not present in upgradeHead nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); var received = upgradeHead.length; rest = null; - var handler = function (data) { + var handler = (data) => { var toRead = Math.min(data.length, nonceLength - received); if (toRead === 0) return; data.copy(nonce, received, 0, toRead); @@ -492,7 +477,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { if (toRead < data.length) rest = data.slice(toRead); // complete the handshake but send empty buffer for headers since they have already been sent - completeHandshake.call(self, nonce, rest, new Buffer(0)); + completeHandshake(nonce, rest, new Buffer(0)); } }; @@ -512,12 +497,12 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, function (result, code, name) { + this.options.verifyClient(info, (result, code, name) => { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; if (!result) abortConnection(socket, code, name); - else onClientVerified.apply(self); + else onClientVerified(); }); return; } else if (!this.options.verifyClient(info)) { From 8c5476fe69790a7c2d014b236b8fe9561ba78711 Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Mon, 17 Oct 2016 22:55:58 +0800 Subject: [PATCH 067/489] [fix] Call `abortConnection()` when path validation fails (#534) --- lib/WebSocketServer.js | 5 ++++- test/WebSocketServer.test.js | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index f65c953b0..de4cb9095 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -168,7 +168,10 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb // check for wrong path if (this.options.path) { var u = url.parse(req.url); - if (u && u.pathname !== this.options.path) return; + if (u && u.pathname !== this.options.path) { + abortConnection(socket, 400, 'Bad Request'); + return; + } } if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 4c42a3219..d6d333015 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -404,6 +404,26 @@ describe('WebSocketServer', function() { }); }); }); + + it('can not finish upgrade when path is not right', function(done) { + var wss = new WebSocketServer({port: ++port, path: '/ws'}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' + }, + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + }); + }); }); describe('hybi mode', function() { From 41e7caef067bc9674355eae830e08057ebac9d76 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 18 Oct 2016 12:24:20 +0200 Subject: [PATCH 068/489] [major] Refactor the `abortConnection()` function --- lib/WebSocketServer.js | 103 ++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index de4cb9095..34848b3ff 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -169,14 +169,12 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb if (this.options.path) { var u = url.parse(req.url); if (u && u.pathname !== this.options.path) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } } - if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { - abortConnection(socket, 400, 'Bad Request'); - return; + if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { + return abortConnection(socket, 400); } if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); @@ -199,15 +197,13 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // verify key presence if (!req.headers['sec-websocket-key']) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } // verify version var version = +req.headers['sec-websocket-version']; if (version !== 8 && version !== 13) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } // verify protocol @@ -235,7 +231,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { `Sec-WebSocket-Accept: ${key}` ]; - if (typeof protocol !== 'undefined') { + if (protocol) { headers.push(`Sec-WebSocket-Protocol: ${protocol}`); } @@ -243,8 +239,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { try { extensions = acceptExtensions.call(this, extensionsOffer); } catch (err) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } if (Object.keys(extensions).length) { @@ -260,6 +255,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { socket.setTimeout(0); socket.setNoDelay(true); + try { socket.write(headers.concat('', '').join('\r\n')); } catch (e) { @@ -294,42 +290,35 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var callbackCalled = false; this.options.handleProtocols(protList, (result, protocol) => { callbackCalled = true; - if (!result) abortConnection(socket, 401, 'Unauthorized'); - else completeHybiUpgrade2(protocol); + if (!result) return abortConnection(socket, 401); + + completeHybiUpgrade2(protocol); }); if (!callbackCalled) { // the handleProtocols handler never called our callback abortConnection(socket, 501, 'Could not process protocols'); } - return; } else { - if (typeof protocols !== 'undefined') { - completeHybiUpgrade2(protocols.split(/, */)[0]); - } else { - completeHybiUpgrade2(); - } + completeHybiUpgrade2(protocols && protocols.split(/, */)[0]); } }; // optionally call external client verification handler if (typeof this.options.verifyClient === 'function') { var info = { + secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, origin: origin, - secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, name) => { - if (typeof code === 'undefined') code = 401; - if (typeof name === 'undefined') name = http.STATUS_CODES[code]; + this.options.verifyClient(info, (result, code, message) => { + if (!result) return abortConnection(socket, code || 401, message); - if (!result) abortConnection(socket, code, name); - else completeHybiUpgrade1(); + completeHybiUpgrade1(); }); return; } else if (!this.options.verifyClient(info)) { - abortConnection(socket, 401, 'Unauthorized'); - return; + return abortConnection(socket, 401); } } @@ -345,14 +334,12 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { // bail if options prevent hixie if (this.options.disableHixie) { - abortConnection(socket, 401, 'Hixie support disabled'); - return; + return abortConnection(socket, 401, 'Hixie support disabled'); } // verify key presence if (!req.headers['sec-websocket-key2']) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } var origin = req.headers['origin']; @@ -367,7 +354,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { } var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = proto + '://' + wshost + req.url; + var location = `${proto}://${wshost}${req.url}`; var protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer @@ -378,8 +365,8 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { 'Connection: Upgrade', 'Sec-WebSocket-Location: ' + location ]; - if (typeof protocol !== 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); - if (typeof origin !== 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); + if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + if (origin) headers.push(`Sec-WebSocket-Origin: ${origin}`); return new Buffer(headers.concat('', '').join('\r\n')); }; @@ -395,11 +382,9 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { socket.write(headerBuffer, 'binary', (err) => { // remove listener if there was an error if (err) socket.removeListener('data', handler); - return; }); } catch (e) { try { socket.destroy(); } catch (e) {} - return; } }; @@ -414,8 +399,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { var n = parseInt(k.replace(/[^\d]/g, '')); var spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } n /= spaces; md5.update(String.fromCharCode( @@ -495,22 +479,19 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { // verify client if (typeof this.options.verifyClient === 'function') { var info = { + secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, origin: origin, - secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, name) => { - if (typeof code === 'undefined') code = 401; - if (typeof name === 'undefined') name = http.STATUS_CODES[code]; + this.options.verifyClient(info, (result, code, message) => { + if (!result) return abortConnection(socket, code || 401, message); - if (!result) abortConnection(socket, code, name); - else onClientVerified(); + onClientVerified(); }); return; } else if (!this.options.verifyClient(info)) { - abortConnection(socket, 401, 'Unauthorized'); - return; + return abortConnection(socket, 401); } } @@ -530,16 +511,22 @@ function acceptExtensions (offer) { return extensions; } -function abortConnection (socket, code, name) { - try { - var response = `HTTP/1.1 ${code} ${name}\r\n` + - `Content-type: text/html\r\n` + - `\r\n\r\n`; - socket.write(response); - } catch (e) { - // ignore errors - we've aborted this connection - } finally { - // ensure that an early aborted connection is shut down completely - try { socket.destroy(); } catch (e) {} +/** + * Close the connection when preconditions are not fulfilled. + * + * @param {net.Socket} socket The socket of the upgrade request + * @param {Number} code The HTTP response status code + * @param {String} [message] The HTTP response body + * @api private + */ +function abortConnection (socket, code, message) { + if (socket.writable) { + message = message || http.STATUS_CODES[code]; + socket.write(`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n`); + socket.write('Connection: close\r\n'); + socket.write('Content-type: text/html\r\n'); + socket.write(`Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`); + socket.write(message); } + socket.destroy(); } From e5a2ab4a9e2921597a835c244b7b284f6e269926 Mon Sep 17 00:00:00 2001 From: Mattijah Date: Tue, 18 Mar 2014 15:11:39 +0000 Subject: [PATCH 069/489] [minor] Remove redundant check for binary support --- lib/WebSocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 9d7c332c3..06f91baf2 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -609,7 +609,7 @@ function initAsClient (address, protocols, options) { this._isServer = false; this.url = address; this.protocolVersion = options.protocolVersion; - this.supports.binary = (this.protocolVersion !== 'hixie-76'); + this.supports.binary = true; // begin handshake var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); From df49d99ef5261fdfc7bab811580cbb6b531861f3 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 19 Oct 2016 14:44:08 +0200 Subject: [PATCH 070/489] Remove messageHandler mechanism --- lib/Receiver.js | 272 +++++++++++++++++++++--------------------------- 1 file changed, 116 insertions(+), 156 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 5e51ebda5..b01b2e58f 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -36,10 +36,8 @@ class Receiver { this.expectHandler = null; this.currentMessage = []; this.currentMessageLength = 0; - this.messageHandlers = []; this.expectData(2, this.processPacket); this.dead = false; - this.processing = false; this.onerror = function () {}; this.ontext = function () {}; @@ -60,12 +58,21 @@ class Receiver { add (data) { if (this.dead) return; - if (!this.expectBytes) return; this.buffers.push(data); this.bufferedBytes += data.length; - while (this.expectBytes && this.expectBytes <= this.bufferedBytes) { + this.process(); + } + + /** + * Check buffer for data. + * + * @api private + */ + + process () { + if (this.expectBytes && this.expectBytes <= this.bufferedBytes) { var bufferForHandler = this.readBuffer(this.expectBytes); this.expectBytes = 0; this.expectHandler(bufferForHandler); @@ -152,6 +159,8 @@ class Receiver { } this.expectBytes = length; this.expectHandler = handler; + + this.process(); } /** @@ -256,7 +265,6 @@ class Receiver { this.bufferedBytes = 0; this.currentMessage = []; this.currentMessageLength = 0; - this.messageHandlers = []; this.currentPayloadLength = 0; } @@ -283,46 +291,22 @@ class Receiver { return this; } - /** - * Execute message handler buffers - * - * @api private - */ - - flush () { - if (this.processing || this.dead) return; - - var handler = this.messageHandlers.shift(); - if (!handler) return; - - this.processing = true; - - handler(() => { - this.processing = false; - this.flush(); - }); - } - /** * Apply extensions to message * * @api private */ - applyExtensions (messageBuffer, fin, compressed, callback) { - if (compressed) { - const extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(messageBuffer, fin, (err, buffer) => { - if (this.dead) return; - if (err) { - callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); - return; - } - callback(null, buffer); - }); - } else { - callback(null, messageBuffer); - } + applyExtensions (messageBuffer, fin, callback) { + const extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(messageBuffer, fin, (err, buffer) => { + if (this.dead) return; + if (err) { + callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); + return; + } + callback(null, buffer); + }); } /** @@ -345,18 +329,70 @@ class Receiver { return true; } -} -module.exports = Receiver; + /** + * Handles compressed data. + * + * @api private + */ -/** - * Buffer utilities - */ + handleDataCompressed (packet) { + this.applyExtensions(packet, this.state.lastFragment, (err, buffer) => { + if (err) { + this.error(err, err.closeCode === 1009 ? 1009 : 1007); + return; + } + + this.handleData(buffer); + this.endPacket(); + }); + } + + /** + * Handles uncompressed data. + * + * @api private + */ -function clone (obj) { - return Object.assign({}, obj); + handleData (buffer) { + if (buffer != null) { + if (this.maxPayload === 0 || (this.maxPayload > 0 && + (this.currentMessageLength + buffer.length) < this.maxPayload)) { + this.currentMessage.push(buffer); + } else { + this.currentMessage = []; + this.currentMessageLength = 0; + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); + return; + } + this.currentMessageLength += buffer.length; + } + if (this.state.lastFragment) { + const messageBuffer = this.currentMessage.length === 1 + ? this.currentMessage[0] + : Buffer.concat(this.currentMessage, this.currentMessageLength); + this.currentMessage = []; + this.currentMessageLength = 0; + + if (this.state.opcode === 2) { + this.onbinary(messageBuffer, { + masked: this.state.masked + }); + } else { + if (!Validation.isValidUTF8(messageBuffer)) { + this.error(new Error('invalid utf8 sequence'), 1007); + return; + } + this.ontext(messageBuffer.toString('utf8'), { + masked: this.state.masked + }); + } + } + } } +module.exports = Receiver; + /** * Opcode handlers */ @@ -399,46 +435,12 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data) || new Buffer(0); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { - if (err) { - receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); - return; - } - - if (buffer != null) { - if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && - (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { - receiver.currentMessage.push(buffer); - } else { - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); - return; - } - receiver.currentMessageLength += buffer.length; - } - if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 - ? receiver.currentMessage[0] - : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - if (!Validation.isValidUTF8(messageBuffer)) { - receiver.error(new Error('invalid utf8 sequence'), 1007); - return; - } - receiver.ontext(messageBuffer.toString('utf8'), { - masked: state.masked, - buffer: messageBuffer - }); - } - callback(); - }); - }); - receiver.flush(); - receiver.endPacket(); + if (receiver.state.compressed) { + receiver.handleDataCompressed(packet); + } else { + receiver.handleData(packet); + receiver.endPacket(); + } } }, // binary @@ -478,42 +480,12 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data) || new Buffer(0); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { - if (err) { - receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); - return; - } - - if (buffer != null) { - if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && - (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { - receiver.currentMessage.push(buffer); - } else { - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); - return; - } - receiver.currentMessageLength += buffer.length; - } - if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 - ? receiver.currentMessage[0] - : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - receiver.onbinary(messageBuffer, { - masked: state.masked, - buffer: messageBuffer - }); - } - callback(); - }); - }); - receiver.flush(); - receiver.endPacket(); + if (receiver.state.compressed) { + receiver.handleDataCompressed(packet); + } else { + receiver.handleData(packet); + receiver.endPacket(); + } } }, // close @@ -543,30 +515,26 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data); - const state = clone(receiver.state); - receiver.messageHandlers.push(() => { - if (packet && packet.length === 1) { - receiver.error('close packets with data must be at least two bytes long', 1002); - return; - } - const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; - if (!ErrorCodes.isValidErrorCode(code)) { - receiver.error('invalid error code', 1002); + if (packet && packet.length === 1) { + receiver.error('close packets with data must be at least two bytes long', 1002); + return; + } + const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; + if (!ErrorCodes.isValidErrorCode(code)) { + receiver.error('invalid error code', 1002); + return; + } + var message = ''; + if (packet && packet.length > 2) { + const messageBuffer = packet.slice(2); + if (!Validation.isValidUTF8(messageBuffer)) { + receiver.error('invalid utf8 sequence', 1007); return; } - var message = ''; - if (packet && packet.length > 2) { - const messageBuffer = packet.slice(2); - if (!Validation.isValidUTF8(messageBuffer)) { - receiver.error('invalid utf8 sequence', 1007); - return; - } - message = messageBuffer.toString('utf8'); - } - receiver.onclose(code, message, { masked: state.masked }); - receiver.reset(); - }); - receiver.flush(); + message = messageBuffer.toString('utf8'); + } + receiver.onclose(code, message, { masked: receiver.state.masked }); + receiver.reset(); } }, // ping @@ -596,12 +564,8 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.onping(packet, { masked: state.masked, binary: true }); - callback(); - }); - receiver.flush(); + const flags = { masked: receiver.state.masked, binary: true }; + receiver.onping(packet, flags); receiver.endPacket(); } }, @@ -632,12 +596,8 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.onpong(packet, { masked: state.masked, binary: true }); - callback(); - }); - receiver.flush(); + const flags = { masked: receiver.state.masked, binary: true }; + receiver.onpong(packet, flags); receiver.endPacket(); } } From c4c5a0e8b8fccbd74edca8405001740558f072f7 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 19 Oct 2016 20:19:48 +0200 Subject: [PATCH 071/489] Remove applyExtensions and call decompress directly --- lib/Receiver.js | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index b01b2e58f..12ae01a4c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -291,24 +291,6 @@ class Receiver { return this; } - /** - * Apply extensions to message - * - * @api private - */ - - applyExtensions (messageBuffer, fin, callback) { - const extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(messageBuffer, fin, (err, buffer) => { - if (this.dead) return; - if (err) { - callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); - return; - } - callback(null, buffer); - }); - } - /** * Checks payload size, disconnects socket when it exceeds maxPayload * @@ -337,7 +319,9 @@ class Receiver { */ handleDataCompressed (packet) { - this.applyExtensions(packet, this.state.lastFragment, (err, buffer) => { + const extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(packet, this.state.lastFragment, (err, buffer) => { + if (this.dead) return; if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; From faf201a1c1230e8f3b143b348d84124893b5436c Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 19 Oct 2016 20:22:49 +0200 Subject: [PATCH 072/489] Use a shared noop function --- lib/Receiver.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 12ae01a4c..f6d81bdd7 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -11,6 +11,8 @@ const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); +function noop () {} + /** * HyBi Receiver implementation */ @@ -39,12 +41,12 @@ class Receiver { this.expectData(2, this.processPacket); this.dead = false; - this.onerror = function () {}; - this.ontext = function () {}; - this.onbinary = function () {}; - this.onclose = function () {}; - this.onping = function () {}; - this.onpong = function () {}; + this.onerror = noop; + this.ontext = noop; + this.onbinary = noop; + this.onclose = noop; + this.onping = noop; + this.onpong = noop; this.buffers = []; this.bufferedBytes = 0; From c0e62781bd347342a905ee9e9ea54309ac4179df Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 20 Oct 2016 10:02:02 +0200 Subject: [PATCH 073/489] [minor] Coerce `maxPayload` to a number in `Receiver` constructor --- lib/Receiver.js | 51 ++++++++++++++---------------------------------- lib/WebSocket.js | 16 +++------------ 2 files changed, 18 insertions(+), 49 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index f6d81bdd7..d291cc0ca 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -11,12 +11,11 @@ const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); -function noop () {} +const noop = () => {}; /** - * HyBi Receiver implementation + * HyBi Receiver implementation. */ - class Receiver { constructor (extensions, maxPayload) { if (typeof extensions === 'number') { @@ -25,7 +24,7 @@ class Receiver { } this.extensions = extensions || {}; - this.maxPayload = maxPayload || 0; + this.maxPayload = maxPayload | 0; this.currentPayloadLength = 0; this.state = { activeFragmentedOperation: null, @@ -57,7 +56,6 @@ class Receiver { * * @api public */ - add (data) { if (this.dead) return; @@ -72,7 +70,6 @@ class Receiver { * * @api private */ - process () { if (this.expectBytes && this.expectBytes <= this.bufferedBytes) { var bufferForHandler = this.readBuffer(this.expectBytes); @@ -86,7 +83,6 @@ class Receiver { * * @api private */ - readBuffer (bytes) { var dst; var l; @@ -131,7 +127,6 @@ class Receiver { * * @api public */ - cleanup () { this.dead = true; this.expectBytes = 0; @@ -153,7 +148,6 @@ class Receiver { * * @api private */ - expectData (length, handler) { if (length === 0) { handler(null); @@ -170,7 +164,6 @@ class Receiver { * * @api private */ - processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) !== 0) { @@ -230,7 +223,6 @@ class Receiver { * * @api private */ - endPacket () { if (this.dead) return; this.expectBytes = 0; @@ -251,7 +243,6 @@ class Receiver { * * @api private */ - reset () { if (this.dead) return; this.state = { @@ -275,18 +266,16 @@ class Receiver { * * @api private */ - unmask (mask, buf) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); return buf; } /** - * Handles an error + * Handles an error. * * @api private */ - error (err, protocolErrorCode) { this.reset(); this.onerror(err, protocolErrorCode); @@ -294,17 +283,15 @@ class Receiver { } /** - * Checks payload size, disconnects socket when it exceeds maxPayload + * Checks payload size, disconnects socket when it exceeds `maxPayload`. * * @api private */ - maxPayloadExceeded (length) { - if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { - return false; - } + if (this.maxPayload < 1) return false; + const fullLength = this.currentPayloadLength + length; - if (fullLength < this.maxPayload) { + if (fullLength <= this.maxPayload) { this.currentPayloadLength = fullLength; return false; } @@ -319,7 +306,6 @@ class Receiver { * * @api private */ - handleDataCompressed (packet) { const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(packet, this.state.lastFragment, (err, buffer) => { @@ -339,11 +325,9 @@ class Receiver { * * @api private */ - handleData (buffer) { if (buffer != null) { - if (this.maxPayload === 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload)) { + if (this.maxPayload < 1 || this.currentMessageLength + buffer.length <= this.maxPayload) { this.currentMessage.push(buffer); } else { this.currentMessage = []; @@ -361,17 +345,13 @@ class Receiver { this.currentMessageLength = 0; if (this.state.opcode === 2) { - this.onbinary(messageBuffer, { - masked: this.state.masked - }); + this.onbinary(messageBuffer, { masked: this.state.masked }); } else { if (!Validation.isValidUTF8(messageBuffer)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(messageBuffer.toString('utf8'), { - masked: this.state.masked - }); + this.ontext(messageBuffer.toString(), { masked: this.state.masked }); } } } @@ -379,10 +359,9 @@ class Receiver { module.exports = Receiver; -/** - * Opcode handlers - */ - +// +// Opcode handlers. +// const opcodes = { // text '1': { @@ -517,7 +496,7 @@ const opcodes = { receiver.error('invalid utf8 sequence', 1007); return; } - message = messageBuffer.toString('utf8'); + message = messageBuffer.toString(); } receiver.onclose(code, message, { masked: receiver.state.masked }); receiver.reset(); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 06f91baf2..f4a63d910 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -537,22 +537,12 @@ function buildHostHeader (isSecure, hostname, port) { * which may or may not be bound to a sepcific WebSocket instance. */ function initAsServerClient (req, socket, upgradeHead, options) { - options = Object.assign({ - protocolVersion: protocolVersion, - protocol: null, - extensions: {}, - maxPayload: 0 - }, options); - // expose state properties - this.protocol = options.protocol; - this.protocolVersion = options.protocolVersion; - this.extensions = options.extensions; - this.supports.binary = (this.protocolVersion !== 'hixie-76'); - this.upgradeReq = req; + Object.assign(this, options); + this.supports.binary = this.protocolVersion !== 'hixie-76'; this.readyState = WebSocket.CONNECTING; + this.upgradeReq = req; this._isServer = true; - this.maxPayload = options.maxPayload; // establish connection if (options.protocolVersion === 'hixie-76') { establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); From 5f53194a3192db730c6043771a0abbdf4edd420f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 20 Oct 2016 14:08:55 +0200 Subject: [PATCH 074/489] [major] Make `extensions` a required argument for `Receiver` constructor --- lib/Receiver.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index d291cc0ca..eea740171 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -18,25 +18,20 @@ const noop = () => {}; */ class Receiver { constructor (extensions, maxPayload) { - if (typeof extensions === 'number') { - maxPayload = extensions; - extensions = {}; - } - this.extensions = extensions || {}; this.maxPayload = maxPayload | 0; - this.currentPayloadLength = 0; this.state = { activeFragmentedOperation: null, + fragmentedOperation: false, lastFragment: false, masked: false, - opcode: 0, - fragmentedOperation: false + opcode: 0 }; this.expectBytes = 0; this.expectHandler = null; this.currentMessage = []; this.currentMessageLength = 0; + this.currentPayloadLength = 0; this.expectData(2, this.processPacket); this.dead = false; From b7ae3b49218fb58fe2af2560d010f6691671efff Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 20 Oct 2016 20:10:47 +0200 Subject: [PATCH 075/489] [test] Clean up `Receiver` tests --- bench/util.js | 19 +- test/Receiver.test.js | 594 ++++++++++++++++++++++-------------------- 2 files changed, 322 insertions(+), 291 deletions(-) diff --git a/bench/util.js b/bench/util.js index 7363e8b29..47446b0de 100644 --- a/bench/util.js +++ b/bench/util.js @@ -7,12 +7,12 @@ 'use strict'; /** - * Performs hybi07+ type masking on a hex string or buffer. + * Performs hybi07+ type masking. */ function mask (buf, maskString) { const _mask = Buffer.from(maskString || '3483a868', 'hex'); - if (typeof buf === 'string') buf = Buffer.from(buf); + buf = Buffer.from(buf); for (var i = 0; i < buf.length; ++i) { buf[i] ^= _mask[i % 4]; @@ -54,4 +54,17 @@ function getHybiLengthAsHexString (len, masked) { return s; } -module.exports = { getHybiLengthAsHexString, mask, pack }; +/** + * Split a buffer in two. + */ +function splitBuffer (buf) { + const i = Math.floor(buf.length / 2); + return [buf.slice(0, i), buf.slice(i)]; +} + +module.exports = { + getHybiLengthAsHexString, + splitBuffer, + mask, + pack +}; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 9c3343bf7..0138ddedc 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -1,423 +1,441 @@ -var assert = require('assert') - , Receiver = require('../lib/Receiver') - , PerMessageDeflate = require('../lib/PerMessageDeflate'); -require('should'); -require('./hybi-common'); - -describe('Receiver', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var p = Receiver(); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } +'use strict'; + +const assert = require('assert'); +const crypto = require('crypto'); + +const PerMessageDeflate = require('../lib/PerMessageDeflate'); +const Receiver = require('../lib/Receiver'); +const util = require('../bench/util'); + +describe('Receiver', function () { + describe('#ctor', function () { + it('throws TypeError when called without new', function () { + assert.throws(Receiver, TypeError); }); }); - it('can parse unmasked text message', function() { - var p = new Receiver(); - var packet = '81 05 48 65 6c 6c 6f'; + it('can parse unmasked text message', function (done) { + const p = new Receiver(); - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('Hello', data); + p.ontext = function (data) { + assert.strictEqual(data, 'Hello'); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from('810548656c6c6f', 'hex')); }); - it('can parse close message', function() { - var p = new Receiver(); - var packet = '88 00'; - var gotClose = false; - p.onclose = function(data) { - gotClose = true; + it('can parse close message', function (done) { + const p = new Receiver(); + + p.onclose = function (code, data) { + assert.strictEqual(code, 1000); + assert.strictEqual(data, ''); + done(); }; - p.add(getBufferFromHexString(packet)); - gotClose.should.be.ok; + p.add(Buffer.from('8800', 'hex')); }); - it('can parse masked text message', function() { - var p = new Receiver(); - var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('5:::{"name":"echo"}', data); + + it('can parse masked text message', function (done) { + const p = new Receiver(); + + p.ontext = function (data) { + assert.strictEqual(data, '5:::{"name":"echo"}'); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex')); }); - it('can parse a masked text message longer than 125 bytes', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + + it('can parse a masked text message longer than 125 bytes', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(200); + + const mask = '3483a868'; + const frame = '81FE' + util.pack(4, msg.length) + mask + + util.mask(msg, mask).toString('hex'); + + p.ontext = function (data) { + assert.strictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a really long masked text message', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString(); - var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + + it('can parse a really long masked text message', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(64 * 1024); + + const mask = '3483a868'; + const frame = '81FF' + util.pack(16, msg.length) + mask + + util.mask(msg, mask).toString('hex'); + + p.ontext = function (data) { + assert.strictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - var msgpiece1 = message.substr(0, 150); - var msgpiece2 = message.substr(150); - var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); - var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + + it('can parse a fragmented masked text message of 300 bytes', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(300); + + const fragment1 = msg.substr(0, 150); + const fragment2 = msg.substr(150); + + const mask = '3483a868'; + const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + + util.mask(fragment1, mask).toString('hex'); + const frame2 = '80FE' + util.pack(4, fragment2.length) + mask + + util.mask(fragment2, mask).toString('hex'); + + p.ontext = function (data) { + assert.strictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet1)); - p.add(getBufferFromHexString(packet2)); - gotData.should.be.ok; + p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from(frame2, 'hex')); }); - it('can parse a ping message', function() { - var p = new Receiver(); - var message = 'Hello'; - var packet = '89 ' + getHybiLengthAsHexString(message.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - var gotPing = false; - p.onping = function(data) { - gotPing = true; - assert.equal(message, data); + it('can parse a ping message', function (done) { + const p = new Receiver(); + const msg = 'Hello'; + + const mask = '3483a868'; + const frame = '89' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onping = function (data) { + assert.strictEqual(data.toString(), msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotPing.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a ping with no data', function() { - var p = new Receiver(); - var packet = '89 00'; - var gotPing = false; - p.onping = function(data) { - gotPing = true; + it('can parse a ping with no data', function (done) { + const p = new Receiver(); + + p.onping = function (data) { + assert.strictEqual(data, null); + done(); }; - p.add(getBufferFromHexString(packet)); - gotPing.should.be.ok; + p.add(Buffer.from('8900', 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - var msgpiece1 = message.substr(0, 150); - var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); + it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (1/2)', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(300); + const pingMessage = 'Hello'; - var pingMessage = 'Hello'; - var pingPacket = '89 ' + getHybiLengthAsHexString(pingMessage.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68')); + const fragment1 = msg.substr(0, 150); + const fragment2 = msg.substr(150); + + const mask = '3483a868'; + const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + + util.mask(fragment1, mask).toString('hex'); + const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + + util.mask(pingMessage, mask).toString('hex'); + const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + + util.mask(fragment2, mask).toString('hex'); - var msgpiece2 = message.substr(150); - var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); + let gotPing = false; - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + p.ontext = function (data) { + assert.strictEqual(data, msg); + assert.ok(gotPing); + done(); }; - var gotPing = false; - p.onping = function(data) { + p.onping = function (data) { gotPing = true; - assert.equal(pingMessage, data); + assert.strictEqual(data.toString(), pingMessage); }; - p.add(getBufferFromHexString(packet1)); - p.add(getBufferFromHexString(pingPacket)); - p.add(getBufferFromHexString(packet2)); - gotData.should.be.ok; - gotPing.should.be.ok; + p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from(frame2, 'hex')); + p.add(Buffer.from(frame3, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - - var msgpiece1 = message.substr(0, 150); - var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); + it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (2/2)', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(300); var pingMessage = 'Hello'; - var pingPacket = '89 ' + getHybiLengthAsHexString(pingMessage.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68')); - var msgpiece2 = message.substr(150); - var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); + var fragment1 = msg.substr(0, 150); + var fragment2 = msg.substr(150); + + const mask = '3483a868'; + const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + + util.mask(fragment1, mask).toString('hex'); + const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + + util.mask(pingMessage, mask).toString('hex'); + const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + + util.mask(fragment2, mask).toString('hex'); + + let buffers = []; + + buffers = buffers.concat(util.splitBuffer(Buffer.from(frame1, 'hex'))); + buffers = buffers.concat(util.splitBuffer(Buffer.from(frame2, 'hex'))); + buffers = buffers.concat(util.splitBuffer(Buffer.from(frame3, 'hex'))); - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + let gotPing = false; + + p.ontext = function (data) { + assert.strictEqual(data, msg); + assert.ok(gotPing); + done(); }; - var gotPing = false; - p.onping = function(data) { + p.onping = function (data) { gotPing = true; - assert.equal(pingMessage, data); + assert.strictEqual(data.toString(), pingMessage); }; - var buffers = []; - buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1))); - buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket))); - buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2))); for (var i = 0; i < buffers.length; ++i) { p.add(buffers[i]); } - gotData.should.be.ok; - gotPing.should.be.ok; }); - it('can parse a 100 byte long masked binary message', function() { - var p = new Receiver(); - var length = 100; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 100 byte long masked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(100); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data.toString('hex'), msg.toString('hex')); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 256 byte long masked binary message', function() { - var p = new Receiver(); - var length = 256; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 256 byte long masked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(256); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long masked binary message', function() { - var p = new Receiver(); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 200kb long masked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(200 * 1024); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long unmasked binary message', function() { - var p = new Receiver(); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 200kb long unmasked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(200 * 1024); + + const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + + msg.toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse compressed message', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + + it('can parse compressed message', function (done) { + const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - var buf = new Buffer('Hello'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); - p.ontext = function(data) { - assert.equal('Hello', data); + p.ontext = function (data) { + assert.strictEqual(data, 'Hello'); done(); }; - perMessageDeflate.compress(buf, true, function(err, compressed) { + perMessageDeflate.compress(buf, true, function (err, compressed) { if (err) return done(err); - p.add(new Buffer([0xc1, compressed.length])); + + p.add(Buffer.from([0xc1, compressed.length])); p.add(compressed); }); }); - it('can parse compressed fragments', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + + it('can parse compressed fragments', function (done) { + const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - var buf1 = new Buffer('foo'); - var buf2 = new Buffer('bar'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf1 = Buffer.from('foo'); + const buf2 = Buffer.from('bar'); - p.ontext = function(data) { - assert.equal('foobar', data); + p.ontext = function (data) { + assert.strictEqual(data, 'foobar'); done(); }; - perMessageDeflate.compress(buf1, false, function(err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, compressed1) { if (err) return done(err); - p.add(new Buffer([0x41, compressed1.length])); + + p.add(Buffer.from([0x41, compressed1.length])); p.add(compressed1); - perMessageDeflate.compress(buf2, true, function(err, compressed2) { - p.add(new Buffer([0x80, compressed2.length])); + perMessageDeflate.compress(buf2, true, function (err, compressed2) { + if (err) return done(err); + + p.add(Buffer.from([0x80, compressed2.length])); p.add(compressed2); }); }); }); - it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function() { - var p = new Receiver(20480); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotError = false; - p.error = function(reason,code) { - gotError = true; - assert.equal(code, 1009); + + it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function (done) { + const p = new Receiver({}, 20 * 1024); + const msg = crypto.randomBytes(200 * 1024); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.error = function (reason, code) { + assert.strictEqual(code, 1009); + done(); }; - p.add(getBufferFromHexString(packet)); - gotError.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function() { - var p = new Receiver(20480); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message); - - var gotError = false; - p.error = function(reason,code) { - gotError = true; - assert.equal(code, 1009); + + it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function (done) { + const p = new Receiver({}, 20 * 1024); + const msg = crypto.randomBytes(200 * 1024); + + const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + + msg.toString('hex'); + + p.error = function (reason, code) { + assert.strictEqual(code, 1009); + done(); }; - p.add(getBufferFromHexString(packet)); - gotError.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a compressed message that exceeds maxpayload of 3bytes', function(done) { - var perMessageDeflate = new PerMessageDeflate({},false,3); + + it('will raise an error on a compressed message that exceeds maxpayload of 3 bytes', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 3); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate },3); - var buf = new Buffer('Hellooooooooooooooooooooooooooooooooooooooo'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 3); + const buf = Buffer.from('Hellooooooooooooooooooooooooooooooooooooooo'); - p.onerror = function(reason,code) { - assert.equal(code, 1009); + p.onerror = function (reason, code) { + assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf, true, function(err, compressed) { + perMessageDeflate.compress(buf, true, function (err, compressed) { if (err) return done(err); - p.add(new Buffer([0xc1, compressed.length])); + + p.add(Buffer.from([0xc1, compressed.length])); p.add(compressed); }); }); - it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function(done) { - var perMessageDeflate = new PerMessageDeflate({},false,2); + + it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 2); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2); - var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo'); - var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); + const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); + const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); - p.onerror = function(reason,code) { - assert.equal(code, 1009); + p.onerror = function (reason, code) { + assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf1, false, function(err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, compressed1) { if (err) return done(err); - p.add(new Buffer([0x41, compressed1.length])); + + p.add(Buffer.from([0x41, compressed1.length])); p.add(compressed1); - perMessageDeflate.compress(buf2, true, function(err, compressed2) { - p.add(new Buffer([0x80, compressed2.length])); + perMessageDeflate.compress(buf2, true, function (err, compressed2) { + if (err) return done(err); + + p.add(Buffer.from([0x80, compressed2.length])); p.add(compressed2); }); }); }); - it('will not crash if another message is received after receiving a message that exceeds maxpayload', function(done) { - var perMessageDeflate = new PerMessageDeflate({},false,2); + + it('will not crash if another message is received after receiving a message that exceeds maxpayload', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 2); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2); - var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo'); - var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); + const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); + const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + + let gotError = false; - p.onerror = function(reason,code) { - assert.equal(code, 1009); + p.onerror = function (reason, code) { + gotError = true; + assert.strictEqual(code, 1009); }; - perMessageDeflate.compress(buf1, false, function(err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, compressed1) { if (err) return done(err); - p.add(new Buffer([0x41, compressed1.length])); + + p.add(Buffer.from([0x41, compressed1.length])); p.add(compressed1); - assert.equal(p.onerror,null); + assert.ok(gotError); + assert.strictEqual(p.onerror, null); - perMessageDeflate.compress(buf2, true, function(err, compressed2) { - p.add(new Buffer([0x80, compressed2.length])); - p.add(compressed2); - done(); + perMessageDeflate.compress(buf2, true, function (err, compressed2) { + if (err) return done(err); + + p.add(Buffer.from([0x80, compressed2.length])); + p.add(compressed2); + done(); }); }); }); - it('can cleanup during consuming data', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + + it('can cleanup when consuming data', function (done) { + const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - var buf = new Buffer('Hello'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); - perMessageDeflate.compress(buf, true, function(err, compressed) { + perMessageDeflate.compress(buf, true, function (err, compressed) { if (err) return done(err); - var data = Buffer.concat([new Buffer([0xc1, compressed.length]), compressed]); + + const data = Buffer.concat([Buffer.from([0xc1, compressed.length]), compressed]); p.add(data); p.add(data); p.add(data); From 78425d0c3360acd67280f33cab89c84a46d3eb0d Mon Sep 17 00:00:00 2001 From: Dirk Krause Date: Wed, 19 Oct 2016 21:56:25 +0200 Subject: [PATCH 076/489] [doc] Add broadcast to everyone else example --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 93106d7a3..1e189e3de 100644 --- a/README.md +++ b/README.md @@ -128,11 +128,21 @@ server.listen(port, function () { console.log('Listening on ' + server.address() var WebSocketServer = require('ws').Server , wss = new WebSocketServer({ port: 8080 }); +// Broadcast to all. wss.broadcast = function broadcast(data) { wss.clients.forEach(function each(client) { client.send(data); }); }; + +wss.on('connection', function connection(ws) { + ws.on('message', function message(data) { + // Broadcast to everyone else. + wss.clients.forEach(function each(client) { + if (client !== ws) client.send(data); + }); + }); +}); ``` ### Error handling best practices From ea50be7ab12ad028b41a315e924aa8c442c095d7 Mon Sep 17 00:00:00 2001 From: codingphil Date: Wed, 5 Feb 2014 11:18:32 +0100 Subject: [PATCH 077/489] [fix] Do not override the `fin` option of the `send` method --- lib/WebSocket.js | 2 +- test/WebSocket.test.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f4a63d910..a0a292ff7 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -230,7 +230,7 @@ WebSocket.prototype.send = function send (data, options, cb) { } options = options || {}; - options.fin = true; + if (options.fin !== false) options.fin = true; if (typeof options.binary === 'undefined') { options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index f6937833d..6049b4dda 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -748,6 +748,24 @@ describe('WebSocket', function() { }); }); + it('does not override the `fin` option', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { + ws.send('fragment', { fin: false }); + ws.send('fragment', { fin: true }); + }); + }); + + wss.on('connection', (ws) => { + ws.on('message', (msg) => { + assert.strictEqual(msg, 'fragmentfragment'); + wss.close(done); + }); + }); + }); + it('send and receive binary data as an array', function(done) { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); From dbfa5bab1f4413fc484b258192b2aba338a2e2d8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 22 Oct 2016 16:02:40 +0200 Subject: [PATCH 078/489] [benchmark] Add 1 MiB test to the benchmark suite --- bench/parser.benchmark.js | 4 +++- bench/sender.benchmark.js | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 3d795d338..bbda00293 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -29,6 +29,7 @@ const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024); +const binaryDataPacket4 = createBinaryPacket(1024 * 1024); var receiver = new Receiver({}, 1024 * 1024); const suite = new benchmark.Suite(); @@ -42,7 +43,8 @@ suite.add('close message', () => { suite.add('masked text message', () => receiver.add(maskedTextPacket)); suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); -suite.add('binary data (200 kB)', () => receiver.add(binaryDataPacket3)); +suite.add('binary data (200 KiB)', () => receiver.add(binaryDataPacket3)); +suite.add('binary data (1 MiB)', () => receiver.add(binaryDataPacket4)); suite.on('cycle', (e) => { console.log(e.target.toString()); receiver = new Receiver(); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index e0ac8d31b..11e856a33 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -10,14 +10,17 @@ const benchmark = require('benchmark'); const Sender = require('../').Sender; -const framePacket = Buffer.alloc(200 * 1024).fill(99); +const data1 = Buffer.alloc(200 * 1024, 99); +const data2 = Buffer.alloc(1024 * 1024, 99); const suite = new benchmark.Suite(); var sender = new Sender(); sender._socket = { write () {} }; -suite.add('frameAndSend, unmasked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, false)); -suite.add('frameAndSend, masked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, true)); +suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, false)); +suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, true)); +suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, false)); +suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, true)); suite.on('cycle', (e) => { console.log(e.target.toString()); sender = new Sender(); From c60078f4fa2a7f4558416e479017173209ae547f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 22 Oct 2016 16:43:29 +0200 Subject: [PATCH 079/489] [minor] Remove duplicated cleanup code `Receiver.prototype.reset()`, called by `Receiver.prototype.error()`, does the same cleanup. --- lib/Receiver.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index eea740171..66607f17b 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -323,14 +323,12 @@ class Receiver { handleData (buffer) { if (buffer != null) { if (this.maxPayload < 1 || this.currentMessageLength + buffer.length <= this.maxPayload) { + this.currentMessageLength += buffer.length; this.currentMessage.push(buffer); } else { - this.currentMessage = []; - this.currentMessageLength = 0; this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); return; } - this.currentMessageLength += buffer.length; } if (this.state.lastFragment) { const messageBuffer = this.currentMessage.length === 1 From 4ffadc21af35633f634dd64a7be04fec096688a1 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 22 Oct 2016 22:25:34 +0200 Subject: [PATCH 080/489] Use a common buffer for the fixed trailing bytes in deflate --- lib/PerMessageDeflate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 4efae7971..3b2e4d76e 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -5,6 +5,7 @@ const zlib = require('zlib'); const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; +const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); /** * Per-message Compression Extensions implementation @@ -234,7 +235,7 @@ class PerMessageDeflate { this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); if (fin) { - this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); + this._inflate.write(TRAILER); } this._inflate.flush(function () { cleanup(); From 63314f7d1c9942b1e5d937f50619f635097acfee Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 22 Oct 2016 22:27:33 +0200 Subject: [PATCH 081/489] Remove unnecessary checks --- lib/PerMessageDeflate.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 3b2e4d76e..8ae51f356 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -74,7 +74,7 @@ class PerMessageDeflate { if (this._inflate.writeInProgress) { this._inflate.pendingClose = true; } else { - if (this._inflate.close) this._inflate.close(); + this._inflate.close(); this._inflate = null; } } @@ -82,7 +82,7 @@ class PerMessageDeflate { if (this._deflate.writeInProgress) { this._deflate.pendingClose = true; } else { - if (this._deflate.close) this._deflate.close(); + this._deflate.close(); this._deflate = null; } } @@ -268,7 +268,7 @@ class PerMessageDeflate { self._inflate.removeListener('data', onData); self._inflate.writeInProgress = false; if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { - if (self._inflate.close) self._inflate.close(); + self._inflate.close(); self._inflate = null; } } @@ -322,7 +322,7 @@ class PerMessageDeflate { self._deflate.removeListener('data', onData); self._deflate.writeInProgress = false; if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { - if (self._deflate.close) self._deflate.close(); + self._deflate.close(); self._deflate = null; } } From 6b3904b42dbd48aed2e0d8d599787cca04f05384 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 21 Oct 2016 19:45:38 +0200 Subject: [PATCH 082/489] Add threshold option to compression. --- doc/ws.md | 1 + lib/PerMessageDeflate.js | 1 + lib/Sender.js | 43 ++++++++++++++++++---------------------- test/Sender.test.js | 21 +++++++++++++++++--- test/WebSocket.test.js | 2 +- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 401688806..7740866ad 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -63,6 +63,7 @@ If `handleProtocols` is not set then the handshake is accepted regardless the va * `serverMaxWindowBits` Number: The value of windowBits. * `clientMaxWindowBits` Number: The value of max windowBits to be requested to clients. * `memLevel` Number: The value of memLevel. +* `threshold` Number: Payloads smaller than this will not be compressed. Default 1024 bytes. If a property is empty then either an offered configuration or a default value is used. diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 8ae51f356..397377b91 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -18,6 +18,7 @@ class PerMessageDeflate { this._deflate = null; this.params = null; this._maxPayload = maxPayload || 0; + this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold; } /** diff --git a/lib/Sender.js b/lib/Sender.js index f68036a58..5e2535c4c 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -78,7 +78,7 @@ class Sender { */ doPing (data, options) { var mask = options && options.mask; - this.frameAndSend(0x9, data || '', true, mask); + this.frameAndSend(0x9, data ? Buffer.from(data.toString()) : null, true, mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -104,7 +104,7 @@ class Sender { */ doPong (data, options) { var mask = options && options.mask; - this.frameAndSend(0xa, data || '', true, mask); + this.frameAndSend(0xa, data ? Buffer.from(data.toString()) : null, true, mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -129,6 +129,17 @@ class Sender { } if (finalFragment) this.firstFragment = true; + if (data && !Buffer.isBuffer(data)) { + if ((data.buffer || data) instanceof ArrayBuffer) { + data = getBufferFromNative(data); + } else { + if (typeof data === 'number') { + data = data.toString(); + } + data = Buffer.from(data); + } + } + if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); } else { @@ -142,6 +153,11 @@ class Sender { * @api private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { + if (data && data.length < this.extensions[PerMessageDeflate.extensionName].threshold) { + this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + this.messageHandlerCallback(); + return; + } this.applyExtensions(data, finalFragment, this.compress, (err, data) => { if (err) { if (cb) cb(err); @@ -159,8 +175,6 @@ class Sender { * @api private */ frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { - var canModifyData = false; - if (!data) { var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] .concat(maskData ? [0, 0, 0, 0] : []); @@ -168,23 +182,6 @@ class Sender { return; } - if (!Buffer.isBuffer(data)) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } else { - canModifyData = true; - // - // If people want to send a number, this would allocate the number in - // bytes as memory size instead of storing the number as buffer value. So - // we need to transform it to string in order to prevent possible - // vulnerabilities / memory attacks. - // - if (typeof data === 'number') data = data.toString(); - - data = new Buffer(data); - } - } - var dataLength = data.length; var dataOffset = maskData ? 6 : 2; var secondByte = dataLength; @@ -197,6 +194,7 @@ class Sender { secondByte = 126; } + var canModifyData = opcode === 1 || compressed; var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; var outputBuffer = new Buffer(totalLength); @@ -276,9 +274,6 @@ class Sender { */ applyExtensions (data, fin, compress, callback) { if (compress && data) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); } else { callback(null, data); diff --git a/test/Sender.test.js b/test/Sender.test.js index 42b23ff7d..dc82c4eb0 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -30,7 +30,7 @@ describe('Sender', function() { it('does not modify a masked text buffer', function() { var sender = new Sender({ write: function() {} }); var text = 'hi there'; - sender.frameAndSend(1, text, true, true); + sender.frameAndSend(1, Buffer.from(text), true, true); text.should.eql('hi there'); }); @@ -41,13 +41,13 @@ describe('Sender', function() { done(); } }); - sender.frameAndSend(1, 'hi', true, false, true); + sender.frameAndSend(1, Buffer.from('hi'), true, false, true); }); }); describe('#send', function() { it('compresses data if compress option is enabled', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + var perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); perMessageDeflate.accept([{}]); var sender = new Sender({ @@ -61,6 +61,21 @@ describe('Sender', function() { sender.send('hi', { compress: true }); }); + it('does not compress data for small payloads', function(done) { + var perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + var sender = new Sender({ + write: function(data) { + (data[0] & 0x40).should.not.equal(0x40); + done(); + } + }, { + 'permessage-deflate': perMessageDeflate + }); + sender.send('hi', { compress: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function(done) { var messageCount = 0; var maxMessages = 5000; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 6049b4dda..2c72f2eaa 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -2226,7 +2226,7 @@ describe('WebSocket', function() { describe('#terminate', function() { it('will raise error callback, if any, if called during send data', function(done) { var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: { threshold: 0 }}); var errorGiven = false; ws.on('open', function() { ws.send('hi', function(error) { From 545635d2e616fc4c0b7693de1a33be93e078f32d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 23 Oct 2016 18:29:09 +0200 Subject: [PATCH 083/489] [fix] Reset `currentPayloadLength` only on final frame --- lib/Receiver.js | 7 +++-- test/Receiver.test.js | 61 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 66607f17b..fcfed88fa 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -226,9 +226,12 @@ class Receiver { // end current fragmented operation this.state.activeFragmentedOperation = null; } - this.currentPayloadLength = 0; + if (this.state.activeFragmentedOperation !== null) { + this.state.opcode = this.state.activeFragmentedOperation; + } else { + this.currentPayloadLength = this.state.opcode = 0; + } this.state.lastFragment = false; - this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; this.state.masked = false; this.expectData(2, this.processPacket); } diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 0138ddedc..6e548f7d2 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -48,7 +48,7 @@ describe('Receiver', function () { p.add(Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex')); }); - it('can parse a masked text message longer than 125 bytes', function (done) { + it('can parse a masked text message longer than 125 B', function (done) { const p = new Receiver(); const msg = 'A'.repeat(200); @@ -80,7 +80,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes', function (done) { + it('can parse a fragmented masked text message of 300 B', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); @@ -129,7 +129,7 @@ describe('Receiver', function () { p.add(Buffer.from('8900', 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (1/2)', function (done) { + it('can parse a fragmented masked text message of 300 B with a ping in the middle (1/2)', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -162,7 +162,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame3, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (2/2)', function (done) { + it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); var pingMessage = 'Hello'; @@ -201,7 +201,7 @@ describe('Receiver', function () { } }); - it('can parse a 100 byte long masked binary message', function (done) { + it('can parse a 100 B long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(100); @@ -217,7 +217,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 256 byte long masked binary message', function (done) { + it('can parse a 256 B long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(256); @@ -233,7 +233,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long masked binary message', function (done) { + it('can parse a 200 KiB long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -249,7 +249,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long unmasked binary message', function (done) { + it('can parse a 200 KiB long unmasked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -312,7 +312,44 @@ describe('Receiver', function () { }); }); - it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function (done) { + it('resets `currentPayloadLength` only on final frame (unfragmented)', function () { + const p = new Receiver({}, 10); + + assert.strictEqual(p.currentPayloadLength, 0); + p.add(Buffer.from('810548656c6c6f', 'hex')); + assert.strictEqual(p.currentPayloadLength, 0); + }); + + it('resets `currentPayloadLength` only on final frame (fragmented)', function () { + const p = new Receiver({}, 10); + + const frame1 = '01024865'; + const frame2 = '80036c6c6f'; + + assert.strictEqual(p.currentPayloadLength, 0); + p.add(Buffer.from(frame1, 'hex')); + assert.strictEqual(p.currentPayloadLength, 2); + p.add(Buffer.from(frame2, 'hex')); + assert.strictEqual(p.currentPayloadLength, 0); + }); + + it('resets `currentPayloadLength` only on final frame (fragmented + ping)', function () { + const p = new Receiver({}, 10); + + const frame1 = '01024865'; + const frame2 = '8900'; + const frame3 = '80036c6c6f'; + + assert.strictEqual(p.currentPayloadLength, 0); + p.add(Buffer.from(frame1, 'hex')); + assert.strictEqual(p.currentPayloadLength, 2); + p.add(Buffer.from(frame2, 'hex')); + assert.strictEqual(p.currentPayloadLength, 2); + p.add(Buffer.from(frame3, 'hex')); + assert.strictEqual(p.currentPayloadLength, 0); + }); + + it('will raise an error on a 200 KiB long masked binary message when maxpayload is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -328,7 +365,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function (done) { + it('will raise an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -343,7 +380,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a compressed message that exceeds maxpayload of 3 bytes', function (done) { + it('will raise an error on a compressed message that exceeds maxpayload of 3 B', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 3); perMessageDeflate.accept([{}]); @@ -363,7 +400,7 @@ describe('Receiver', function () { }); }); - it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function (done) { + it('will raise an error on a compressed fragment that exceeds maxpayload of 2 B', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 2); perMessageDeflate.accept([{}]); From 27e15ed13b144d070fe24b78e0484fb916dbe03a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 23 Oct 2016 18:33:46 +0200 Subject: [PATCH 084/489] [minor] Remove redundant code --- lib/Receiver.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index fcfed88fa..b35756836 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -220,8 +220,6 @@ class Receiver { */ endPacket () { if (this.dead) return; - this.expectBytes = 0; - this.expectHandler = null; if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { // end current fragmented operation this.state.activeFragmentedOperation = null; From f026859be3568f880056b4e75aa6d47200526615 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 23 Oct 2016 18:41:44 +0200 Subject: [PATCH 085/489] [minor] Remove unnecessary `fragmentedOperation` flag --- lib/Receiver.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index b35756836..c5c527729 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -22,7 +22,6 @@ class Receiver { this.maxPayload = maxPayload | 0; this.state = { activeFragmentedOperation: null, - fragmentedOperation: false, lastFragment: false, masked: false, opcode: 0 @@ -181,7 +180,6 @@ class Receiver { return; } // continuation frame - this.state.fragmentedOperation = true; this.state.opcode = this.state.activeFragmentedOperation; if (!(this.state.opcode === 1 || this.state.opcode === 2)) { this.error(new Error('continuation frame cannot follow current opcode'), 1002); @@ -199,10 +197,7 @@ class Receiver { this.state.compressed = compressed; this.state.opcode = opcode; if (this.state.lastFragment === false) { - this.state.fragmentedOperation = true; this.state.activeFragmentedOperation = opcode; - } else { - this.state.fragmentedOperation = false; } } const handler = opcodes[this.state.opcode]; @@ -245,8 +240,7 @@ class Receiver { activeFragmentedOperation: null, lastFragment: false, masked: false, - opcode: 0, - fragmentedOperation: false + opcode: 0 }; this.expectBytes = 0; this.expectHandler = null; From d6717423f839d9929a6c49b37a7060ebfa6aced0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 08:26:12 +0200 Subject: [PATCH 086/489] [major] Drop support for Hixie-76 (#871) --- README.md | 3 - doc/ws.md | 5 +- lib/Receiver.hixie.js | 170 ---------------- lib/Sender.hixie.js | 112 ----------- lib/WebSocket.js | 20 +- lib/WebSocketServer.js | 193 +----------------- package.json | 2 - test/Receiver.hixie.test.js | 170 ---------------- test/Sender.hixie.test.js | 146 -------------- test/WebSocket.test.js | 25 +-- test/WebSocketServer.test.js | 379 +++-------------------------------- 11 files changed, 47 insertions(+), 1178 deletions(-) delete mode 100644 lib/Receiver.hixie.js delete mode 100644 lib/Sender.hixie.js delete mode 100644 test/Receiver.hixie.test.js delete mode 100644 test/Sender.hixie.test.js diff --git a/README.md b/README.md index 1e189e3de..3d7863d60 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,6 @@ for the full reports. ## Protocol support -* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. - Added to ws version 0.4.2, but server only. Can be disabled by setting the - `disableHixie` option to true.) * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) * **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) diff --git a/doc/ws.md b/doc/ws.md index 7740866ad..ce9f58ffd 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -14,7 +14,6 @@ This class is a WebSocket server. It is an `EventEmitter`. * `handleProtocols` Function * `path` String * `noServer` Boolean - * `disableHixie` Boolean * `clientTracking` Boolean * `perMessageDeflate` Boolean|Object * `callback` Function @@ -108,7 +107,7 @@ This class represents a WebSocket connection. It is an `EventEmitter`. * `protocol` String * `agent` Agent * `headers` Object - * `protocolVersion` Number|String + * `protocolVersion` Number -- the following only apply if `address` is a String * `host` String * `origin` String @@ -138,7 +137,7 @@ Possible states are `WebSocket.CONNECTING`, `WebSocket.OPEN`, `WebSocket.CLOSING ### websocket.protocolVersion -The WebSocket protocol version used for this connection, `8`, `13` or `hixie-76` (the latter only for server clients). +The WebSocket protocol version used for this connection, `8`, `13`. ### websocket.url diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js deleted file mode 100644 index cda7ecdb6..000000000 --- a/lib/Receiver.hixie.js +++ /dev/null @@ -1,170 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -/** - * State constants - */ - -const EMPTY = 0; -const BODY = 1; -const BINARYLENGTH = 2; -const BINARYBODY = 3; - -/** - * Hixie Receiver implementation - */ - -class Receiver { - constructor () { - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - this.dead = false; - - this.onerror = function () {}; - this.ontext = function () {}; - this.onbinary = function () {}; - this.onclose = function () {}; - this.onping = function () {}; - this.onpong = function () {}; - } - - /** - * Add new data to the parser. - * - * @api public - */ - - add (data) { - var self = this; - function doAdd () { - if (self.state === EMPTY) { - if (data.length === 2 && data[0] === 0xFF && data[1] === 0x00) { - self.reset(); - self.onclose(); - return; - } - if (data[0] === 0x80) { - self.messageEnd = 0; - self.state = BINARYLENGTH; - data = data.slice(1); - } else { - if (data[0] !== 0x00) { - self.error(new Error('payload must start with 0x00 byte'), true); - return; - } - data = data.slice(1); - self.state = BODY; - } - } - if (self.state === BINARYLENGTH) { - var i = 0; - while ((i < data.length) && (data[i] & 0x80)) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - ++i; - } - if (i < data.length) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - self.state = BINARYBODY; - ++i; - } - if (i > 0) { - data = data.slice(i); - } - } - if (self.state === BINARYBODY) { - var dataleft = self.messageEnd - self.spanLength; - if (data.length >= dataleft) { - // consume the whole buffer to finish the frame - self.buffers.push(data); - self.spanLength += dataleft; - self.messageEnd = dataleft; - return self.parse(); - } - // frame's not done even if we consume it all - self.buffers.push(data); - self.spanLength += data.length; - return; - } - self.buffers.push(data); - if ((self.messageEnd = data.indexOf(0xFF)) !== -1) { - self.spanLength += self.messageEnd; - return self.parse(); - } else { - self.spanLength += data.length; - } - } - while (data) data = doAdd(); - } - - /** - * Releases all resources used by the receiver. - * - * @api public - */ - - cleanup () { - this.dead = true; - this.state = EMPTY; - this.buffers = []; - } - - /** - * Process buffered data. - * - * @api public - */ - - parse () { - var output = new Buffer(this.spanLength); - var outputIndex = 0; - for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { - var buffer = this.buffers[bi]; - buffer.copy(output, outputIndex); - outputIndex += buffer.length; - } - var lastBuffer = this.buffers[this.buffers.length - 1]; - if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); - if (this.state !== BODY) --this.messageEnd; - var tail = null; - if (this.messageEnd < lastBuffer.length - 1) { - tail = lastBuffer.slice(this.messageEnd + 1); - } - this.reset(); - this.ontext(output.toString('utf8')); - return tail; - } - - /** - * Handles an error - * - * @api private - */ - - error (err, terminate) { - this.reset(); - this.onerror(err, terminate); - return this; - } - - /** - * Reset parser state - * - * @api private - */ - reset (reason) { - if (this.dead) return; - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - } -} - -module.exports = Receiver; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js deleted file mode 100644 index 439eb302f..000000000 --- a/lib/Sender.hixie.js +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -const EventEmitter = require('events'); - -/** - * Hixie Sender implementation, Inherits from EventEmitter. - */ - -class Sender extends EventEmitter { - constructor (socket) { - super(); - - this.socket = socket; - this.continuationFrame = false; - this.isClosed = false; - } - - /** - * Frames and writes data. - * - * @api public - */ - send (data, options, cb) { - if (this.isClosed) return; - - var isString = typeof data === 'string'; - var length = isString ? Buffer.byteLength(data) : data.length; - var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes - var writeStartMarker = this.continuationFrame === false; - var writeEndMarker = !options || !(typeof options.fin !== 'undefined' && !options.fin); - - var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; - bufferLength += length; - bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; - - var buffer = new Buffer(bufferLength); - var offset = writeStartMarker ? 1 : 0; - - if (writeStartMarker) { - if (options && options.binary) { - buffer.write('\x80', 'binary'); - // assume length less than 2**14 bytes - if (lengthbytes > 1) { - buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); - } - buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); - } else { - buffer.write('\x00', 'binary'); - } - } - - if (isString) buffer.write(data, offset, 'utf8'); - else data.copy(buffer, offset, 0); - - if (writeEndMarker) { - if (options && options.binary) { - // sending binary, not writing end marker - } else { - buffer.write('\xff', offset + length, 'binary'); - } - this.continuationFrame = false; - } else { - this.continuationFrame = true; - } - - try { - this.socket.write(buffer, 'binary', cb); - } catch (e) { - this.emit('error', e); - } - } - - /** - * Sends a close instruction to the remote party. - * - * @api public - */ - - close (code, data, mask, cb) { - if (this.isClosed) return; - this.isClosed = true; - try { - if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); - this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); - } catch (e) { - this.emit('error', e); - } - } - - /** - * Sends a ping message to the remote party. Not available for hixie. - * - * @api public - */ - - ping (data, options) {} - - /** - * Sends a pong message to the remote party. Not available for hixie. - * - * @api public - */ - pong (data, options) {} -} - -module.exports = Sender; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a0a292ff7..e0eead112 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -15,8 +15,6 @@ const stream = require('stream'); const Ultron = require('ultron'); const Sender = require('./Sender'); const Receiver = require('./Receiver'); -const SenderHixie = require('./Sender.hixie'); -const ReceiverHixie = require('./Receiver.hixie'); const Extensions = require('./Extensions'); const PerMessageDeflate = require('./PerMessageDeflate'); const EventEmitter = require('events'); @@ -72,7 +70,7 @@ function WebSocket (address, protocols, options) { this._closeReceived = false; this.bytesReceived = 0; this.readyState = null; - this.supports = {}; + this.supports = { binary: true }; this.extensions = {}; this._binaryType = 'nodebuffer'; @@ -539,16 +537,11 @@ function buildHostHeader (isSecure, hostname, port) { function initAsServerClient (req, socket, upgradeHead, options) { // expose state properties Object.assign(this, options); - this.supports.binary = this.protocolVersion !== 'hixie-76'; this.readyState = WebSocket.CONNECTING; this.upgradeReq = req; this._isServer = true; // establish connection - if (options.protocolVersion === 'hixie-76') { - establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); - } else { - establishConnection.call(this, Receiver, Sender, socket, upgradeHead); - } + establishConnection.call(this, socket, upgradeHead); } function initAsClient (address, protocols, options) { @@ -599,7 +592,6 @@ function initAsClient (address, protocols, options) { this._isServer = false; this.url = address; this.protocolVersion = options.protocolVersion; - this.supports.binary = true; // begin handshake var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); @@ -772,7 +764,7 @@ function initAsClient (address, protocols, options) { this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - establishConnection.call(this, Receiver, Sender, socket, upgradeHead); + establishConnection.call(this, socket, upgradeHead); // perform cleanup on http resources req.removeAllListeners(); @@ -784,13 +776,13 @@ function initAsClient (address, protocols, options) { this.readyState = WebSocket.CONNECTING; } -function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { +function establishConnection (socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket); socket.setTimeout(0); socket.setNoDelay(true); - this._receiver = new ReceiverClass(this.extensions, this.maxPayload); + this._receiver = new Receiver(this.extensions, this.maxPayload); this._socket = socket; // socket cleanup handlers @@ -847,7 +839,7 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { }; // finalize the client - this._sender = new SenderClass(socket, this.extensions); + this._sender = new Sender(socket, this.extensions); this._sender.onerror = (error) => { this.close(1002, ''); this.emit('error', error); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 34848b3ff..e51c2e1c5 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -38,7 +38,6 @@ function WebSocketServer (options, callback) { handleProtocols: null, path: null, noServer: false, - disableHixie: false, clientTracking: true, perMessageDeflate: true, maxPayload: 100 * 1024 * 1024, @@ -173,12 +172,11 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb } } - if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { return abortConnection(socket, 400); } - if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); - else handleHybiUpgrade.apply(this, arguments); + handleHybiUpgrade.apply(this, arguments); }; module.exports = WebSocketServer; @@ -325,180 +323,6 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { completeHybiUpgrade1(); } -function handleHixieUpgrade (req, socket, upgradeHead, cb) { - // handle premature socket errors - var errorHandler = () => { - try { socket.destroy(); } catch (e) {} - }; - socket.on('error', errorHandler); - - // bail if options prevent hixie - if (this.options.disableHixie) { - return abortConnection(socket, 401, 'Hixie support disabled'); - } - - // verify key presence - if (!req.headers['sec-websocket-key2']) { - return abortConnection(socket, 400); - } - - var origin = req.headers['origin']; - - // setup handshake completion to run after client has been verified - var onClientVerified = () => { - var wshost; - if (!req.headers['x-forwarded-host']) { - wshost = req.headers.host; - } else { - wshost = req.headers['x-forwarded-host']; - } - - var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = `${proto}://${wshost}${req.url}`; - var protocol = req.headers['sec-websocket-protocol']; - - // build the response header and return a Buffer - var buildResponseHeader = () => { - var headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: WebSocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Location: ' + location - ]; - if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - if (origin) headers.push(`Sec-WebSocket-Origin: ${origin}`); - - return new Buffer(headers.concat('', '').join('\r\n')); - }; - - // send handshake response before receiving the nonce - var handshakeResponse = () => { - socket.setTimeout(0); - socket.setNoDelay(true); - - var headerBuffer = buildResponseHeader(); - - try { - socket.write(headerBuffer, 'binary', (err) => { - // remove listener if there was an error - if (err) socket.removeListener('data', handler); - }); - } catch (e) { - try { socket.destroy(); } catch (e) {} - } - }; - - // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = (nonce, rest, headerBuffer) => { - // calculate key - var k1 = req.headers['sec-websocket-key1']; - var k2 = req.headers['sec-websocket-key2']; - var md5 = crypto.createHash('md5'); - - [k1, k2].forEach((k) => { - var n = parseInt(k.replace(/[^\d]/g, '')); - var spaces = k.replace(/[^ ]/g, '').length; - if (spaces === 0 || n % spaces !== 0) { - return abortConnection(socket, 400); - } - n /= spaces; - md5.update(String.fromCharCode( - n >> 24 & 0xFF, - n >> 16 & 0xFF, - n >> 8 & 0xFF, - n & 0xFF), 'binary'); - }); - md5.update(nonce.toString('binary'), 'binary'); - - socket.setTimeout(0); - socket.setNoDelay(true); - - try { - var hashBuffer = new Buffer(md5.digest('binary'), 'binary'); - var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length); - headerBuffer.copy(handshakeBuffer, 0); - hashBuffer.copy(handshakeBuffer, headerBuffer.length); - - // do a single write, which - upon success - causes a new client websocket to be setup - socket.write(handshakeBuffer, 'binary', (err) => { - if (err) return; // do not create client if an error happens - var client = new WebSocket([req, socket, rest], { - protocolVersion: 'hixie-76', - protocol: protocol - }); - if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); - } - - // signal upgrade complete - socket.removeListener('error', errorHandler); - cb(client); - }); - } catch (e) { - try { socket.destroy(); } catch (e) {} - return; - } - }; - - // retrieve nonce - var nonceLength = 8; - var nonce, rest; - if (upgradeHead && upgradeHead.length >= nonceLength) { - nonce = upgradeHead.slice(0, nonceLength); - rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; - completeHandshake(nonce, rest, buildResponseHeader()); - } else { - // nonce not present in upgradeHead - nonce = new Buffer(nonceLength); - upgradeHead.copy(nonce, 0); - var received = upgradeHead.length; - rest = null; - var handler = (data) => { - var toRead = Math.min(data.length, nonceLength - received); - if (toRead === 0) return; - data.copy(nonce, received, 0, toRead); - received += toRead; - if (received === nonceLength) { - socket.removeListener('data', handler); - if (toRead < data.length) rest = data.slice(toRead); - - // complete the handshake but send empty buffer for headers since they have already been sent - completeHandshake(nonce, rest, new Buffer(0)); - } - }; - - // handle additional data as we receive it - socket.on('data', handler); - - // send header response before we have the nonce to fix haproxy buffering - handshakeResponse(); - } - }; - - // verify client - if (typeof this.options.verifyClient === 'function') { - var info = { - secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, - origin: origin, - req: req - }; - if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, message) => { - if (!result) return abortConnection(socket, code || 401, message); - - onClientVerified(); - }); - return; - } else if (!this.options.verifyClient(info)) { - return abortConnection(socket, 401); - } - } - - // no client verification required - onClientVerified(); -} - function acceptExtensions (offer) { var extensions = {}; var options = this.options.perMessageDeflate; @@ -522,11 +346,14 @@ function acceptExtensions (offer) { function abortConnection (socket, code, message) { if (socket.writable) { message = message || http.STATUS_CODES[code]; - socket.write(`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n`); - socket.write('Connection: close\r\n'); - socket.write('Content-type: text/html\r\n'); - socket.write(`Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`); - socket.write(message); + socket.write( + `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + `Content-Length: ${Buffer.byteLength(message)}\r\n` + + '\r\n' + + message + ); } socket.destroy(); } diff --git a/package.json b/package.json index 29cf55b19..888898f60 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "license": "MIT", "main": "index.js", "keywords": [ - "Hixie", "HyBi", "Push", "RFC-6455", @@ -32,7 +31,6 @@ "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.0.x", "eslint-plugin-standard": "2.0.x", - "expect.js": "0.3.x", "istanbul": "0.4.x", "mocha": "3.1.x", "should": "8.0.x", diff --git a/test/Receiver.hixie.test.js b/test/Receiver.hixie.test.js deleted file mode 100644 index 8646d7683..000000000 --- a/test/Receiver.hixie.test.js +++ /dev/null @@ -1,170 +0,0 @@ -var assert = require('assert') - , expect = require('expect.js') - , Receiver = require('../lib/Receiver.hixie'); -require('./hybi-common'); - -describe('Receiver', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var p = Receiver(); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - - it('can parse text message', function() { - var p = new Receiver(); - var packet = '00 48 65 6c 6c 6f ff'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('Hello', data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - }); - - it('can parse multiple text messages', function() { - var p = new Receiver(); - var packet = '00 48 65 6c 6c 6f ff 00 48 65 6c 6c 6f ff'; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); - - it('can parse empty message', function() { - var p = new Receiver(); - var packet = '00 ff'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('', data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - }); - - it('can parse text messages delivered over multiple frames', function() { - var p = new Receiver(); - var packets = [ - '00 48', - '65 6c 6c', - '6f ff 00 48', - '65', - '6c 6c 6f', - 'ff' - ]; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - for (var i = 0; i < packets.length; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); - - it('emits an error if a payload doesnt start with 0x00', function() { - var p = new Receiver(); - var packets = [ - '00 6c ff', - '00 6c ff ff', - 'ff 00 6c ff 00 6c ff', - '00', - '6c 6c 6f', - 'ff' - ]; - - var gotData = false; - var gotError = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - p.onerror = function(reason, code) { - gotError = code == true; - }; - - for (var i = 0; i < packets.length && !gotError; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotError).to.equal(true); - expect(messages[0]).to.equal('l'); - expect(messages[1]).to.equal('l'); - expect(messages.length).to.equal(2); - }); - - it('can parse close messages', function() { - var p = new Receiver(); - var packets = [ - 'ff 00' - ]; - - var gotClose = false; - var gotError = false; - p.onclose = function() { - gotClose = true; - }; - p.onerror = function(reason, code) { - gotError = code == true; - }; - - for (var i = 0; i < packets.length && !gotError; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotClose).to.equal(true); - expect(gotError).to.equal(false); - }); - - it('can parse binary messages delivered over multiple frames', function() { - var p = new Receiver(); - var packets = [ - '80 05 48', - '65 6c 6c', - '6f 80 80 05 48', - '65', - '6c 6c 6f' - ]; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - for (var i = 0; i < packets.length; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); -}); diff --git a/test/Sender.hixie.test.js b/test/Sender.hixie.test.js deleted file mode 100644 index 3bf3e6474..000000000 --- a/test/Sender.hixie.test.js +++ /dev/null @@ -1,146 +0,0 @@ -var assert = require('assert') - , Sender = require('../lib/Sender.hixie'); -require('should'); -require('./hybi-common'); - -describe('Sender', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var sender = Sender({ write: function() {} }); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - - describe('#send', function() { - it('frames and sends a text message', function(done) { - var message = 'Hello world'; - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(message, {}, function() { - received.toString('utf8').should.eql('\u0000' + message + '\ufffd'); - done(); - }); - }); - - it('frames and sends an empty message', function(done) { - var socket = { - write: function(data, encoding, cb) { - done(); - } - }; - var sender = new Sender(socket, {}); - sender.send('', {}, function() {}); - }); - - it('frames and sends a buffer', function(done) { - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), {}, function() { - received.toString('utf8').should.eql('\u0000foobar\ufffd'); - done(); - }); - }); - - it('frames and sends a binary message', function(done) { - var message = 'Hello world'; - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(message, {binary: true}, function() { - received.toString('hex').should.eql( - // 0x80 0x0b H e l l o w o r l d - '800b48656c6c6f20776f726c64'); - done(); - }); - }); -/* - it('throws an exception for binary data', function(done) { - var socket = { - write: function(data, encoding, cb) { - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.on('error', function() { - done(); - }); - sender.send(new Buffer(100), {binary: true}, function() {}); - }); -*/ - it('can fauxe stream data', function(done) { - var received = []; - var socket = { - write: function(data, encoding, cb) { - received.push(data); - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), { fin: false }, function() {}); - sender.send('bazbar', { fin: false }, function() {}); - sender.send(new Buffer('end'), { fin: true }, function() { - received[0].toString('utf8').should.eql('\u0000foobar'); - received[1].toString('utf8').should.eql('bazbar'); - received[2].toString('utf8').should.eql('end\ufffd'); - done(); - }); - }); - }); - - describe('#close', function() { - it('sends a hixie close frame', function(done) { - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.close(null, null, null, function() { - received.toString('utf8').should.eql('\ufffd\u0000'); - done(); - }); - }); - - it('sends a message end marker if fauxe streaming has started, before hixie close frame', function(done) { - var received = []; - var socket = { - write: function(data, encoding, cb) { - received.push(data); - if (cb) process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), { fin: false }, function() {}); - sender.close(null, null, null, function() { - received[0].toString('utf8').should.eql('\u0000foobar'); - received[1].toString('utf8').should.eql('\ufffd'); - received[2].toString('utf8').should.eql('\ufffd\u0000'); - done(); - }); - }); - }); -}); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 2c72f2eaa..212ea8cf1 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1939,7 +1939,7 @@ describe('WebSocket', function() { describe('protocol support discovery', function() { describe('#supports', function() { describe('#binary', function() { - it('returns true for hybi transport', function(done) { + it('returns true', function(done) { var wss = new WebSocketServer({port: ++port}, function() { var ws = new WebSocket('ws://localhost:' + port); }); @@ -1949,29 +1949,6 @@ describe('WebSocket', function() { done(); }); }); - - it('returns false for hixie transport', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(client) { - assert.equal(false, client.supports.binary); - wss.close(); - done(); - }); - }); }); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index d6d333015..e8a08d80c 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -342,10 +342,10 @@ describe('WebSocketServer', function() { }); }); - describe('#maxpayload #hybiOnly', function() { + describe('#maxpayload', function() { it('maxpayload is passed on to clients,', function(done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -358,7 +358,7 @@ describe('WebSocketServer', function() { }); it('maxpayload is passed on to hybi receivers', function(done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -372,7 +372,7 @@ describe('WebSocketServer', function() { it('maxpayload is passed on to permessage-deflate', function(done) { var PerMessageDeflate = require('../lib/PerMessageDeflate'); var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -405,7 +405,7 @@ describe('WebSocketServer', function() { }); }); - it('can not finish upgrade when path is not right', function(done) { + it('closes the connection when path does not match', function (done) { var wss = new WebSocketServer({port: ++port, path: '/ws'}, function() { var options = { port: port, @@ -413,7 +413,7 @@ describe('WebSocketServer', function() { headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - }, + } }; var req = http.request(options); req.end(); @@ -424,6 +424,28 @@ describe('WebSocketServer', function() { }); }); }); + + it('closes the connection when protocol version is Hixie-76', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var options = { + port: port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', + 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', + 'Sec-WebSocket-Protocol': 'sample' + } + }; + var req = http.request(options); + req.on('response', function (res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + req.end(); + }); + }); }); describe('hybi mode', function() { @@ -968,351 +990,6 @@ describe('WebSocketServer', function() { }); }); - describe('hixie mode', function() { - it('can be disabled', function(done) { - var wss = new WebSocketServer({port: ++port, disableHixie: true}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - describe('connection establishing', function() { - it('does not accept connections with no sec-websocket-key1', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('does not accept connections with no sec-websocket-key2', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('accepts connections with valid handshake', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - ws.terminate(); - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('client can be denied', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be accepted', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return true; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - ws.terminate(); - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('verifyClient gets client origin', function(done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { - info.origin.should.eql('http://foobarbaz.com'); - verifyClientCalled = true; - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - - it('verifyClient gets original request', function(done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { - info.req.headers['sec-websocket-key1'].should.eql('3e6b263 4 17 80'); - verifyClientCalled = true; - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - - it('client can be denied asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(false); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be denied asynchronously with custom response code', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(false, 404, 'Not Found'); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(404); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be accepted asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(true); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('handles messages passed along with the upgrade request (upgrade head)', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return true; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90', - 'Origin': 'http://foobar.com' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.write(new Buffer([0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff], 'binary')); - req.end(); - }); - wss.on('connection', function(ws) { - ws.on('message', function(data) { - data.should.eql('Hello'); - ws.terminate(); - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - }); - }); - describe('client properties', function() { it('protocol is exposed', function(done) { var wss = new WebSocketServer({port: ++port}, function() { From 2b74c1f9fc73244ce7f9457a3c494f160253de00 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 08:46:40 +0200 Subject: [PATCH 087/489] [doc] Clarify how classes are exported Fixes #828 --- doc/ws.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index ce9f58ffd..26137b9b9 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -1,10 +1,10 @@ # ws -## Class: ws.Server +## Class: WebSocket.Server This class is a WebSocket server. It is an `EventEmitter`. -### new ws.Server([options], [callback]) +### new WebSocket.Server([options], [callback]) * `options` Object * `host` String @@ -74,7 +74,7 @@ Close the server and terminate all clients, calls callback when done with an err Handles a HTTP Upgrade request. `request` is an instance of `http.ServerRequest`, `socket` is an instance of `net.Socket`. -When the Upgrade was successfully, the `callback` will be called with a `ws.WebSocket` object as parameter. +When the Upgrade was successfully, the `callback` will be called with a `WebSocket` object as parameter. ### Event: 'error' @@ -92,14 +92,14 @@ Emitted with the object of HTTP headers that are going to be written to the `Str `function (socket) { }` -When a new WebSocket connection is established. `socket` is an object of type `ws.WebSocket`. +When a new WebSocket connection is established. `socket` is an object of type `WebSocket`. -## Class: ws.WebSocket +## Class: WebSocket This class represents a WebSocket connection. It is an `EventEmitter`. -### new ws.WebSocket(address, [protocols], [options]) +### new WebSocket(address, [protocols], [options]) * `address` String * `protocols` String|Array @@ -121,11 +121,11 @@ This class represents a WebSocket connection. It is an `EventEmitter`. * `perMessageDeflate` Boolean|Object * `localAddress` String -Instantiating with an `address` creates a new WebSocket client object. If `address` is an Array (request, socket, rest), it is instantiated as a Server client (e.g. called from the `ws.Server`). +Instantiating with an `address` creates a new WebSocket client object. If `address` is an Array (request, socket, rest), it is instantiated as a Server client (e.g. called from the `WebSocket.Server`). ### options.perMessageDeflate -Parameters of permessage-deflate extension which have the same form with the one for `ws.Server` except the direction of requests. (e.g. `serverNoContextTakeover` is the value to be requested to the server) +Parameters of permessage-deflate extension which have the same form with the one for `WebSocket.Server` except the direction of requests. (e.g. `serverNoContextTakeover` is the value to be requested to the server) ### websocket.bytesReceived From 200db3354fed6b97ee0c5bfbf4d37586783fc1c6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 10:09:28 +0200 Subject: [PATCH 088/489] [doc] Improve documentation for `WebSocket.prototype.send()` --- doc/ws.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 26137b9b9..897f875c2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -174,7 +174,20 @@ Resume the client stream ### websocket.send(data, [options], [callback]) -Sends `data` through the connection. `options` can be an object with members `mask`, `binary` and `compress`. The optional `callback` is executed after the send completes. +* `data` Any The data to send. +* `options` Object An options object. + * `compress` Boolean Specifies whether `data` should be compressed or not. + Defaults to `true` when permessage-deflate is enabled. + * `binary` Boolean Specifies whether `data` should be sent as a binary or not. + Default is autodetected. + * `mask` Boolean Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. + * `fin` Boolean Specifies whether `data` is the last fragment of a message or + not. Defaults to `true`. +* `callback` Function An optional callback which is invoked when the send + completes. + +Sends `data` through the connection. ### websocket.stream([options], callback) From dd885a8d95db7686484db8b57c37f65c5ed36f86 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 10:45:56 +0200 Subject: [PATCH 089/489] [minor] Rename hybi-common.js to hybi-util.js --- bench/parser.benchmark.js | 2 +- test/Receiver.test.js | 2 +- test/hybi-common.js | 99 ------------------------------ bench/util.js => test/hybi-util.js | 0 4 files changed, 2 insertions(+), 101 deletions(-) delete mode 100644 test/hybi-common.js rename bench/util.js => test/hybi-util.js (100%) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index bbda00293..f0b9cae72 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -8,8 +8,8 @@ const benchmark = require('benchmark'); +const util = require('../test/hybi-util'); const Receiver = require('../').Receiver; -const util = require('./util'); function createBinaryPacket (length) { const message = Buffer.alloc(length); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 6e548f7d2..f9f017d2b 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -5,7 +5,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Receiver = require('../lib/Receiver'); -const util = require('../bench/util'); +const util = require('./hybi-util'); describe('Receiver', function () { describe('#ctor', function () { diff --git a/test/hybi-common.js b/test/hybi-common.js deleted file mode 100644 index 006f9c693..000000000 --- a/test/hybi-common.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Returns a Buffer from a "ff 00 ff"-type hex string. - */ - -getBufferFromHexString = function(byteStr) { - var bytes = byteStr.split(' '); - var buf = new Buffer(bytes.length); - for (var i = 0; i < bytes.length; ++i) { - buf[i] = parseInt(bytes[i], 16); - } - return buf; -} - -/** - * Returns a hex string from a Buffer. - */ - -getHexStringFromBuffer = function(data) { - var s = ''; - for (var i = 0; i < data.length; ++i) { - s += padl(data[i].toString(16), 2, '0') + ' '; - } - return s.trim(); -} - -/** - * Splits a buffer in two parts. - */ - -splitBuffer = function(buffer) { - var b1 = new Buffer(Math.ceil(buffer.length / 2)); - buffer.copy(b1, 0, 0, b1.length); - var b2 = new Buffer(Math.floor(buffer.length / 2)); - buffer.copy(b2, 0, b1.length, b1.length + b2.length); - return [b1, b2]; -} - -/** - * Performs hybi07+ type masking on a hex string or buffer. - */ - -mask = function(buf, maskString) { - if (typeof buf == 'string') buf = new Buffer(buf); - var mask = getBufferFromHexString(maskString || '34 83 a8 68'); - for (var i = 0; i < buf.length; ++i) { - buf[i] ^= mask[i % 4]; - } - return buf; -} - -/** - * Returns a hex string representing the length of a message - */ - -getHybiLengthAsHexString = function(len, masked) { - if (len < 126) { - var buf = new Buffer(1); - buf[0] = (masked ? 0x80 : 0) | len; - } - else if (len < 65536) { - var buf = new Buffer(3); - buf[0] = (masked ? 0x80 : 0) | 126; - getBufferFromHexString(pack(4, len)).copy(buf, 1); - } - else { - var buf = new Buffer(9); - buf[0] = (masked ? 0x80 : 0) | 127; - getBufferFromHexString(pack(16, len)).copy(buf, 1); - } - return getHexStringFromBuffer(buf); -} - -/** - * Unpacks a Buffer into a number. - */ - -unpack = function(buffer) { - var n = 0; - for (var i = 0; i < buffer.length; ++i) { - n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; - } - return n; -} - -/** - * Returns a hex string, representing a specific byte count 'length', from a number. - */ - -pack = function(length, number) { - return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); -} - -/** - * Left pads the string 's' to a total length of 'n' with char 'c'. - */ - -padl = function(s, n, c) { - return new Array(1 + n - s.length).join(c) + s; -} diff --git a/bench/util.js b/test/hybi-util.js similarity index 100% rename from bench/util.js rename to test/hybi-util.js From bd0851d4c860657892a5f5a5a43654604b68e8ed Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Tue, 25 Oct 2016 13:02:02 +0200 Subject: [PATCH 090/489] Fix sending buffers as text --- lib/Sender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Sender.js b/lib/Sender.js index 5e2535c4c..f1acac557 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -194,7 +194,7 @@ class Sender { secondByte = 126; } - var canModifyData = opcode === 1 || compressed; + var canModifyData = compressed; var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; var outputBuffer = new Buffer(totalLength); From a10f3c113e0e65bce30f7cc4e538223233321da1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 08:52:18 +0200 Subject: [PATCH 091/489] [deps] Bump eslint-plugin-promise to version 3.3.x --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 888898f60..1b0a893a1 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,11 @@ "eslint": "3.8.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", - "eslint-plugin-promise": "3.0.x", + "eslint-plugin-promise": "3.3.x", "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", "mocha": "3.1.x", "should": "8.0.x", "utf-8-validate": "1.2.x" - }, - "gypfile": true + } } From 398d88a864557a8cfdb45eb8c723fc15b990cb1b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 09:14:26 +0200 Subject: [PATCH 092/489] [ci] Add node 7 to .travis.yml --- .travis.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 451ba0135..4b92d55ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,7 @@ language: node_js -sudo: false +sudo: required +dist: trusty node_js: + - "7" - "6" - - "5" - "4" -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - gcc-4.9 - - g++-4.9 -before_install: - - export CC="gcc-4.9" CXX="g++-4.9" From 460e37488227131b961a6c148088aed9ba30332f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 09:29:44 +0200 Subject: [PATCH 093/489] [lint] Run eslint --fix on test/* --- test/Extensions.test.js | 20 +- test/PerMessageDeflate.test.js | 118 ++-- test/Sender.test.js | 52 +- test/Validation.test.js | 12 +- test/WebSocket.integration.js | 20 +- test/WebSocket.test.js | 1034 ++++++++++++++++---------------- test/WebSocketServer.test.js | 504 ++++++++-------- test/autobahn-server.js | 12 +- test/autobahn.js | 24 +- test/testserver.js | 71 ++- 10 files changed, 932 insertions(+), 935 deletions(-) diff --git a/test/Extensions.test.js b/test/Extensions.test.js index 84ec5edac..ce0af8381 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -1,21 +1,21 @@ var Extensions = require('../lib/Extensions'); require('should'); -describe('Extensions', function() { - describe('parse', function() { - it('should parse', function() { +describe('Extensions', function () { + describe('parse', function () { + it('should parse', function () { var extensions = Extensions.parse('foo'); extensions.should.eql({ foo: [{}] }); }); - it('should parse params', function() { + it('should parse params', function () { var extensions = Extensions.parse('foo; bar; baz=1; bar=2'); extensions.should.eql({ foo: [{ bar: [true, '2'], baz: ['1'] }] }); }); - it('should parse multiple extensions', function() { + it('should parse multiple extensions', function () { var extensions = Extensions.parse('foo, bar; baz, foo; baz'); extensions.should.eql({ foo: [{}, { baz: [true] }], @@ -23,7 +23,7 @@ describe('Extensions', function() { }); }); - it('should parse quoted params', function() { + it('should parse quoted params', function () { var extensions = Extensions.parse('foo; bar="hi"'); extensions.should.eql({ foo: [{ bar: ['hi'] }] @@ -31,18 +31,18 @@ describe('Extensions', function() { }); }); - describe('format', function() { - it('should format', function() { + describe('format', function () { + it('should format', function () { var extensions = Extensions.format({ foo: {} }); extensions.should.eql('foo'); }); - it('should format params', function() { + it('should format params', function () { var extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); extensions.should.eql('foo; bar; bar=2; baz=1'); }); - it('should format multiple extensions', function() { + it('should format multiple extensions', function () { var extensions = Extensions.format({ foo: [{}, { baz: true }], bar: { baz: true } diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 6b70ccbdf..c9608d40a 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -2,9 +2,9 @@ var PerMessageDeflate = require('../lib/PerMessageDeflate'); var Extensions = require('../lib/Extensions'); require('should'); -describe('PerMessageDeflate', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { +describe('PerMessageDeflate', function () { + describe('#ctor', function () { + it('throws TypeError when called without new', function (done) { try { var perMessageDeflate = PerMessageDeflate(); } @@ -15,13 +15,13 @@ describe('PerMessageDeflate', function() { }); }); - describe('#offer', function() { - it('should create default params', function() { + describe('#offer', function () { + it('should create default params', function () { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.offer().should.eql({ client_max_window_bits: true }); }); - it('should create params from options', function() { + it('should create params from options', function () { var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -37,14 +37,14 @@ describe('PerMessageDeflate', function() { }); }); - describe('#accept', function() { - describe('as server', function() { - it('should accept empty offer', function() { + describe('#accept', function () { + describe('as server', function () { + it('should accept empty offer', function () { var perMessageDeflate = new PerMessageDeflate({}, true); perMessageDeflate.accept([{}]).should.eql({}); }); - it('should accept offer', function() { + it('should accept offer', function () { var perMessageDeflate = new PerMessageDeflate({}, true); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ @@ -55,7 +55,7 @@ describe('PerMessageDeflate', function() { }); }); - it('should prefer configuration than offer', function() { + it('should prefer configuration than offer', function () { var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -71,7 +71,7 @@ describe('PerMessageDeflate', function() { }); }); - it('should fallback', function() { + it('should fallback', function () { var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10, permessage-deflate'); perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ @@ -79,46 +79,46 @@ describe('PerMessageDeflate', function() { }); }); - it('should throw an error if server_no_context_takeover is unsupported', function() { + it('should throw an error if server_no_context_takeover is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_max_window_bits is unsupported', function() { + it('should throw an error if server_max_window_bits is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_max_window_bits is less than configuration', function() { + it('should throw an error if server_max_window_bits is less than configuration', function () { var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits is unsupported on client', function() { + it('should throw an error if client_max_window_bits is unsupported on client', function () { var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); var extensions = Extensions.parse('permessage-deflate'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); }); - describe('as client', function() { - it('should accept empty response', function() { + describe('as client', function () { + it('should accept empty response', function () { var perMessageDeflate = new PerMessageDeflate({}); perMessageDeflate.accept([{}]).should.eql({}); }); - it('should accept response parameter', function() { + it('should accept response parameter', function () { var perMessageDeflate = new PerMessageDeflate({}); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ @@ -129,89 +129,89 @@ describe('PerMessageDeflate', function() { }); }); - it('should throw an error if client_no_context_takeover is unsupported', function() { + it('should throw an error if client_no_context_takeover is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits is unsupported', function() { + it('should throw an error if client_max_window_bits is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits is greater than configuration', function() { + it('should throw an error if client_max_window_bits is greater than configuration', function () { var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); }); - describe('validate parameters', function() { - it('should throw an error if a parameter has multiple values', function() { + describe('validate parameters', function () { + it('should throw an error if a parameter has multiple values', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; server_no_context_takeover'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if a parameter is undefined', function() { + it('should throw an error if a parameter is undefined', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; foo;'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_no_context_takeover has a value', function() { + it('should throw an error if server_no_context_takeover has a value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_no_context_takeover has a value', function() { + it('should throw an error if client_no_context_takeover has a value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_max_window_bits has an invalid value', function() { + it('should throw an error if server_max_window_bits has an invalid value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits has an invalid value', function() { + it('should throw an error if client_max_window_bits has an invalid value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); }); }); - describe('#compress/#decompress', function() { - it('should compress/decompress data', function(done) { + describe('#compress/#decompress', function () { + it('should compress/decompress data', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function(err, compressed) { + perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function(err, data) { + perMessageDeflate.decompress(compressed, true, function (err, data) { if (err) return done(err); data.should.eql(new Buffer([1, 2, 3])); done(); @@ -219,18 +219,18 @@ describe('PerMessageDeflate', function() { }); }); - it('should compress/decompress fragments', function(done) { + it('should compress/decompress fragments', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); var buf = new Buffer([1, 2, 3, 4]); - perMessageDeflate.compress(buf.slice(0, 2), false, function(err, compressed1) { + perMessageDeflate.compress(buf.slice(0, 2), false, function (err, compressed1) { if (err) return done(err); - perMessageDeflate.compress(buf.slice(2), true, function(err, compressed2) { + perMessageDeflate.compress(buf.slice(2), true, function (err, compressed2) { if (err) return done(err); - perMessageDeflate.decompress(compressed1, false, function(err, data1) { + perMessageDeflate.decompress(compressed1, false, function (err, data1) { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function(err, data2) { + perMessageDeflate.decompress(compressed2, true, function (err, data2) { if (err) return done(err); new Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); done(); @@ -240,13 +240,13 @@ describe('PerMessageDeflate', function() { }); }); - it('should compress/decompress data with parameters', function(done) { + it('should compress/decompress data with parameters', function (done) { var perMessageDeflate = new PerMessageDeflate({ memLevel: 5 }); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function(err, compressed) { + perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function(err, data) { + perMessageDeflate.decompress(compressed, true, function (err, data) { if (err) return done(err); data.should.eql(new Buffer([1, 2, 3])); done(); @@ -254,18 +254,18 @@ describe('PerMessageDeflate', function() { }); }); - it('should compress/decompress data with no context takeover', function(done) { + it('should compress/decompress data with no context takeover', function (done) { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover'); perMessageDeflate.accept(extensions['permessage-deflate']); var buf = new Buffer('foofoo'); - perMessageDeflate.compress(buf, true, function(err, compressed1) { + perMessageDeflate.compress(buf, true, function (err, compressed1) { if (err) return done(err); - perMessageDeflate.decompress(compressed1, true, function(err, data) { + perMessageDeflate.decompress(compressed1, true, function (err, data) { if (err) return done(err); - perMessageDeflate.compress(data, true, function(err, compressed2) { + perMessageDeflate.compress(data, true, function (err, compressed2) { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function(err, data) { + perMessageDeflate.decompress(compressed2, true, function (err, data) { if (err) return done(err); compressed2.length.should.equal(compressed1.length); data.should.eql(buf); diff --git a/test/Sender.test.js b/test/Sender.test.js index dc82c4eb0..862071c04 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -1,12 +1,12 @@ -var Sender = require('../lib/Sender') - , PerMessageDeflate = require('../lib/PerMessageDeflate'); +var Sender = require('../lib/Sender'), + PerMessageDeflate = require('../lib/PerMessageDeflate'); require('should'); -describe('Sender', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { +describe('Sender', function () { + describe('#ctor', function () { + it('throws TypeError when called without new', function (done) { try { - var sender = Sender({ write: function() {} }); + var sender = Sender({ write: function () {} }); } catch (e) { e.should.be.instanceof(TypeError); @@ -15,9 +15,9 @@ describe('Sender', function() { }); }); - describe('#frameAndSend', function() { - it('does not modify a masked binary buffer', function() { - var sender = new Sender({ write: function() {} }); + describe('#frameAndSend', function () { + it('does not modify a masked binary buffer', function () { + var sender = new Sender({ write: function () {} }); var buf = new Buffer([1, 2, 3, 4, 5]); sender.frameAndSend(2, buf, true, true); buf[0].should.eql(1); @@ -27,16 +27,16 @@ describe('Sender', function() { buf[4].should.eql(5); }); - it('does not modify a masked text buffer', function() { - var sender = new Sender({ write: function() {} }); + it('does not modify a masked text buffer', function () { + var sender = new Sender({ write: function () {} }); var text = 'hi there'; sender.frameAndSend(1, Buffer.from(text), true, true); text.should.eql('hi there'); }); - it('sets rsv1 flag if compressed', function(done) { + it('sets rsv1 flag if compressed', function (done) { var sender = new Sender({ - write: function(data) { + write: function (data) { (data[0] & 0x40).should.equal(0x40); done(); } @@ -45,13 +45,13 @@ describe('Sender', function() { }); }); - describe('#send', function() { - it('compresses data if compress option is enabled', function(done) { + describe('#send', function () { + it('compresses data if compress option is enabled', function (done) { var perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); perMessageDeflate.accept([{}]); var sender = new Sender({ - write: function(data) { + write: function (data) { (data[0] & 0x40).should.equal(0x40); done(); } @@ -60,13 +60,13 @@ describe('Sender', function() { }); sender.send('hi', { compress: true }); }); - - it('does not compress data for small payloads', function(done) { + + it('does not compress data for small payloads', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); var sender = new Sender({ - write: function(data) { + write: function (data) { (data[0] & 0x40).should.not.equal(0x40); done(); } @@ -76,12 +76,12 @@ describe('Sender', function() { sender.send('hi', { compress: true }); }); - it('Should be able to handle many send calls while processing without crashing on flush', function(done) { + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { var messageCount = 0; var maxMessages = 5000; var sender = new Sender({ - write: function(data) { + write: function (data) { messageCount++; if (messageCount > maxMessages) return done(); } @@ -94,15 +94,15 @@ describe('Sender', function() { sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); }); }); - - describe('#close', function() { - it('should consume all data before closing', function(done) { + + describe('#close', function () { + it('should consume all data before closing', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); var count = 0; var sender = new Sender({ - write: function(data) { + write: function (data) { count++; } }, { @@ -111,7 +111,7 @@ describe('Sender', function() { sender.send('foo', {compress: true}); sender.send('bar', {compress: true}); sender.send('baz', {compress: true}); - sender.close(1000, null, false, function(err) { + sender.close(1000, null, false, function (err) { count.should.be.equal(4); done(err); }); diff --git a/test/Validation.test.js b/test/Validation.test.js index 37c339935..8038a7c63 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -1,21 +1,21 @@ var Validation = require('../lib/Validation').Validation; require('should'); -describe('Validation', function() { - describe('isValidUTF8', function() { - it('should return true for a valid utf8 string', function() { +describe('Validation', function () { + describe('isValidUTF8', function () { + it('should return true for a valid utf8 string', function () { var validBuffer = new Buffer('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque gravida mattis rhoncus. Donec iaculis, metus quis varius accumsan, erat mauris condimentum diam, et egestas erat enim ut ligula. Praesent sollicitudin tellus eget dolor euismod euismod. Nullam ac augue nec neque varius luctus. Curabitur elit mi, consequat ultricies adipiscing mollis, scelerisque in erat. Phasellus facilisis fermentum ullamcorper. Nulla et sem eu arcu pharetra pellentesque. Praesent consectetur tempor justo, vel iaculis dui ullamcorper sit amet. Integer tristique viverra ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, lacus lectus feugiat libero, non fermentum erat nisi at risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pulvinar dignissim tellus, eu dignissim lorem vulputate quis. Morbi ut pulvinar augue.'); Validation.isValidUTF8(validBuffer).should.be.ok; }); - it('should return false for an erroneous string', function() { + it('should return false for an erroneous string', function () { var invalidBuffer = new Buffer([0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64]); Validation.isValidUTF8(invalidBuffer).should.not.be.ok; }); - it('should return true for valid cases from the autobahn test suite', function() { + it('should return true for valid cases from the autobahn test suite', function () { Validation.isValidUTF8(new Buffer('\xf0\x90\x80\x80')).should.be.ok; Validation.isValidUTF8(new Buffer([0xf0, 0x90, 0x80, 0x80])).should.be.ok; }); - it('should return false for erroneous autobahn strings', function() { + it('should return false for erroneous autobahn strings', function () { Validation.isValidUTF8(new Buffer([0xce, 0xba, 0xe1, 0xbd])).should.not.be.ok; }); }); diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index 5d4f426f4..c21821566 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -1,10 +1,10 @@ -var assert = require('assert') - , WebSocket = require('../') - , server = require('./testserver'); +var assert = require('assert'), + WebSocket = require('../'), + server = require('./testserver'); var port = 20000; -function getArrayBuffer(buf) { +function getArrayBuffer (buf) { var l = buf.length; var arrayBuf = new ArrayBuffer(l); var uint8View = new Uint8Array(arrayBuf); @@ -15,7 +15,7 @@ function getArrayBuffer(buf) { return uint8View.buffer; } -function areArraysEqual(x, y) { +function areArraysEqual (x, y) { if (x.length != y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; @@ -23,19 +23,19 @@ function areArraysEqual(x, y) { return true; } -describe('WebSocket', function() { - it('communicates successfully with echo service', function(done) { +describe('WebSocket', function () { + it('communicates successfully with echo service', function (done) { var ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 13, origin: 'http://websocket.org'}); var str = Date.now().toString(); var dataReceived = false; - ws.on('open', function() { + ws.on('open', function () { ws.send(str, {mask: true}); }); - ws.on('close', function() { + ws.on('close', function () { assert.equal(true, dataReceived); done(); }); - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { assert.equal(str, data); ws.terminate(); dataReceived = true; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 212ea8cf1..6fbbc7801 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1,17 +1,17 @@ -var assert = require('assert') - , https = require('https') - , http = require('http') - , should = require('should') - , WebSocket = require('../') - , WebSocketServer = require('../').Server - , fs = require('fs') - , os = require('os') - , server = require('./testserver') - , crypto = require('crypto'); +var assert = require('assert'), + https = require('https'), + http = require('http'), + should = require('should'), + WebSocket = require('../'), + WebSocketServer = require('../').Server, + fs = require('fs'), + os = require('os'), + server = require('./testserver'), + crypto = require('crypto'); var port = 20000; -function getArrayBuffer(buf) { +function getArrayBuffer (buf) { var l = buf.length; var arrayBuf = new ArrayBuffer(l); var uint8View = new Uint8Array(arrayBuf); @@ -21,8 +21,7 @@ function getArrayBuffer(buf) { return uint8View.buffer; } - -function areArraysEqual(x, y) { +function areArraysEqual (x, y) { if (x.length != y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; @@ -30,9 +29,9 @@ function areArraysEqual(x, y) { return true; } -describe('WebSocket', function() { - describe('#ctor', function() { - it('throws exception for invalid url', function(done) { +describe('WebSocket', function () { + describe('#ctor', function () { + it('throws exception for invalid url', function (done) { try { var ws = new WebSocket('echo.websocket.org'); } @@ -41,18 +40,18 @@ describe('WebSocket', function() { } }); - it('should return a new instance if called without new', function(done) { + it('should return a new instance if called without new', function (done) { var ws = WebSocket('ws://localhost:' + port); ws.should.be.an.instanceOf(WebSocket); done(); }); }); - describe('options', function() { - it('should accept an `agent` option', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('options', function () { + it('should accept an `agent` option', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var agent = { - addRequest: function() { + addRequest: function () { wss.close(); done(); } @@ -61,10 +60,10 @@ describe('WebSocket', function() { }); }); // GH-227 - it('should accept the `options` object as the 3rd argument', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should accept the `options` object as the 3rd argument', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var agent = { - addRequest: function() { + addRequest: function () { wss.close(); done(); } @@ -73,21 +72,21 @@ describe('WebSocket', function() { }); }); - it('should accept the localAddress option', function(done) { + it('should accept the localAddress option', function (done) { // explore existing interfaces - var devs = os.networkInterfaces() - , localAddresses = [] - , j, ifc, dev, devname; - for ( devname in devs ) { + var devs = os.networkInterfaces(), + localAddresses = [], + j, ifc, dev, devname; + for (devname in devs) { dev = devs[devname]; - for ( j=0;j 0) break; ws.send((new Array(10000)).join('hello')); } ws.terminate(); - ws.on('close', function() { + ws.on('close', function () { wss.close(); done(); }); @@ -203,7 +202,7 @@ describe('WebSocket', function() { }); }); - describe('Custom headers', function() { + describe('Custom headers', function () { it('request has an authorization header', function (done) { var auth = 'test:testpass'; var srv = http.createServer(function (req, res) {}); @@ -247,23 +246,23 @@ describe('WebSocket', function() { }); }); - describe('#readyState', function() { - it('defaults to connecting', function(done) { - server.createServer(++port, function(srv) { + describe('#readyState', function () { + it('defaults to connecting', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); assert.equal(WebSocket.CONNECTING, ws.readyState); ws.terminate(); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('set to open once connection is established', function(done) { - server.createServer(++port, function(srv) { + it('set to open once connection is established', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.equal(WebSocket.OPEN, ws.readyState); srv.close(); done(); @@ -271,11 +270,11 @@ describe('WebSocket', function() { }); }); - it('set to closed once connection is closed', function(done) { - server.createServer(++port, function(srv) { + it('set to closed once connection is closed', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.close(1001); - ws.on('close', function() { + ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); srv.close(); done(); @@ -283,11 +282,11 @@ describe('WebSocket', function() { }); }); - it('set to closed once connection is terminated', function(done) { - server.createServer(++port, function(srv) { + it('set to closed once connection is terminated', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.terminate(); - ws.on('close', function() { + ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); srv.close(); done(); @@ -311,21 +310,21 @@ describe('WebSocket', function() { * Ready state constant tests */ - Object.keys(readyStates).forEach(function(state) { - describe('.' + state, function() { - it('is enumerable property of class', function() { - var propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state) + Object.keys(readyStates).forEach(function (state) { + describe('.' + state, function () { + it('is enumerable property of class', function () { + var propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); assert.equal(readyStates[state], propertyDescripter.value); assert.equal(true, propertyDescripter.enumerable); }); }); }); - server.createServer(++port, function(srv) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - Object.keys(readyStates).forEach(function(state) { - describe('.' + state, function() { - it('is property of instance', function() { + Object.keys(readyStates).forEach(function (state) { + describe('.' + state, function () { + it('is property of instance', function () { assert.equal(readyStates[state], ws[state]); }); }); @@ -333,27 +332,27 @@ describe('WebSocket', function() { }); }); - describe('events', function() { - it('emits a ping event', function(done) { + describe('events', function () { + it('emits a ping event', function (done) { var wss = new WebSocketServer({port: ++port}); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.ping(); }); var ws = new WebSocket('ws://localhost:' + port); - ws.on('ping', function() { + ws.on('ping', function () { ws.terminate(); wss.close(); done(); }); }); - it('emits a pong event', function(done) { + it('emits a pong event', function (done) { var wss = new WebSocketServer({port: ++port}); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.pong(); }); var ws = new WebSocket('ws://localhost:' + port); - ws.on('pong', function() { + ws.on('pong', function () { ws.terminate(); wss.close(); done(); @@ -361,96 +360,96 @@ describe('WebSocket', function() { }); }); - describe('connection establishing', function() { - it('can disconnect before connection is established', function(done) { - server.createServer(++port, function(srv) { + describe('connection establishing', function () { + it('can disconnect before connection is established', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.terminate(); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('can close before connection is established', function(done) { - server.createServer(++port, function(srv) { + it('can close before connection is established', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.close(1001); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('can handle error before request is upgraded', function(done) { + it('can handle error before request is upgraded', function (done) { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded - ++port; - var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { - assert.fail('connect shouldnt be raised here'); - }); - var errorCallBackFired = false; - ws.on('error', function() { - errorCallBackFired = true; - }); - ws.on('close', function() { - setTimeout(function() { - assert.equal(true, errorCallBackFired); - assert.equal(ws.readyState, WebSocket.CLOSED); - done(); - }, 50) - }); + ++port; + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function () { + assert.fail('connect shouldnt be raised here'); + }); + var errorCallBackFired = false; + ws.on('error', function () { + errorCallBackFired = true; + }); + ws.on('close', function () { + setTimeout(function () { + assert.equal(true, errorCallBackFired); + assert.equal(ws.readyState, WebSocket.CLOSED); + done(); + }, 50); + }); }); - it('invalid server key is denied', function(done) { - server.createServer(++port, server.handlers.invalidKey, function(srv) { + it('invalid server key is denied', function (done) { + server.createServer(++port, server.handlers.invalidKey, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() { + ws.on('error', function () { srv.close(); done(); }); }); }); - it('close event is raised when server closes connection', function(done) { - server.createServer(++port, server.handlers.closeAfterConnect, function(srv) { + it('close event is raised when server closes connection', function (done) { + server.createServer(++port, server.handlers.closeAfterConnect, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('error is emitted if server aborts connection', function(done) { - server.createServer(++port, server.handlers.return401, function(srv) { + it('error is emitted if server aborts connection', function (done) { + server.createServer(++port, server.handlers.return401, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('error', function() { + ws.on('error', function () { srv.close(); done(); }); }); }); - it('unexpected response can be read when sent by server', function(done) { - server.createServer(++port, server.handlers.return401, function(srv) { + it('unexpected response can be read when sent by server', function (done) { + server.createServer(++port, server.handlers.return401, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('unexpected-response', function(req, res) { + ws.on('unexpected-response', function (req, res) { assert.equal(res.statusCode, 401); var data = ''; @@ -471,13 +470,13 @@ describe('WebSocket', function() { }); }); - it('request can be aborted when unexpected response is sent by server', function(done) { - server.createServer(++port, server.handlers.return401, function(srv) { + it('request can be aborted when unexpected response is sent by server', function (done) { + server.createServer(++port, server.handlers.return401, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('unexpected-response', function(req, res) { + ws.on('unexpected-response', function (req, res) { assert.equal(res.statusCode, 401); res.on('end', function () { @@ -514,8 +513,8 @@ describe('WebSocket', function() { }); }); - describe('#pause and #resume', function() { - it('pauses the underlying stream', function(done) { + describe('#pause and #resume', function () { + it('pauses the underlying stream', function (done) { // this test is sort-of racecondition'y, since an unlikely slow connection // to localhost can cause the test to succeed even when the stream pausing // isn't working as intended. that is an extremely unlikely scenario, though @@ -523,39 +522,39 @@ describe('WebSocket', function() { var client; var serverClient; var openCount = 0; - function onOpen() { + function onOpen () { if (++openCount == 2) { var paused = true; - serverClient.on('message', function() { + serverClient.on('message', function () { paused.should.not.be.ok; wss.close(); done(); }); serverClient.pause(); - setTimeout(function() { + setTimeout(function () { paused = false; serverClient.resume(); }, 200); client.send('foo'); } } - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); serverClient = ws; serverClient.on('open', onOpen); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { client = ws; onOpen(); }); }); }); - describe('#ping', function() { - it('before connect should fail', function(done) { - server.createServer(++port, function(srv) { + describe('#ping', function () { + it('before connect should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); try { ws.ping(); } @@ -567,10 +566,10 @@ describe('WebSocket', function() { }); }); - it('before connect can silently fail', function(done) { - server.createServer(++port, function(srv) { + it('before connect can silently fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); ws.ping('', {}, true); srv.close(); ws.terminate(); @@ -578,13 +577,13 @@ describe('WebSocket', function() { }); }); - it('without message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('without message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping(); }); - srv.on('ping', function(message) { + srv.on('ping', function (message) { srv.close(); ws.terminate(); done(); @@ -592,13 +591,13 @@ describe('WebSocket', function() { }); }); - it('with message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping('hi'); }); - srv.on('ping', function(message) { + srv.on('ping', function (message) { assert.equal('hi', message); srv.close(); ws.terminate(); @@ -607,15 +606,15 @@ describe('WebSocket', function() { }); }); - it('can send safely receive numbers as ping payload', function(done) { - server.createServer(++port, function(srv) { + it('can send safely receive numbers as ping payload', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping(200); }); - srv.on('ping', function(message) { + srv.on('ping', function (message) { assert.equal('200', message); srv.close(); ws.terminate(); @@ -624,13 +623,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping('hi', {mask: true}); }); - srv.on('ping', function(message, flags) { + srv.on('ping', function (message, flags) { assert.ok(flags.masked); assert.equal('hi', message); srv.close(); @@ -641,11 +640,11 @@ describe('WebSocket', function() { }); }); - describe('#pong', function() { - it('before connect should fail', function(done) { - server.createServer(++port, function(srv) { + describe('#pong', function () { + it('before connect should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); try { ws.pong(); } @@ -657,10 +656,10 @@ describe('WebSocket', function() { }); }); - it('before connect can silently fail', function(done) { - server.createServer(++port, function(srv) { + it('before connect can silently fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); ws.pong('', {}, true); srv.close(); ws.terminate(); @@ -668,13 +667,13 @@ describe('WebSocket', function() { }); }); - it('without message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('without message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.pong(); }); - srv.on('pong', function(message) { + srv.on('pong', function (message) { srv.close(); ws.terminate(); done(); @@ -682,13 +681,13 @@ describe('WebSocket', function() { }); }); - it('with message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.pong('hi'); }); - srv.on('pong', function(message) { + srv.on('pong', function (message) { assert.equal('hi', message); srv.close(); ws.terminate(); @@ -697,13 +696,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.pong('hi', {mask: true}); }); - srv.on('pong', function(message, flags) { + srv.on('pong', function (message, flags) { assert.ok(flags.masked); assert.equal('hi', message); srv.close(); @@ -714,16 +713,16 @@ describe('WebSocket', function() { }); }); - describe('#send', function() { - it('very long binary data can be sent and received (with echoing server)', function(done) { - server.createServer(++port, function(srv) { + describe('#send', function () { + it('very long binary data can be sent and received (with echoing server)', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5 * 1024 * 1024); for (var i = 0; i < array.length; ++i) array[i] = i / 5; - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); ws.terminate(); @@ -733,13 +732,13 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { - server.createServer(++port, function(srv) { + it('can send and receive text data', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi'); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.equal('hi', message); ws.terminate(); srv.close(); @@ -766,16 +765,16 @@ describe('WebSocket', function() { }); }); - it('send and receive binary data as an array', function(done) { - server.createServer(++port, function(srv) { + it('send and receive binary data as an array', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(6); for (var i = 0; i < array.length; ++i) array[i] = i / 2; var partial = array.subarray(2, 5); - ws.on('open', function() { + ws.on('open', function () { ws.send(partial, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(partial, new Float32Array(getArrayBuffer(message)))); ws.terminate(); @@ -785,14 +784,14 @@ describe('WebSocket', function() { }); }); - it('binary data can be sent and received as buffer', function(done) { - server.createServer(++port, function(srv) { + it('binary data can be sent and received as buffer', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var buf = new Buffer('foobar'); - ws.on('open', function() { + ws.on('open', function () { ws.send(buf, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(buf, message)); ws.terminate(); @@ -802,12 +801,12 @@ describe('WebSocket', function() { }); }); - it('ArrayBuffer is auto-detected without binary flag', function(done) { - server.createServer(++port, function(srv) { + it('ArrayBuffer is auto-detected without binary flag', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5); for (var i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function() { + ws.on('open', function () { ws.send(array.buffer); }); ws.onmessage = function (event) { @@ -820,11 +819,11 @@ describe('WebSocket', function() { }); }); - it('Buffer is auto-detected without binary flag', function(done) { - server.createServer(++port, function(srv) { + it('Buffer is auto-detected without binary flag', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var buf = new Buffer('foobar'); - ws.on('open', function() { + ws.on('open', function () { ws.send(buf); }); ws.onmessage = function (event) { @@ -837,10 +836,10 @@ describe('WebSocket', function() { }); }); - it('before connect should fail', function(done) { - server.createServer(++port, function(srv) { + it('before connect should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); try { ws.send('hi'); } @@ -852,11 +851,11 @@ describe('WebSocket', function() { }); }); - it('before connect should pass error through callback, if present', function(done) { - server.createServer(++port, function(srv) { + it('before connect should pass error through callback, if present', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); - ws.send('hi', function(error) { + ws.on('error', function () {}); + ws.send('hi', function (error) { assert.ok(error instanceof Error); ws.terminate(); srv.close(); @@ -865,13 +864,13 @@ describe('WebSocket', function() { }); }); - it('without data should be successful', function(done) { - server.createServer(++port, function(srv) { + it('without data should be successful', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send(); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.equal('', message); srv.close(); ws.terminate(); @@ -880,11 +879,11 @@ describe('WebSocket', function() { }); }); - it('calls optional callback when flushed', function(done) { - server.createServer(++port, function(srv) { + it('calls optional callback when flushed', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { - ws.send('hi', function() { + ws.on('open', function () { + ws.send('hi', function () { srv.close(); ws.terminate(); done(); @@ -893,13 +892,13 @@ describe('WebSocket', function() { }); }); - it('with unencoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with unencoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi'); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.equal('hi', message); srv.close(); ws.terminate(); @@ -908,13 +907,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi', {mask: true}); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.ok(flags.masked); assert.equal('hi', message); srv.close(); @@ -924,15 +923,15 @@ describe('WebSocket', function() { }); }); - it('with unencoded binary message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with unencoded binary message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5); for (var i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {binary: true}); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); srv.close(); @@ -942,15 +941,15 @@ describe('WebSocket', function() { }); }); - it('with encoded binary message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded binary message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5); for (var i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {mask: true, binary: true}); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(flags.masked); assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); @@ -961,23 +960,23 @@ describe('WebSocket', function() { }); }); - it('with binary stream will send fragmented data', function(done) { - server.createServer(++port, function(srv) { + it('with binary stream will send fragmented data', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true}, function(error) { + ws.send(fileStream, {binary: true}, function (error) { assert.equal(null, error); callbackFired = true; }); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); ws.terminate(); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(callbackFired); srv.close(); done(); @@ -985,23 +984,23 @@ describe('WebSocket', function() { }); }); - it('with text stream will send fragmented data', function(done) { - server.createServer(++port, function(srv) { + it('with text stream will send fragmented data', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, {binary: false}, function(error) { + ws.send(fileStream, {binary: false}, function (error) { assert.equal(null, error); callbackFired = true; }); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); ws.terminate(); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(callbackFired); srv.close(); done(); @@ -1009,17 +1008,17 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent send to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent send to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.send('foobar'); ws.send('baz'); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1040,21 +1039,21 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent stream to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent stream to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) send('foo'); else send('bar', true); }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1071,16 +1070,16 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent ping to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent ping to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.ping('foobar'); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); if (++receivedIndex == 2) { @@ -1089,7 +1088,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('ping', function(data) { + srv.on('ping', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1100,16 +1099,16 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent pong to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent pong to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.pong('foobar'); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); if (++receivedIndex == 2) { @@ -1118,7 +1117,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('pong', function(data) { + srv.on('pong', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1129,25 +1128,25 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent close to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent close to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.close(1000, 'foobar'); }); - ws.on('close', function() { + ws.on('close', function () { srv.close(); ws.terminate(); done(); }); - ws.on('error', function() { /* That's quite alright -- a send was attempted after close */ }); - srv.on('message', function(data, flags) { + ws.on('error', function () { /* That's quite alright -- a send was attempted after close */ }); + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); }); - srv.on('close', function(code, data) { + srv.on('close', function (code, data) { assert.equal(1000, code); assert.equal('foobar', data); }); @@ -1155,17 +1154,17 @@ describe('WebSocket', function() { }); }); - describe('#stream', function() { - it('very long binary data can be streamed', function(done) { - server.createServer(++port, function(srv) { + describe('#stream', function () { + it('very long binary data can be streamed', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var buffer = new Buffer(10 * 1024); for (var i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; - ws.on('open', function() { + ws.on('open', function () { var i = 0; var blockSize = 800; var bufLen = buffer.length; - ws.stream({binary: true}, function(error, send) { + ws.stream({binary: true}, function (error, send) { assert.ok(!error); var start = i * blockSize; var toSend = Math.min(blockSize, bufLen - (i * blockSize)); @@ -1175,7 +1174,7 @@ describe('WebSocket', function() { i += 1; }); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(buffer, data)); ws.terminate(); @@ -1185,11 +1184,11 @@ describe('WebSocket', function() { }); }); - it('before connect should pass error through callback', function(done) { - server.createServer(++port, function(srv) { + it('before connect should pass error through callback', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); - ws.stream(function(error) { + ws.on('error', function () {}); + ws.stream(function (error) { assert.ok(error instanceof Error); ws.terminate(); srv.close(); @@ -1198,11 +1197,11 @@ describe('WebSocket', function() { }); }); - it('without callback should fail', function(done) { - server.createServer(++port, function(srv) { + it('without callback should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { try { ws.stream(); } @@ -1215,13 +1214,13 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent send to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent send to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); @@ -1234,7 +1233,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1255,18 +1254,18 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent stream to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent stream to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); var i2 = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i2 == 1) send('foo'); else send('bar', true); @@ -1277,7 +1276,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1287,10 +1286,10 @@ describe('WebSocket', function() { assert.ok(!flags.binary); assert.equal('foobar', data); } - else if (receivedIndex == 3){ + else if (receivedIndex == 3) { assert.ok(!flags.binary); assert.equal('baz', data); - setTimeout(function() { + setTimeout(function () { srv.close(); ws.terminate(); done(); @@ -1301,13 +1300,13 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent ping to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent ping to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); @@ -1319,7 +1318,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); if (++receivedIndex == 2) { @@ -1328,7 +1327,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('ping', function(data) { + srv.on('ping', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1339,13 +1338,13 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent pong to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent pong to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); @@ -1357,7 +1356,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); if (++receivedIndex == 2) { @@ -1366,7 +1365,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('pong', function(data) { + srv.on('pong', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1377,19 +1376,19 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent close to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent close to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; var errorGiven = false; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { if (++i == 1) { send(payload.substr(0, 5)); ws.close(1000, 'foobar'); } - else if(i == 2) { + else if (i == 2) { send(payload.substr(5, 5), true); } else if (i == 3) { @@ -1398,17 +1397,17 @@ describe('WebSocket', function() { } }); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(errorGiven); srv.close(); ws.terminate(); done(); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); }); - srv.on('close', function(code, data) { + srv.on('close', function (code, data) { assert.equal(1000, code); assert.equal('foobar', data); }); @@ -1416,20 +1415,20 @@ describe('WebSocket', function() { }); }); - describe('#close', function() { - it('will raise error callback, if any, if called during send stream', function(done) { - server.createServer(++port, function(srv) { + describe('#close', function () { + it('will raise error callback, if any, if called during send stream', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var errorGiven = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, function(error) { + ws.send(fileStream, function (error) { errorGiven = error != null; }); ws.close(1000, 'foobar'); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { assert.ok(errorGiven); srv.close(); ws.terminate(); @@ -1439,10 +1438,10 @@ describe('WebSocket', function() { }); }); - it('without invalid first argument throws exception', function(done) { - server.createServer(++port, function(srv) { + it('without invalid first argument throws exception', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { try { ws.close('error'); } @@ -1455,10 +1454,10 @@ describe('WebSocket', function() { }); }); - it('without reserved error code 1004 throws exception', function(done) { - server.createServer(++port, function(srv) { + it('without reserved error code 1004 throws exception', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { try { ws.close(1004); } @@ -1471,13 +1470,13 @@ describe('WebSocket', function() { }); }); - it('without message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('without message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.close(1000); }); - srv.on('close', function(code, message, flags) { + srv.on('close', function (code, message, flags) { assert.equal('', message); srv.close(); ws.terminate(); @@ -1486,13 +1485,13 @@ describe('WebSocket', function() { }); }); - it('with message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.close(1000, 'some reason'); }); - srv.on('close', function(code, message, flags) { + srv.on('close', function (code, message, flags) { assert.ok(flags.masked); assert.equal('some reason', message); srv.close(); @@ -1502,13 +1501,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.close(1000, 'some reason', {mask: true}); }); - srv.on('close', function(code, message, flags) { + srv.on('close', function (code, message, flags) { assert.ok(flags.masked); assert.equal('some reason', message); srv.close(); @@ -1518,15 +1517,15 @@ describe('WebSocket', function() { }); }); - it('ends connection to the server', function(done) { - server.createServer(++port, function(srv) { + it('ends connection to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var connectedOnce = false; - ws.on('open', function() { + ws.on('open', function () { connectedOnce = true; ws.close(1000, 'some reason', {mask: true}); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(connectedOnce); srv.close(); ws.terminate(); @@ -1535,9 +1534,9 @@ describe('WebSocket', function() { }); }); - it('consumes all data when the server socket closed', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - wss.on('connection', function(conn) { + it('consumes all data when the server socket closed', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + wss.on('connection', function (conn) { conn.send('foo'); conn.send('bar'); conn.send('baz'); @@ -1572,9 +1571,9 @@ describe('WebSocket', function() { }); }); - describe('W3C API emulation', function() { - it('should not throw errors when getting and setting', function(done) { - server.createServer(++port, function(srv) { + describe('W3C API emulation', function () { + it('should not throw errors when getting and setting', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var listener = function () {}; @@ -1598,34 +1597,34 @@ describe('WebSocket', function() { }); }); - it('should work the same as the EventEmitter api', function(done) { - server.createServer(++port, function(srv) { + it('should work the same as the EventEmitter api', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - var listener = function() {}; + var listener = function () {}; var message = 0; var close = 0; var open = 0; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(!!messageEvent.data); ++message; ws.close(); }; - ws.onopen = function() { + ws.onopen = function () { ++open; - } + }; - ws.onclose = function() { + ws.onclose = function () { ++close; - } + }; - ws.on('open', function() { + ws.on('open', function () { ws.send('foo'); }); - ws.on('close', function() { - process.nextTick(function() { + ws.on('close', function () { + process.nextTick(function () { assert.ok(message === 1); assert.ok(open === 1); assert.ok(close === 1); @@ -1638,13 +1637,13 @@ describe('WebSocket', function() { }); }); - it('should receive text data wrapped in a MessageEvent when using addEventListener', function(done) { - server.createServer(++port, function(srv) { + it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('open', function() { + ws.addEventListener('open', function () { ws.send('hi'); }); - ws.addEventListener('message', function(messageEvent) { + ws.addEventListener('message', function (messageEvent) { assert.equal('hi', messageEvent.data); ws.terminate(); srv.close(); @@ -1653,10 +1652,10 @@ describe('WebSocket', function() { }); }); - it('should receive valid CloseEvent when server closes with code 1000', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should receive valid CloseEvent when server closes with code 1000', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal(true, closeEvent.wasClean); assert.equal(1000, closeEvent.code); ws.terminate(); @@ -1664,15 +1663,15 @@ describe('WebSocket', function() { done(); }); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.close(1000); }); }); - it('should receive valid CloseEvent when server closes with code 1001', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should receive valid CloseEvent when server closes with code 1001', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal(false, closeEvent.wasClean); assert.equal(1001, closeEvent.code); assert.equal('some daft reason', closeEvent.reason); @@ -1681,72 +1680,72 @@ describe('WebSocket', function() { done(); }); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.close(1001, 'some daft reason'); }); }); - it('should have target set on Events', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should have target set on Events', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('open', function(openEvent) { + ws.addEventListener('open', function (openEvent) { assert.equal(ws, openEvent.target); }); - ws.addEventListener('message', function(messageEvent) { + ws.addEventListener('message', function (messageEvent) { assert.equal(ws, messageEvent.target); wss.close(); }); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal(ws, closeEvent.target); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function(errorEvent) { + ws.addEventListener('error', function (errorEvent) { assert.equal(errorEvent.message, 'forced'); assert.equal(ws, errorEvent.target); ws.terminate(); done(); }); }); - wss.on('connection', function(client) { - client.send('hi') + wss.on('connection', function (client) { + client.send('hi'); }); }); - it('should have type set on Events', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should have type set on Events', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('open', function(openEvent) { + ws.addEventListener('open', function (openEvent) { assert.equal('open', openEvent.type); }); - ws.addEventListener('message', function(messageEvent) { + ws.addEventListener('message', function (messageEvent) { assert.equal('message', messageEvent.type); wss.close(); }); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal('close', closeEvent.type); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function(errorEvent) { + ws.addEventListener('error', function (errorEvent) { assert.equal(errorEvent.message, 'forced'); assert.equal('error', errorEvent.type); ws.terminate(); done(); }); }); - wss.on('connection', function(client) { - client.send('hi') + wss.on('connection', function (client) { + client.send('hi'); }); }); - it('should pass binary data as a node.js Buffer by default', function(done) { - server.createServer(++port, function(srv) { + it('should pass binary data as a node.js Buffer by default', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Uint8Array(4096); - ws.onopen = function() { + ws.onopen = function () { ws.send(array, {binary: true}); }; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(messageEvent.binary); assert.ok(ws.binaryType === 'nodebuffer'); assert.ok(messageEvent.data instanceof Buffer); @@ -1757,16 +1756,16 @@ describe('WebSocket', function() { }); }); - it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function(done) { - server.createServer(++port, function(srv) { + it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.binaryType = 'arraybuffer'; var array = new Uint8Array(4096); - ws.onopen = function() { + ws.onopen = function () { ws.send(array, {binary: true}); }; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(messageEvent.binary); assert.ok(messageEvent.data instanceof ArrayBuffer); ws.terminate(); @@ -1776,15 +1775,15 @@ describe('WebSocket', function() { }); }); - it('should ignore binaryType for text messages', function(done) { - server.createServer(++port, function(srv) { + it('should ignore binaryType for text messages', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.binaryType = 'arraybuffer'; - ws.onopen = function() { + ws.onopen = function () { ws.send('foobar'); }; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(!messageEvent.binary); assert.ok(typeof messageEvent.data === 'string'); ws.terminate(); @@ -1793,11 +1792,10 @@ describe('WebSocket', function() { }; }); }); - }); - describe('ssl', function() { - it('can connect to secure websocket server', function(done) { + describe('ssl', function () { + it('can connect to secure websocket server', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -1807,10 +1805,10 @@ describe('WebSocket', function() { res.end(); }); var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -1818,7 +1816,7 @@ describe('WebSocket', function() { }); }); - it('can connect to secure websocket server with client side certificate', function(done) { + it('can connect to secure websocket server with client side certificate', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem'), @@ -1836,15 +1834,15 @@ describe('WebSocket', function() { var success = false; var wss = new WebSocketServer({ server: app, - verifyClient: function(info) { + verifyClient: function (info) { success = !!info.req.client.authorized; return true; } }); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port, clientOptions); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -1853,7 +1851,7 @@ describe('WebSocket', function() { }); }); - it('cannot connect to secure websocket server via ws://', function(done) { + it('cannot connect to secure websocket server via ws://', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -1863,9 +1861,9 @@ describe('WebSocket', function() { res.end(); }); var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { - var ws = new WebSocket('ws://localhost:' + port, { rejectUnauthorized :false }); - ws.on('error', function() { + app.listen(++port, function () { + var ws = new WebSocket('ws://localhost:' + port, { rejectUnauthorized: false }); + ws.on('error', function () { app.close(); ws.terminate(); wss.close(); @@ -1874,7 +1872,7 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { + it('can send and receive text data', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -1884,14 +1882,14 @@ describe('WebSocket', function() { res.end(); }); var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('foobar'); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { message.should.eql('foobar'); app.close(); ws.terminate(); @@ -1901,24 +1899,24 @@ describe('WebSocket', function() { }); }); - it('can send and receive very long binary data', function(done) { + it('can send and receive very long binary data', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') - } + }; var app = https.createServer(options, function (req, res) { res.writeHead(200); res.end(); }); - crypto.randomBytes(5 * 1024 * 1024, function(ex, buf) { + crypto.randomBytes(5 * 1024 * 1024, function (ex, buf) { if (ex) throw ex; var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send(buf, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { flags.binary.should.be.ok; areArraysEqual(buf, message).should.be.ok; app.close(); @@ -1927,8 +1925,8 @@ describe('WebSocket', function() { done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {binary: true}); }); }); @@ -1936,14 +1934,14 @@ describe('WebSocket', function() { }); }); - describe('protocol support discovery', function() { - describe('#supports', function() { - describe('#binary', function() { - it('returns true', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('protocol support discovery', function () { + describe('#supports', function () { + describe('#binary', function () { + it('returns true', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { assert.equal(true, client.supports.binary); wss.close(); done(); @@ -1953,11 +1951,11 @@ describe('WebSocket', function() { }); }); - describe('host and origin headers', function() { - it('includes the host header with port number', function(done) { + describe('host and origin headers', function () { + it('includes the host header with port number', function (done) { var srv = http.createServer(); - srv.listen(++port, function(){ - srv.on('upgrade', function(req, socket, upgradeHeade) { + srv.listen(++port, function () { + srv.on('upgrade', function (req, socket, upgradeHeade) { assert.equal('localhost:' + port, req.headers['host']); srv.close(); done(); @@ -1966,10 +1964,10 @@ describe('WebSocket', function() { }); }); - it('lacks default origin header', function(done) { + it('lacks default origin header', function (done) { var srv = http.createServer(); - srv.listen(++port, function() { - srv.on('upgrade', function(req, socket, upgradeHeade) { + srv.listen(++port, function () { + srv.on('upgrade', function (req, socket, upgradeHeade) { should(req.headers).not.have.property('origin'); srv.close(); done(); @@ -1978,11 +1976,11 @@ describe('WebSocket', function() { }); }); - it('honors origin set in options', function(done) { + it('honors origin set in options', function (done) { var srv = http.createServer(); - srv.listen(++port, function() { - var options = {origin: 'https://example.com:8000'} - srv.on('upgrade', function(req, socket, upgradeHeade) { + srv.listen(++port, function () { + var options = {origin: 'https://example.com:8000'}; + srv.on('upgrade', function (req, socket, upgradeHeade) { assert.equal(options.origin, req.headers['origin']); srv.close(); done(); @@ -1991,32 +1989,32 @@ describe('WebSocket', function() { }); }); - it('excludes default ports from host header', function(done) { + it('excludes default ports from host header', function (done) { // can't create a server listening on ports 80 or 443 // so we need to expose the method that does this - var buildHostHeader = WebSocket.buildHostHeader - var host = buildHostHeader(false, 'localhost', 80) + var buildHostHeader = WebSocket.buildHostHeader; + var host = buildHostHeader(false, 'localhost', 80); assert.equal('localhost', host); - host = buildHostHeader(false, 'localhost', 88) + host = buildHostHeader(false, 'localhost', 88); assert.equal('localhost:88', host); - host = buildHostHeader(true, 'localhost', 443) + host = buildHostHeader(true, 'localhost', 443); assert.equal('localhost', host); - host = buildHostHeader(true, 'localhost', 8443) + host = buildHostHeader(true, 'localhost', 8443); assert.equal('localhost:8443', host); - done() + done(); }); }); - describe('permessage-deflate', function() { - it('is enabled by default', function(done) { + describe('permessage-deflate', function () { + it('is enabled by default', function (done) { var srv = http.createServer(function (req, res) {}); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function() { + srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port); - srv.on('upgrade', function(req, socket, head) { + srv.on('upgrade', function (req, socket, head) { assert.ok(~req.headers['sec-websocket-extensions'].indexOf('permessage-deflate')); }); - ws.on('open', function() { + ws.on('open', function () { assert.ok(ws.extensions['permessage-deflate']); ws.terminate(); wss.close(); @@ -2025,12 +2023,12 @@ describe('WebSocket', function() { }); }); - it('can be disabled', function(done) { + it('can be disabled', function (done) { var srv = http.createServer(function (req, res) {}); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function() { + srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); - srv.on('upgrade', function(req, socket, head) { + srv.on('upgrade', function (req, socket, head) { assert.ok(!req.headers['sec-websocket-extensions']); ws.terminate(); wss.close(); @@ -2039,10 +2037,10 @@ describe('WebSocket', function() { }); }); - it('can send extension parameters', function(done) { + it('can send extension parameters', function (done) { var srv = http.createServer(function (req, res) {}); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function() { + srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: { serverNoContextTakeover: true, @@ -2051,7 +2049,7 @@ describe('WebSocket', function() { clientMaxWindowBits: true } }); - srv.on('upgrade', function(req, socket, head) { + srv.on('upgrade', function (req, socket, head) { var extensions = req.headers['sec-websocket-extensions']; assert.ok(~extensions.indexOf('permessage-deflate')); assert.ok(~extensions.indexOf('server_no_context_takeover')); @@ -2065,89 +2063,89 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + it('can send and receive text data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi', {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.equal('hi', message); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); - it('can send and receive a typed array', function(done) { + it('can send and receive a typed array', function (done) { var array = new Float32Array(5); for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); - it('can send and receive ArrayBuffer', function(done) { + it('can send and receive ArrayBuffer', function (done) { var array = new Float32Array(5); for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - ws.on('open', function() { + ws.on('open', function () { ws.send(array.buffer, {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); - it('with binary stream will send fragmented data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + it('with binary stream will send fragmented data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); var callbackFired = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true, compress: true}, function(error) { + ws.send(fileStream, {binary: true, compress: true}, function (error) { assert.equal(null, error); callbackFired = true; }); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(callbackFired); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(data, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (data, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); ws.terminate(); @@ -2155,41 +2153,41 @@ describe('WebSocket', function() { }); }); - describe('#send', function() { - it('can set the compress option true when perMessageDeflate is disabled', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#send', function () { + it('can set the compress option true when perMessageDeflate is disabled', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi', {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.equal('hi', message); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); }); - describe('#close', function() { - it('should not raise error callback, if any, if called during send data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + describe('#close', function () { + it('should not raise error callback, if any, if called during send data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); var errorGiven = false; - ws.on('open', function() { - ws.send('hi', function(error) { + ws.on('open', function () { + ws.send('hi', function (error) { errorGiven = error != null; }); ws.close(); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { assert.ok(!errorGiven); wss.close(); ws.terminate(); @@ -2200,19 +2198,19 @@ describe('WebSocket', function() { }); }); - describe('#terminate', function() { - it('will raise error callback, if any, if called during send data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + describe('#terminate', function () { + it('will raise error callback, if any, if called during send data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: { threshold: 0 }}); var errorGiven = false; - ws.on('open', function() { - ws.send('hi', function(error) { + ws.on('open', function () { + ws.send('hi', function (error) { errorGiven = error != null; }); ws.terminate(); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { assert.ok(errorGiven); wss.close(); ws.terminate(); @@ -2222,19 +2220,19 @@ describe('WebSocket', function() { }); }); - it('can call during receiving data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + it('can call during receiving data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - wss.on('connection', function(client) { + wss.on('connection', function (client) { for (var i = 0; i < 10; i++) { client.send('hi'); } - client.send('hi', function() { + client.send('hi', function () { ws.terminate(); }); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { wss.close(); done(); }, 1000); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index e8a08d80c..347d4e110 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -1,13 +1,13 @@ -var http = require('http') - , https = require('https') - , WebSocket = require('../') - , WebSocketServer = WebSocket.Server - , fs = require('fs') - , should = require('should'); +var http = require('http'), + https = require('https'), + WebSocket = require('../'), + WebSocketServer = WebSocket.Server, + fs = require('fs'), + should = require('should'); var port = 8000; -function getArrayBuffer(buf) { +function getArrayBuffer (buf) { var l = buf.length; var arrayBuf = new ArrayBuffer(l); for (var i = 0; i < l; ++i) { @@ -16,7 +16,7 @@ function getArrayBuffer(buf) { return arrayBuf; } -function areArraysEqual(x, y) { +function areArraysEqual (x, y) { if (x.length != y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; @@ -24,15 +24,15 @@ function areArraysEqual(x, y) { return true; } -describe('WebSocketServer', function() { - describe('#ctor', function() { - it('should return a new instance if called without new', function(done) { +describe('WebSocketServer', function () { + describe('#ctor', function () { + it('should return a new instance if called without new', function (done) { var ws = WebSocketServer({noServer: true}); ws.should.be.an.instanceOf(WebSocketServer); done(); }); - it('throws an error if no option object is passed', function() { + it('throws an error if no option object is passed', function () { var gotException = false; try { var wss = new WebSocketServer(); @@ -43,7 +43,7 @@ describe('WebSocketServer', function() { gotException.should.be.ok; }); - it('throws an error if no port or server is specified', function() { + it('throws an error if no port or server is specified', function () { var gotException = false; try { var wss = new WebSocketServer({}); @@ -54,7 +54,7 @@ describe('WebSocketServer', function() { gotException.should.be.ok; }); - it('does not throw an error if no port or server is specified, when the noServer option is true', function() { + it('does not throw an error if no port or server is specified, when the noServer option is true', function () { var gotException = false; try { var wss = new WebSocketServer({noServer: true}); @@ -65,20 +65,20 @@ describe('WebSocketServer', function() { gotException.should.eql(false); }); - it('emits an error if http server bind fails', function(done) { + it('emits an error if http server bind fails', function (done) { var wss1 = new WebSocketServer({port: 50003}); var wss2 = new WebSocketServer({port: 50003}); - wss2.on('error', function() { + wss2.on('error', function () { wss1.close(); done(); }); }); - it('starts a server on a given port', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('starts a server on a given port', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); done(); }); @@ -90,7 +90,7 @@ describe('WebSocketServer', function() { var wss = new WebSocketServer({server: srv}); var ws = new WebSocket('ws://localhost:' + port); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); srv.close(); done(); @@ -115,15 +115,15 @@ describe('WebSocketServer', function() { }); // Don't test this on Windows. It throws errors for obvious reasons. - if(!/^win/i.test(process.platform)) { + if (!/^win/i.test(process.platform)) { it('uses a precreated http server listening on unix socket', function (done) { var srv = http.createServer(); - var sockPath = '/tmp/ws_socket_'+new Date().getTime()+'.'+Math.floor(Math.random() * 1000); + var sockPath = '/tmp/ws_socket_' + new Date().getTime() + '.' + Math.floor(Math.random() * 1000); srv.listen(sockPath, function () { var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws+unix://'+sockPath); + var ws = new WebSocket('ws+unix://' + sockPath); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); srv.close(); done(); @@ -136,9 +136,9 @@ describe('WebSocketServer', function() { var srv = http.createServer(); srv.listen(++port, function () { var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port+'/endpointName'); + var ws = new WebSocket('ws://localhost:' + port + '/endpointName'); - wss.on('connection/endpointName', function(client) { + wss.on('connection/endpointName', function (client) { wss.close(); srv.close(); done(); @@ -146,20 +146,20 @@ describe('WebSocketServer', function() { }); }); - it('can have two different instances listening on the same http server with two different paths', function(done) { + it('can have two different instances listening on the same http server with two different paths', function (done) { var srv = http.createServer(); srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}) - , wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), + wss2 = new WebSocketServer({server: srv, path: '/wss2'}); var doneCount = 0; - wss1.on('connection', function(client) { + wss1.on('connection', function (client) { wss1.close(); if (++doneCount == 2) { srv.close(); done(); } }); - wss2.on('connection', function(client) { + wss2.on('connection', function (client) { wss2.close(); if (++doneCount == 2) { srv.close(); @@ -171,7 +171,7 @@ describe('WebSocketServer', function() { }); }); - it('cannot have two different instances listening on the same http server with the same path', function(done) { + it('cannot have two different instances listening on the same http server with the same path', function (done) { var srv = http.createServer(); srv.listen(++port, function () { var wss1 = new WebSocketServer({server: srv, path: '/wss1'}); @@ -185,26 +185,26 @@ describe('WebSocketServer', function() { } }); }); - it('will not crash when it receives an unhandled opcode', function(done) { + it('will not crash when it receives an unhandled opcode', function (done) { var wss = new WebSocketServer({ port: 8080 }); - wss.on('connection', function connection(ws) { - ws.onerror = function(error) { - done(); - }; + wss.on('connection', function connection (ws) { + ws.onerror = function (error) { + done(); + }; }); var socket = new WebSocket('ws://127.0.0.1:8080/'); - socket.onopen = function() { - socket._socket.write(new Buffer([5])); - socket.send(''); + socket.onopen = function () { + socket._socket.write(new Buffer([5])); + socket.send(''); }; }); }); - describe('#close', function() { - it('does not thrown when called twice', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#close', function () { + it('does not thrown when called twice', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { wss.close(); wss.close(); wss.close(); @@ -213,32 +213,32 @@ describe('WebSocketServer', function() { }); }); - it('will close all clients', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('will close all clients', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.on('close', function() { + ws.on('close', function () { if (++closes == 2) done(); }); }); var closes = 0; - wss.on('connection', function(client) { - client.on('close', function() { + wss.on('connection', function (client) { + client.on('close', function () { if (++closes == 2) done(); }); wss.close(); }); }); - it('does not close a precreated server', function(done) { + it('does not close a precreated server', function (done) { var srv = http.createServer(); var realClose = srv.close; - srv.close = function() { + srv.close = function () { should.fail('must not close pre-created server'); - } + }; srv.listen(++port, function () { var wss = new WebSocketServer({server: srv}); var ws = new WebSocket('ws://localhost:' + port); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); srv.close = realClose; srv.close(); @@ -247,23 +247,23 @@ describe('WebSocketServer', function() { }); }); - it('cleans event handlers on precreated server', function(done) { + it('cleans event handlers on precreated server', function (done) { var srv = http.createServer(); - srv.listen(++port, function() { + srv.listen(++port, function () { var wss = new WebSocketServer({server: srv}); wss.close(); srv.emit('upgrade'); - srv.on('error', function() {}); + srv.on('error', function () {}); srv.emit('error'); - done() + done(); }); }); - it('cleans up websocket data on a precreated server', function(done) { + it('cleans up websocket data on a precreated server', function (done) { var srv = http.createServer(); srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}) - , wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), + wss2 = new WebSocketServer({server: srv, path: '/wss2'}); (typeof srv._webSocketPaths).should.eql('object'); Object.keys(srv._webSocketPaths).length.should.eql(2); wss1.close(); @@ -276,38 +276,38 @@ describe('WebSocketServer', function() { }); }); - describe('#clients', function() { - it('returns a list of connected clients', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#clients', function () { + it('returns a list of connected clients', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); wss.close(); done(); }); }); - it('can be disabled', function(done) { - var wss = new WebSocketServer({port: ++port, clientTracking: false}, function() { + it('can be disabled', function (done) { + var wss = new WebSocketServer({port: ++port, clientTracking: false}, function () { wss.should.not.have.property('clients'); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.should.not.have.property('clients'); wss.close(); done(); }); }); - it('is updated when client terminates the connection', function(done) { + it('is updated when client terminates the connection', function (done) { var ws; - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { - client.on('close', function() { + wss.on('connection', function (client) { + client.on('close', function () { wss.clients.size.should.eql(0); wss.close(); done(); @@ -316,13 +316,13 @@ describe('WebSocketServer', function() { }); }); - it('is updated when client closes the connection', function(done) { + it('is updated when client closes the connection', function (done) { var ws; - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { - client.on('close', function() { + wss.on('connection', function (client) { + client.on('close', function () { wss.clients.size.should.eql(0); wss.close(); done(); @@ -332,9 +332,9 @@ describe('WebSocketServer', function() { }); }); - describe('#options', function() { - it('exposes options passed to constructor', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#options', function () { + it('exposes options passed to constructor', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { wss.options.port.should.eql(port); wss.close(); done(); @@ -342,41 +342,41 @@ describe('WebSocketServer', function() { }); }); - describe('#maxpayload', function() { - it('maxpayload is passed on to clients,', function(done) { + describe('#maxpayload', function () { + it('maxpayload is passed on to clients,', function (done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { + var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); client.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); }); - it('maxpayload is passed on to hybi receivers', function(done) { + it('maxpayload is passed on to hybi receivers', function (done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { + var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); client._receiver.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); }); - it('maxpayload is passed on to permessage-deflate', function(done) { + it('maxpayload is passed on to permessage-deflate', function (done) { var PerMessageDeflate = require('../lib/PerMessageDeflate'); var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { + var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); wss.close(); @@ -385,18 +385,18 @@ describe('WebSocketServer', function() { }); }); - describe('#handleUpgrade', function() { + describe('#handleUpgrade', function () { it('can be used for a pre-existing server', function (done) { var srv = http.createServer(); srv.listen(++port, function () { var wss = new WebSocketServer({noServer: true}); - srv.on('upgrade', function(req, socket, upgradeHead) { - wss.handleUpgrade(req, socket, upgradeHead, function(client) { + srv.on('upgrade', function (req, socket, upgradeHead) { + wss.handleUpgrade(req, socket, upgradeHead, function (client) { client.send('hello'); }); }); var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function(message) { + ws.on('message', function (message) { message.should.eql('hello'); wss.close(); srv.close(); @@ -406,7 +406,7 @@ describe('WebSocketServer', function() { }); it('closes the connection when path does not match', function (done) { - var wss = new WebSocketServer({port: ++port, path: '/ws'}, function() { + var wss = new WebSocketServer({port: ++port, path: '/ws'}, function () { var options = { port: port, host: '127.0.0.1', @@ -417,7 +417,7 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); @@ -448,10 +448,10 @@ describe('WebSocketServer', function() { }); }); - describe('hybi mode', function() { - describe('connection establishing', function() { - it('does not accept connections with no sec-websocket-key', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('hybi mode', function () { + describe('connection establishing', function () { + it('does not accept connections with no sec-websocket-key', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -462,20 +462,20 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with no sec-websocket-version', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with no sec-websocket-version', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -487,20 +487,20 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with invalid sec-websocket-version', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with invalid sec-websocket-version', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -513,22 +513,22 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be denied', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + it('client can be denied', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { return false; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -542,24 +542,24 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(401); - process.nextTick(function() { + process.nextTick(function () { wss.close(); done(); }); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be accepted', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + it('client can be accepted', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { return true; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -574,21 +574,21 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('verifyClient gets client origin', function(done) { + it('verifyClient gets client origin', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { info.origin.should.eql('http://foobarbaz.com'); verifyClientCalled = true; return false; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -602,22 +602,22 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { verifyClientCalled.should.be.ok; wss.close(); done(); }); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('verifyClient gets original request', function(done) { + it('verifyClient gets original request', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); verifyClientCalled = true; return false; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -631,16 +631,16 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { verifyClientCalled.should.be.ok; wss.close(); done(); }); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('verifyClient has secure:true for ssl connections', function(done) { + it('verifyClient has secure:true for ssl connections', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -652,15 +652,15 @@ describe('WebSocketServer', function() { var success = false; var wss = new WebSocketServer({ server: app, - verifyClient: function(info) { + verifyClient: function (info) { success = info.secure === true; return true; } }); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -669,7 +669,7 @@ describe('WebSocketServer', function() { }); }); - it('verifyClient has secure:false for non-ssl connections', function(done) { + it('verifyClient has secure:false for non-ssl connections', function (done) { var app = http.createServer(function (req, res) { res.writeHead(200); res.end(); @@ -677,15 +677,15 @@ describe('WebSocketServer', function() { var success = false; var wss = new WebSocketServer({ server: app, - verifyClient: function(info) { + verifyClient: function (info) { success = info.secure === false; return true; } }); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -694,12 +694,12 @@ describe('WebSocketServer', function() { }); }); - it('client can be denied asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - process.nextTick(function() { + it('client can be denied asynchronously', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { + process.nextTick(function () { cb(false); }); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -713,26 +713,26 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(401); - process.nextTick(function() { + process.nextTick(function () { wss.close(); done(); }); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be denied asynchronously with custom response code', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - process.nextTick(function() { + it('client can be denied asynchronously with custom response code', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { + process.nextTick(function () { cb(false, 404); }); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -746,26 +746,26 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(404); - process.nextTick(function() { + process.nextTick(function () { wss.close(); done(); }); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be accepted asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - process.nextTick(function() { + it('client can be accepted asynchronously', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { + process.nextTick(function () { cb(true); }); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -780,18 +780,18 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('handles messages passed along with the upgrade request (upgrade head)', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + it('handles messages passed along with the upgrade request (upgrade head)', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { return true; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -807,83 +807,83 @@ describe('WebSocketServer', function() { req.write(new Buffer([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f], 'binary')); req.end(); }); - wss.on('connection', function(ws) { - ws.on('message', function(data) { + wss.on('connection', function (ws) { + ws.on('message', function (data) { data.should.eql('Hello'); ws.terminate(); wss.close(); done(); }); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('selects the first protocol by default', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('selects the first protocol by default', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { - ws.protocol.should.eql('prot1'); - wss.close(); - done(); + ws.on('open', function (client) { + ws.protocol.should.eql('prot1'); + wss.close(); + done(); }); }); }); - it('selects the last protocol via protocol handler', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(true, ps[ps.length-1]); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('selects the last protocol via protocol handler', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(true, ps[ps.length - 1]); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { ws.protocol.should.eql('prot2'); wss.close(); done(); + }); }); - }); }); - it('client detects invalid server protocol', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(true, 'prot3'); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('client detects invalid server protocol', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(true, 'prot3'); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { done(new Error('connection must not be established')); - }); - ws.on('error', function() { + }); + ws.on('error', function () { done(); + }); }); - }); }); - it('client detects no server protocol', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(true); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('client detects no server protocol', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(true); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { done(new Error('connection must not be established')); - }); - ws.on('error', function() { + }); + ws.on('error', function () { done(); + }); }); - }); }); - it('client refuses server protocols', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(false); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('client refuses server protocols', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(false); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { done(new Error('connection must not be established')); - }); - ws.on('error', function() { + }); + ws.on('error', function () { done(); + }); }); - }); }); - it('server detects unauthorized protocol handler', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + it('server detects unauthorized protocol handler', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { cb(false); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -898,22 +898,22 @@ describe('WebSocketServer', function() { options.port = port; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(401); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('server detects invalid protocol handler', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + it('server detects invalid protocol handler', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { // not calling callback is an error and shouldn't timeout - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -928,20 +928,20 @@ describe('WebSocketServer', function() { options.port = port; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(501); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('accept connections with sec-websocket-extensions', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('accept connections with sec-websocket-extensions', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -956,30 +956,30 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); }); - describe('messaging', function() { - it('can send and receive data', function(done) { - var data = new Array(65*1024); + describe('messaging', function () { + it('can send and receive data', function (done) { + var data = new Array(65 * 1024); for (var i = 0; i < data.length; ++i) { data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); } data = data.join(''); - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { ws.send(message); }); }); - wss.on('connection', function(client) { - client.on('message', function(message) { + wss.on('connection', function (client) { + client.on('message', function (message) { message.should.eql(data); wss.close(); done(); @@ -990,34 +990,34 @@ describe('WebSocketServer', function() { }); }); - describe('client properties', function() { - it('protocol is exposed', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('client properties', function () { + it('protocol is exposed', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, 'hi'); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.protocol.should.eql('hi'); wss.close(); done(); }); }); - it('protocolVersion is exposed', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('protocolVersion is exposed', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.protocolVersion.should.eql(8); wss.close(); done(); }); }); - it('upgradeReq is the original request object', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('upgradeReq is the original request object', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.upgradeReq.httpVersion.should.eql('1.1'); wss.close(); done(); @@ -1025,9 +1025,9 @@ describe('WebSocketServer', function() { }); }); - describe('permessage-deflate', function() { - it('accept connections with permessage-deflate extension', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('permessage-deflate', function () { + it('accept connections with permessage-deflate extension', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -1042,16 +1042,16 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with not defined extension parameter', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with not defined extension parameter', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -1065,20 +1065,20 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with invalid extension parameter', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with invalid extension parameter', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -1092,16 +1092,16 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); }); }); diff --git a/test/autobahn-server.js b/test/autobahn-server.js index 36fe0c246..74aab5aa3 100644 --- a/test/autobahn-server.js +++ b/test/autobahn-server.js @@ -1,6 +1,6 @@ var WebSocketServer = require('../').Server; -process.on('uncaughtException', function(err) { +process.on('uncaughtException', function (err) { console.log('Caught exception: ', err, err.stack); }); @@ -8,22 +8,22 @@ process.on('SIGINT', function () { try { console.log('Updating reports and shutting down'); var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function() { + ws.on('close', function () { process.exit(); }); } - catch(e) { + catch (e) { process.exit(); } }); var wss = new WebSocketServer({port: 8181}); -wss.on('connection', function(ws) { +wss.on('connection', function (ws) { console.log('new connection'); - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { ws.send(flags.buffer, {binary: flags.binary === true}); }); - ws.on('error', function() { + ws.on('error', function () { console.log('error', arguments); }); }); diff --git a/test/autobahn.js b/test/autobahn.js index 048cc9041..2ea7f1c78 100644 --- a/test/autobahn.js +++ b/test/autobahn.js @@ -3,7 +3,7 @@ var currentTest = 1; var lastTest = -1; var testCount = null; -process.on('uncaughtException', function(err) { +process.on('uncaughtException', function (err) { console.log('Caught exception: ', err, err.stack); }); @@ -11,42 +11,42 @@ process.on('SIGINT', function () { try { console.log('Updating reports and shutting down'); var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function() { + ws.on('close', function () { process.exit(); }); } - catch(e) { + catch (e) { process.exit(); } }); -function nextTest() { +function nextTest () { if (currentTest > testCount || (lastTest != -1 && currentTest > lastTest)) { console.log('Updating reports and shutting down'); var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function() { + ws.on('close', function () { process.exit(); }); return; - }; + } console.log('Running test case ' + currentTest + '/' + testCount); var ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { ws.send(flags.buffer, {binary: flags.binary === true, mask: true}); }); - ws.on('close', function(data) { + ws.on('close', function (data) { currentTest += 1; process.nextTick(nextTest); }); - ws.on('error', function(e) {}); + ws.on('error', function (e) {}); } var ws = new WebSocket('ws://localhost:9001/getCaseCount'); -ws.on('message', function(data, flags) { +ws.on('message', function (data, flags) { testCount = parseInt(data); }); -ws.on('close', function() { +ws.on('close', function () { if (testCount > 0) { nextTest(); } -}); \ No newline at end of file +}); diff --git a/test/testserver.js b/test/testserver.js index be158b5c4..356509fd7 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,9 +1,9 @@ -var http = require('http') - , util = require('util') - , crypto = require('crypto') - , events = require('events') - , Sender = require('../lib/Sender') - , Receiver = require('../lib/Receiver'); +var http = require('http'), + util = require('util'), + crypto = require('crypto'), + events = require('events'), + Sender = require('../lib/Sender'), + Receiver = require('../lib/Receiver'); module.exports = { handlers: { @@ -12,7 +12,7 @@ module.exports = { closeAfterConnect: closeAfterConnectHandler, return401: return401 }, - createServer: function(port, handler, cb) { + createServer: function (port, handler, cb) { if (handler && !cb) { cb = handler; handler = null; @@ -22,11 +22,11 @@ module.exports = { res.end('okay'); }); var srv = new Server(webServer); - webServer.on('upgrade', function(req, socket) { + webServer.on('upgrade', function (req, socket) { webServer._socket = socket; (handler || validServer)(srv, req, socket); }); - webServer.listen(port, '127.0.0.1', function() { cb(srv); }); + webServer.listen(port, '127.0.0.1', function () { cb(srv); }); } }; @@ -34,7 +34,7 @@ module.exports = { * Test strategies */ -function validServer(server, req, socket) { +function validServer (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); @@ -49,14 +49,14 @@ function validServer(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; socket.write(headers.concat('', '').join('\r\n')); @@ -85,7 +85,7 @@ function validServer(server, req, socket) { }; receiver.onclose = function (code, message, flags) { flags = flags || {}; - sender.close(code, message, false, function(err) { + sender.close(code, message, false, function (err) { server.emit('close', code, message, flags); socket.end(); }); @@ -93,12 +93,12 @@ function validServer(server, req, socket) { socket.on('data', function (data) { receiver.add(data); }); - socket.on('end', function() { + socket.on('end', function () { socket.end(); }); } -function invalidRequestHandler(server, req, socket) { +function invalidRequestHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); @@ -113,21 +113,21 @@ function invalidRequestHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "bogus", 'binary'); + shasum.update(key + 'bogus', 'binary'); key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; socket.write(headers.concat('', '').join('\r\n')); socket.end(); } -function closeAfterConnectHandler(server, req, socket) { +function closeAfterConnectHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); @@ -142,25 +142,24 @@ function closeAfterConnectHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; socket.write(headers.concat('', '').join('\r\n')); socket.end(); } - -function return401(server, req, socket) { +function return401 (server, req, socket) { var headers = [ - 'HTTP/1.1 401 Unauthorized' - , 'Content-type: text/html' + 'HTTP/1.1 401 Unauthorized', + 'Content-type: text/html' ]; socket.write(headers.concat('', '').join('\r\n')); @@ -172,13 +171,13 @@ function return401(server, req, socket) { * Server object, which will do the actual emitting */ -function Server(webServer) { +function Server (webServer) { this.webServer = webServer; } util.inherits(Server, events.EventEmitter); -Server.prototype.close = function() { +Server.prototype.close = function () { this.webServer.close(); if (this._socket) this._socket.end(); -} +}; From fa66b98d562dca70575395e2fb912b3bf27611db Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 18:01:25 +0200 Subject: [PATCH 094/489] [list] Fix remaining lint issues on test/* --- test/PerMessageDeflate.test.js | 13 +- test/Sender.test.js | 11 +- test/WebSocket.integration.js | 31 +- test/WebSocket.test.js | 498 +++++++++++++---------------- test/WebSocketServer.test.js | 566 +++++++++++++++------------------ test/autobahn-server.js | 8 +- test/autobahn.js | 14 +- test/testserver.js | 19 +- 8 files changed, 522 insertions(+), 638 deletions(-) diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index c9608d40a..73e324c16 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -1,14 +1,15 @@ -var PerMessageDeflate = require('../lib/PerMessageDeflate'); -var Extensions = require('../lib/Extensions'); +'use strict'; + +const PerMessageDeflate = require('../lib/PerMessageDeflate'); +const Extensions = require('../lib/Extensions'); require('should'); describe('PerMessageDeflate', function () { describe('#ctor', function () { it('throws TypeError when called without new', function (done) { try { - var perMessageDeflate = PerMessageDeflate(); - } - catch (e) { + PerMessageDeflate(); + } catch (e) { e.should.be.instanceof(TypeError); done(); } @@ -232,7 +233,7 @@ describe('PerMessageDeflate', function () { if (err) return done(err); perMessageDeflate.decompress(compressed2, true, function (err, data2) { if (err) return done(err); - new Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); + Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); done(); }); }); diff --git a/test/Sender.test.js b/test/Sender.test.js index 862071c04..f81b96d64 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -1,14 +1,15 @@ -var Sender = require('../lib/Sender'), - PerMessageDeflate = require('../lib/PerMessageDeflate'); +'use strict'; + +const Sender = require('../lib/Sender'); +const PerMessageDeflate = require('../lib/PerMessageDeflate'); require('should'); describe('Sender', function () { describe('#ctor', function () { it('throws TypeError when called without new', function (done) { try { - var sender = Sender({ write: function () {} }); - } - catch (e) { + Sender({ write: function () {} }); + } catch (e) { e.should.be.instanceof(TypeError); done(); } diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index c21821566..228ffcb93 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -1,31 +1,14 @@ -var assert = require('assert'), - WebSocket = require('../'), - server = require('./testserver'); +'use strict'; -var port = 20000; - -function getArrayBuffer (buf) { - var l = buf.length; - var arrayBuf = new ArrayBuffer(l); - var uint8View = new Uint8Array(arrayBuf); - - for (var i = 0; i < l; i++) { - uint8View[i] = buf[i]; - } - return uint8View.buffer; -} - -function areArraysEqual (x, y) { - if (x.length != y.length) return false; - for (var i = 0, l = x.length; i < l; ++i) { - if (x[i] !== y[i]) return false; - } - return true; -} +const assert = require('assert'); +const WebSocket = require('../'); describe('WebSocket', function () { it('communicates successfully with echo service', function (done) { - var ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 13, origin: 'http://websocket.org'}); + var ws = new WebSocket('ws://echo.websocket.org/', { + origin: 'http://websocket.org', + protocolVersion: 13 + }); var str = Date.now().toString(); var dataReceived = false; ws.on('open', function () { diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 6fbbc7801..425689e73 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1,15 +1,19 @@ -var assert = require('assert'), - https = require('https'), - http = require('http'), - should = require('should'), - WebSocket = require('../'), - WebSocketServer = require('../').Server, - fs = require('fs'), - os = require('os'), - server = require('./testserver'), - crypto = require('crypto'); - -var port = 20000; +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$", "args": "none" }] */ + +'use strict'; + +const assert = require('assert'); +const https = require('https'); +const http = require('http'); +const WebSocket = require('../'); +const fs = require('fs'); +const os = require('os'); +const server = require('./testserver'); +const crypto = require('crypto'); + +const WebSocketServer = WebSocket.Server; + +let port = 20000; function getArrayBuffer (buf) { var l = buf.length; @@ -22,7 +26,7 @@ function getArrayBuffer (buf) { } function areArraysEqual (x, y) { - if (x.length != y.length) return false; + if (x.length !== y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; } @@ -31,87 +35,58 @@ function areArraysEqual (x, y) { describe('WebSocket', function () { describe('#ctor', function () { - it('throws exception for invalid url', function (done) { - try { - var ws = new WebSocket('echo.websocket.org'); - } - catch (e) { - done(); - } + it('should return a new instance if called without new', function (done) { + var ws = WebSocket('ws://localhost'); + + assert.ok(ws instanceof WebSocket); + ws.on('error', () => done()); }); - it('should return a new instance if called without new', function (done) { - var ws = WebSocket('ws://localhost:' + port); - ws.should.be.an.instanceOf(WebSocket); - done(); + it('throws exception for invalid url', function () { + assert.throws(() => new WebSocket('echo.websocket.org')); }); }); describe('options', function () { it('should accept an `agent` option', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var agent = { - addRequest: function () { - wss.close(); - done(); - } - }; - var ws = new WebSocket('ws://localhost:' + port, { agent: agent }); - }); + const agent = { addRequest: () => done() }; + const ws = new WebSocket('ws://localhost', { agent }); }); + // GH-227 it('should accept the `options` object as the 3rd argument', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var agent = { - addRequest: function () { - wss.close(); - done(); - } - }; - var ws = new WebSocket('ws://localhost:' + port, [], { agent: agent }); - }); + const agent = { addRequest: () => done() }; + const ws = new WebSocket('ws://localhost', [], { agent }); }); it('should accept the localAddress option', function (done) { // explore existing interfaces - var devs = os.networkInterfaces(), - localAddresses = [], - j, ifc, dev, devname; - for (devname in devs) { - dev = devs[devname]; - for (j = 0; j < dev.length; j++) { - ifc = dev[j]; + const devs = os.networkInterfaces(); + const localAddresses = []; + + Object.keys(devs).forEach((name) => { + devs[name].forEach((ifc) => { if (!ifc.internal && ifc.family === 'IPv4') { localAddresses.push(ifc.address); } - } - } - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, { localAddress: localAddresses[0] }); - ws.on('open', function () { - done(); }); }); - }); - it('should accept the localAddress option whether it was wrong interface', function (done) { - if (process.platform === 'linux' && process.version.match(/^v0\.([0-9]\.|10)/)) { - return done(); - } - var wss = new WebSocketServer({port: ++port}, function () { - try { - var ws = new WebSocket('ws://localhost:' + port, { localAddress: '123.456.789.428' }); - ws.on('error', function (error) { - error.code.should.eql('EADDRNOTAVAIL'); - done(); - }); - } - catch (e) { - e.should.match(/localAddress must be a valid IP/); - done(); - } + const wss = new WebSocketServer({ port: ++port }, function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + localAddress: localAddresses[0] + }); + + ws.on('open', () => wss.close(done)); }); }); + + it('should accept the localAddress option whether it was wrong interface', function () { + assert.throws( + () => new WebSocket(`ws://localhost:${port}`, { localAddress: '123.456.789.428' }), + /must be a valid IP: 123.456.789.428/ + ); + }); }); describe('properties', function () { @@ -119,7 +94,7 @@ describe('WebSocket', function () { var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: false }); ws.on('message', function () { - ws.bytesReceived.should.eql(8); + assert.strictEqual(ws.bytesReceived, 8); wss.close(); done(); }); @@ -185,19 +160,16 @@ describe('WebSocket', function () { }); it('stress kernel write buffer', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: false }); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { while (true) { if (ws.bufferedAmount > 0) break; - ws.send((new Array(10000)).join('hello')); + ws.send('hello'.repeat(1e4)); } - ws.terminate(); - ws.on('close', function () { - wss.close(); - done(); - }); + wss.close(done); }); }); }); @@ -205,7 +177,7 @@ describe('WebSocket', function () { describe('Custom headers', function () { it('request has an authorization header', function (done) { var auth = 'test:testpass'; - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv}); srv.listen(++port); var ws = new WebSocket('ws://' + auth + '@localhost:' + port); @@ -222,7 +194,7 @@ describe('WebSocket', function () { }); it('accepts custom headers', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv}); srv.listen(++port); @@ -523,10 +495,10 @@ describe('WebSocket', function () { var serverClient; var openCount = 0; function onOpen () { - if (++openCount == 2) { + if (++openCount === 2) { var paused = true; serverClient.on('message', function () { - paused.should.not.be.ok; + assert.ok(!paused); wss.close(); done(); }); @@ -557,8 +529,7 @@ describe('WebSocket', function () { ws.on('error', function () {}); try { ws.ping(); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -583,7 +554,7 @@ describe('WebSocket', function () { ws.on('open', function () { ws.ping(); }); - srv.on('ping', function (message) { + srv.on('ping', function () { srv.close(); ws.terminate(); done(); @@ -647,8 +618,7 @@ describe('WebSocket', function () { ws.on('error', function () {}); try { ws.pong(); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -673,7 +643,7 @@ describe('WebSocket', function () { ws.on('open', function () { ws.pong(); }); - srv.on('pong', function (message) { + srv.on('pong', function () { srv.close(); ws.terminate(); done(); @@ -842,8 +812,7 @@ describe('WebSocket', function () { ws.on('error', function () {}); try { ws.send('hi'); - } - catch (e) { + } catch (e) { ws.terminate(); srv.close(); done(); @@ -1020,15 +989,13 @@ describe('WebSocket', function () { var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); - } - else { + } else { assert.ok(!flags.binary); assert.equal('baz', data); srv.close(); @@ -1048,18 +1015,17 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) send('foo'); + if (++i === 1) send('foo'); else send('bar', true); }); }); var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); srv.close(); @@ -1082,7 +1048,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1090,7 +1056,7 @@ describe('WebSocket', function () { }); srv.on('ping', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1111,7 +1077,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1119,7 +1085,7 @@ describe('WebSocket', function () { }); srv.on('pong', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1200,12 +1166,10 @@ describe('WebSocket', function () { it('without callback should fail', function (done) { server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; ws.on('open', function () { try { ws.stream(); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -1222,12 +1186,11 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.send('foobar'); ws.send('baz'); - } - else { + } else { send(payload.substr(5, 5), true); } }); @@ -1235,15 +1198,13 @@ describe('WebSocket', function () { var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.equal(payload, data); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); - } - else { + } else { assert.ok(!flags.binary); assert.equal('baz', data); srv.close(); @@ -1262,31 +1223,30 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); var i2 = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i2 == 1) send('foo'); + if (++i2 === 1) send('foo'); else send('bar', true); }); ws.send('baz'); + } else { + send(payload.substr(5, 5), true); } - else send(payload.substr(5, 5), true); }); }); var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.equal(payload, data); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); - } - else if (receivedIndex == 3) { + } else if (receivedIndex === 3) { assert.ok(!flags.binary); assert.equal('baz', data); setTimeout(function () { @@ -1294,8 +1254,9 @@ describe('WebSocket', function () { ws.terminate(); done(); }, 1000); + } else { + throw new Error('more messages than we actually sent just arrived'); } - else throw new Error('more messages than we actually sent just arrived'); }); }); }); @@ -1308,11 +1269,10 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.ping('foobar'); - } - else { + } else { send(payload.substr(5, 5), true); } }); @@ -1321,7 +1281,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1329,7 +1289,7 @@ describe('WebSocket', function () { }); srv.on('ping', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1346,11 +1306,10 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.pong('foobar'); - } - else { + } else { send(payload.substr(5, 5), true); } }); @@ -1359,7 +1318,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1367,7 +1326,7 @@ describe('WebSocket', function () { }); srv.on('pong', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1384,14 +1343,12 @@ describe('WebSocket', function () { ws.on('open', function () { var i = 0; ws.stream(function (error, send) { - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.close(1000, 'foobar'); - } - else if (i == 2) { + } else if (i === 2) { send(payload.substr(5, 5), true); - } - else if (i == 3) { + } else if (i === 3) { assert.ok(error); errorGiven = true; } @@ -1444,8 +1401,7 @@ describe('WebSocket', function () { ws.on('open', function () { try { ws.close('error'); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -1460,8 +1416,7 @@ describe('WebSocket', function () { ws.on('open', function () { try { ws.close(1004); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -1476,7 +1431,7 @@ describe('WebSocket', function () { ws.on('open', function () { ws.close(1000); }); - srv.on('close', function (code, message, flags) { + srv.on('close', function (code, message) { assert.equal('', message); srv.close(); ws.terminate(); @@ -1600,7 +1555,6 @@ describe('WebSocket', function () { it('should work the same as the EventEmitter api', function (done) { server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - var listener = function () {}; var message = 0; var close = 0; var open = 0; @@ -1796,139 +1750,113 @@ describe('WebSocket', function () { describe('ssl', function () { it('can connect to secure websocket server', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + const wss = new WebSocketServer({ server }); + + wss.on('connection', (ws) => { wss.close(); - done(); + server.close(done); }); + + server.listen(++port, () => new WebSocket('wss://localhost:' + port)); }); it('can connect to secure websocket server with client side certificate', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), + const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), ca: [fs.readFileSync('test/fixtures/ca1-cert.pem')], + key: fs.readFileSync('test/fixtures/key.pem'), requestCert: true - }; - var clientOptions = { - key: fs.readFileSync('test/fixtures/agent1-key.pem'), - cert: fs.readFileSync('test/fixtures/agent1-cert.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var success = false; - var wss = new WebSocketServer({ - server: app, - verifyClient: function (info) { + }); + + let success = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { success = !!info.req.client.authorized; return true; - } - }); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port, clientOptions); + }, + server }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + + wss.on('connection', (ws) => { + assert.ok(success); + server.close(done); wss.close(); - success.should.be.ok; - done(); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`wss://localhost:${port}`, { + cert: fs.readFileSync('test/fixtures/agent1-cert.pem'), + key: fs.readFileSync('test/fixtures/agent1-key.pem') + }); }); }); it('cannot connect to secure websocket server via ws://', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port, { rejectUnauthorized: false }); - ws.on('error', function () { - app.close(); - ws.terminate(); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') + }); + const wss = new WebSocketServer({ server }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + rejectUnauthorized: false + }); + + ws.on('error', () => { + server.close(done); wss.close(); - done(); }); }); }); it('can send and receive text data', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function () { - ws.send('foobar'); - }); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - message.should.eql('foobar'); - app.close(); - ws.terminate(); + const wss = new WebSocketServer({ server }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'foobar'); + server.close(done); wss.close(); - done(); }); }); + + server.listen(++port, () => { + const ws = new WebSocket(`wss://localhost:${port}`); + + ws.on('open', () => ws.send('foobar')); + }); }); it('can send and receive very long binary data', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - crypto.randomBytes(5 * 1024 * 1024, function (ex, buf) { - if (ex) throw ex; - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function () { - ws.send(buf, {binary: true}); - }); - ws.on('message', function (message, flags) { - flags.binary.should.be.ok; - areArraysEqual(buf, message).should.be.ok; - app.close(); - ws.terminate(); - wss.close(); - done(); - }); - }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {binary: true}); - }); + const buf = crypto.randomBytes(5 * 1024 * 1024); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') + }); + const wss = new WebSocketServer({ server }); + + wss.on('connection', (ws) => { + ws.on('message', (message) => ws.send(message)); + }); + + server.listen(++port, () => { + const ws = new WebSocket('wss://localhost:' + port); + + ws.on('open', () => ws.send(buf)); + ws.on('message', (message, flags) => { + assert.strictEqual(flags.binary, true); + assert.ok(buf.equals(message)); + server.close(done); + wss.close(); }); }); }); @@ -1938,11 +1866,11 @@ describe('WebSocket', function () { describe('#supports', function () { describe('#binary', function () { it('returns true', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { - assert.equal(true, client.supports.binary); + wss.on('connection', (client) => { + assert.strictEqual(client.supports.binary, true); wss.close(); done(); }); @@ -1953,43 +1881,50 @@ describe('WebSocket', function () { describe('host and origin headers', function () { it('includes the host header with port number', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - srv.on('upgrade', function (req, socket, upgradeHeade) { - assert.equal('localhost:' + port, req.headers['host']); - srv.close(); - done(); + const server = http.createServer(); + + server.listen(++port, () => { + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['host'], `localhost:${port}`); + server.close(done); + socket.destroy(); }); - var ws = new WebSocket('ws://localhost:' + port); + + const ws = new WebSocket(`ws://localhost:${port}`); }); }); it('lacks default origin header', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - srv.on('upgrade', function (req, socket, upgradeHeade) { - should(req.headers).not.have.property('origin'); - srv.close(); - done(); + const server = http.createServer(); + + server.listen(++port, () => { + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['origin'], undefined); + server.close(done); + socket.destroy(); }); - var ws = new WebSocket('ws://localhost:' + port); + + const ws = new WebSocket(`ws://localhost:${port}`); }); }); it('honors origin set in options', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var options = {origin: 'https://example.com:8000'}; - srv.on('upgrade', function (req, socket, upgradeHeade) { - assert.equal(options.origin, req.headers['origin']); - srv.close(); - done(); + const server = http.createServer(); + + server.listen(++port, function () { + const options = { origin: 'https://example.com:8000' }; + + server.on('upgrade', function (req, socket, head) { + assert.strictEqual(req.headers['origin'], options.origin); + server.close(done); + socket.destroy(); }); - var ws = new WebSocket('ws://localhost:' + port, options); + + const ws = new WebSocket(`ws://localhost:${port}`, options); }); }); - it('excludes default ports from host header', function (done) { + it('excludes default ports from host header', function () { // can't create a server listening on ports 80 or 443 // so we need to expose the method that does this var buildHostHeader = WebSocket.buildHostHeader; @@ -2001,13 +1936,12 @@ describe('WebSocket', function () { assert.equal('localhost', host); host = buildHostHeader(true, 'localhost', 8443); assert.equal('localhost:8443', host); - done(); }); }); describe('permessage-deflate', function () { it('is enabled by default', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port); @@ -2024,7 +1958,7 @@ describe('WebSocket', function () { }); it('can be disabled', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); @@ -2038,7 +1972,7 @@ describe('WebSocket', function () { }); it('can send extension parameters', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 347d4e110..35f4d9c5b 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -1,73 +1,37 @@ -var http = require('http'), - https = require('https'), - WebSocket = require('../'), - WebSocketServer = WebSocket.Server, - fs = require('fs'), - should = require('should'); - -var port = 8000; - -function getArrayBuffer (buf) { - var l = buf.length; - var arrayBuf = new ArrayBuffer(l); - for (var i = 0; i < l; ++i) { - arrayBuf[i] = buf[i]; - } - return arrayBuf; -} - -function areArraysEqual (x, y) { - if (x.length != y.length) return false; - for (var i = 0, l = x.length; i < l; ++i) { - if (x[i] !== y[i]) return false; - } - return true; -} +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$", "args": "none" }] */ + +'use strict'; + +const assert = require('assert'); +const WebSocket = require('../'); +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +require('should'); + +const WebSocketServer = WebSocket.Server; + +let port = 8000; describe('WebSocketServer', function () { describe('#ctor', function () { - it('should return a new instance if called without new', function (done) { - var ws = WebSocketServer({noServer: true}); - ws.should.be.an.instanceOf(WebSocketServer); - done(); - }); - it('throws an error if no option object is passed', function () { - var gotException = false; - try { - var wss = new WebSocketServer(); - } - catch (e) { - gotException = true; - } - gotException.should.be.ok; + assert.throws(() => new WebSocketServer()); }); it('throws an error if no port or server is specified', function () { - var gotException = false; - try { - var wss = new WebSocketServer({}); - } - catch (e) { - gotException = true; - } - gotException.should.be.ok; + assert.throws(() => new WebSocketServer({})); }); - it('does not throw an error if no port or server is specified, when the noServer option is true', function () { - var gotException = false; - try { - var wss = new WebSocketServer({noServer: true}); - } - catch (e) { - gotException = true; - } - gotException.should.eql(false); + it('should return a new instance if called without new', function () { + var wss = WebSocketServer({ noServer: true }); + + assert.ok(wss instanceof WebSocketServer); }); it('emits an error if http server bind fails', function (done) { - var wss1 = new WebSocketServer({port: 50003}); - var wss2 = new WebSocketServer({port: 50003}); + var wss1 = new WebSocketServer({ port: 50003 }); + var wss2 = new WebSocketServer({ port: 50003 }); wss2.on('error', function () { wss1.close(); done(); @@ -85,15 +49,15 @@ describe('WebSocketServer', function () { }); it('uses a precreated http server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws://localhost:${port}`); wss.on('connection', function (client) { wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); @@ -103,10 +67,10 @@ describe('WebSocketServer', function () { http.get('http://localhost:' + port, function (res) { var body = ''; - res.statusCode.should.equal(426); + assert.strictEqual(res.statusCode, 426); res.on('data', function (chunk) { body += chunk; }); res.on('end', function () { - body.should.equal(http.STATUS_CODES[426]); + assert.strictEqual(body, http.STATUS_CODES[426]); wss.close(); done(); }); @@ -117,87 +81,89 @@ describe('WebSocketServer', function () { // Don't test this on Windows. It throws errors for obvious reasons. if (!/^win/i.test(process.platform)) { it('uses a precreated http server listening on unix socket', function (done) { - var srv = http.createServer(); - var sockPath = '/tmp/ws_socket_' + new Date().getTime() + '.' + Math.floor(Math.random() * 1000); - srv.listen(sockPath, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws+unix://' + sockPath); + const server = http.createServer(); + const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; + + server.listen(sockPath, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws+unix://${sockPath}`); - wss.on('connection', function (client) { + wss.on('connection', (ws) => { wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); } it('emits path specific connection event', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port + '/endpointName'); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws://localhost:${port}/endpointName`); - wss.on('connection/endpointName', function (client) { + wss.on('connection/endpointName', (ws) => { wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); it('can have two different instances listening on the same http server with two different paths', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), - wss2 = new WebSocketServer({server: srv, path: '/wss2'}); - var doneCount = 0; - wss1.on('connection', function (client) { + const server = http.createServer(); + + server.listen(++port, () => { + const wss1 = new WebSocketServer({ server, path: '/wss1' }); + const wss2 = new WebSocketServer({ server, path: '/wss2' }); + let doneCount = 0; + + wss1.on('connection', (client) => { wss1.close(); - if (++doneCount == 2) { - srv.close(); - done(); + if (++doneCount === 2) { + server.close(done); } }); - wss2.on('connection', function (client) { + + wss2.on('connection', (client) => { wss2.close(); - if (++doneCount == 2) { - srv.close(); - done(); + if (++doneCount === 2) { + server.close(done); } }); - var ws1 = new WebSocket('ws://localhost:' + port + '/wss1'); - var ws2 = new WebSocket('ws://localhost:' + port + '/wss2?foo=1'); + + /* eslint-disable no-unused-vars */ + const ws1 = new WebSocket(`ws://localhost:${port}/wss1`); + const ws2 = new WebSocket(`ws://localhost:${port}/wss2?foo=1`); + /* eslint-enable no-unused-vars */ }); }); it('cannot have two different instances listening on the same http server with the same path', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}); - try { - var wss2 = new WebSocketServer({server: srv, path: '/wss1'}); - } - catch (e) { - wss1.close(); - srv.close(); - done(); - } - }); + const server = http.createServer(); + const wss1 = new WebSocketServer({ server: server, path: '/wss1' }); + + try { + // eslint-disable-next-line no-unused-vars + const wss2 = new WebSocketServer({ server: server, path: '/wss1' }); + } catch (e) { + wss1.close(); + done(); + } }); + it('will not crash when it receives an unhandled opcode', function (done) { - var wss = new WebSocketServer({ port: 8080 }); - wss.on('connection', function connection (ws) { - ws.onerror = function (error) { - done(); - }; + const wss = new WebSocketServer({ port: 8080 }); + + wss.on('connection', (ws) => { + ws.onerror = () => done(); }); - var socket = new WebSocket('ws://127.0.0.1:8080/'); + const ws = new WebSocket('ws://localhost:8080/'); - socket.onopen = function () { - socket._socket.write(new Buffer([5])); - socket.send(''); + ws.onopen = () => { + ws._socket.write(new Buffer([5])); + ws.send(''); }; }); }); @@ -217,61 +183,66 @@ describe('WebSocketServer', function () { var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); ws.on('close', function () { - if (++closes == 2) done(); + if (++closes === 2) done(); }); }); var closes = 0; wss.on('connection', function (client) { client.on('close', function () { - if (++closes == 2) done(); + if (++closes === 2) done(); }); wss.close(); }); }); it('does not close a precreated server', function (done) { - var srv = http.createServer(); - var realClose = srv.close; - srv.close = function () { - should.fail('must not close pre-created server'); + const server = http.createServer(); + const realClose = server.close; + + server.close = () => { + throw new Error('must not close pre-created server'); }; - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port); - wss.on('connection', function (client) { - wss.close(); - srv.close = realClose; - srv.close(); - done(); - }); + + const wss = new WebSocketServer({ server }); + + wss.on('connection', function (ws) { + wss.close(); + server.close = realClose; + server.close(done); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`); }); }); it('cleans event handlers on precreated server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ server }); wss.close(); - srv.emit('upgrade'); - srv.on('error', function () {}); - srv.emit('error'); - done(); + + assert.strictEqual(server.listeners('listening').length, 0); + assert.strictEqual(server.listeners('upgrade').length, 0); + assert.strictEqual(server.listeners('error').length, 0); + + server.close(done); }); }); it('cleans up websocket data on a precreated server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), - wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + const srv = http.createServer(); + srv.listen(++port, () => { + const wss1 = new WebSocketServer({server: srv, path: '/wss1'}); + const wss2 = new WebSocketServer({server: srv, path: '/wss2'}); (typeof srv._webSocketPaths).should.eql('object'); Object.keys(srv._webSocketPaths).length.should.eql(2); wss1.close(); Object.keys(srv._webSocketPaths).length.should.eql(1); wss2.close(); (typeof srv._webSocketPaths).should.eql('undefined'); - srv.close(); - done(); + srv.close(done); }); }); }); @@ -526,10 +497,11 @@ describe('WebSocketServer', function () { }); it('client can be denied', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { - return false; - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o) => false, + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -539,28 +511,28 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 8, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(401); - process.nextTick(function () { - wss.close(); - done(); - }); }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); + }); + + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('client can be accepted', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { - return true; - }}, function () { - var options = { + var wss = new WebSocketServer({ + port: ++port, + verifyClient: (o) => true + }, () => { + var req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -570,26 +542,28 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobar.com' } - }; - var req = http.request(options); + }); req.end(); }); + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function () {}); }); it('verifyClient gets client origin', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { - info.origin.should.eql('http://foobarbaz.com'); - verifyClientCalled = true; - return false; - }}, function () { - var options = { + var wss = new WebSocketServer({ + verifyClient: (info) => { + info.origin.should.eql('http://foobarbaz.com'); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + var req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -599,26 +573,27 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobarbaz.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { verifyClientCalled.should.be.ok; wss.close(); done(); }); + req.end(); }); - wss.on('error', function () {}); }); it('verifyClient gets original request', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { - info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); - verifyClientCalled = true; - return false; - }}, function () { - var options = { + var wss = new WebSocketServer({ + verifyClient: (info) => { + info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + var req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -628,16 +603,14 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobarbaz.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { verifyClientCalled.should.be.ok; wss.close(); done(); }); + req.end(); }); - wss.on('error', function () {}); }); it('verifyClient has secure:true for ssl connections', function (done) { @@ -695,12 +668,11 @@ describe('WebSocketServer', function () { }); it('client can be denied asynchronously', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { - process.nextTick(function () { - cb(false); - }); - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -710,30 +682,26 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 8, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { res.statusCode.should.eql(401); - process.nextTick(function () { - wss.close(); - done(); - }); + wss.close(); + done(); }); + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('client can be denied asynchronously with custom response code', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { - process.nextTick(function () { - cb(false, 404); - }); - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false, 404), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -743,30 +711,26 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 8, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { res.statusCode.should.eql(404); - process.nextTick(function () { - wss.close(); - done(); - }); + wss.close(); + done(); }); + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('client can be accepted asynchronously', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { - process.nextTick(function () { - cb(true); - }); - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, true), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -776,23 +740,19 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobar.com' } - }; - var req = http.request(options); + }); req.end(); }); - wss.on('connection', function (ws) { + wss.on('connection', (ws) => { ws.terminate(); wss.close(); done(); }); - wss.on('error', function () {}); }); it('handles messages passed along with the upgrade request (upgrade head)', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { - return true; - }}, function () { - var options = { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -802,20 +762,19 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.write(new Buffer([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f], 'binary')); + }); + req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); req.end(); }); - wss.on('connection', function (ws) { - ws.on('message', function (data) { + + wss.on('connection', (ws) => { + ws.on('message', (data) => { data.should.eql('Hello'); ws.terminate(); wss.close(); done(); }); }); - wss.on('error', function () {}); }); it('selects the first protocol by default', function (done) { @@ -830,61 +789,71 @@ describe('WebSocketServer', function () { }); it('selects the last protocol via protocol handler', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(true, ps[ps.length - 1]); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - ws.protocol.should.eql('prot2'); - wss.close(); - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => { + ws.protocol.should.eql('prot2'); + wss.close(); + done(); }); + }); }); it('client detects invalid server protocol', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(true, 'prot3'); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - done(new Error('connection must not be established')); - }); - ws.on('error', function () { - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, 'prot3'), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); + }); }); it('client detects no server protocol', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(true); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - done(new Error('connection must not be established')); - }); - ws.on('error', function () { - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); + }); }); it('client refuses server protocols', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(false); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - done(new Error('connection must not be established')); - }); - ws.on('error', function () { - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); + }); }); it('server detects unauthorized protocol handler', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(false); - }}, function () { - var options = { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -894,27 +863,24 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - options.port = port; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(401); + }); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); wss.close(); done(); }); + req.end(); }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); }); it('server detects invalid protocol handler', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => { // not calling callback is an error and shouldn't timeout - }}, function () { - var options = { + }, + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -924,20 +890,14 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - options.port = port; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(501); + }); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 501); wss.close(); done(); }); + req.end(); }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); }); it('accept connections with sec-websocket-extensions', function (done) { diff --git a/test/autobahn-server.js b/test/autobahn-server.js index 74aab5aa3..73655d8b3 100644 --- a/test/autobahn-server.js +++ b/test/autobahn-server.js @@ -1,4 +1,7 @@ -var WebSocketServer = require('../').Server; +'use strict'; + +var WebSocket = require('../'); +var WebSocketServer = WebSocket.Server; process.on('uncaughtException', function (err) { console.log('Caught exception: ', err, err.stack); @@ -11,8 +14,7 @@ process.on('SIGINT', function () { ws.on('close', function () { process.exit(); }); - } - catch (e) { + } catch (e) { process.exit(); } }); diff --git a/test/autobahn.js b/test/autobahn.js index 2ea7f1c78..dbe3fefd5 100644 --- a/test/autobahn.js +++ b/test/autobahn.js @@ -1,3 +1,5 @@ +'use strict'; + var WebSocket = require('../'); var currentTest = 1; var lastTest = -1; @@ -14,23 +16,25 @@ process.on('SIGINT', function () { ws.on('close', function () { process.exit(); }); - } - catch (e) { + } catch (e) { process.exit(); } }); function nextTest () { - if (currentTest > testCount || (lastTest != -1 && currentTest > lastTest)) { + var ws; + + if (currentTest > testCount || (lastTest !== -1 && currentTest > lastTest)) { console.log('Updating reports and shutting down'); - var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); + ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); ws.on('close', function () { process.exit(); }); return; } + console.log('Running test case ' + currentTest + '/' + testCount); - var ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); + ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); ws.on('message', function (data, flags) { ws.send(flags.buffer, {binary: flags.binary === true, mask: true}); }); diff --git a/test/testserver.js b/test/testserver.js index 356509fd7..3f24964c4 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,9 +1,11 @@ -var http = require('http'), - util = require('util'), - crypto = require('crypto'), - events = require('events'), - Sender = require('../lib/Sender'), - Receiver = require('../lib/Receiver'); +'use strict'; + +const http = require('http'); +const util = require('util'); +const crypto = require('crypto'); +const events = require('events'); +const Sender = require('../lib/Sender'); +const Receiver = require('../lib/Receiver'); module.exports = { handlers: { @@ -38,7 +40,6 @@ function validServer (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); - return; } if (!req.headers['sec-websocket-key']) { @@ -85,7 +86,7 @@ function validServer (server, req, socket) { }; receiver.onclose = function (code, message, flags) { flags = flags || {}; - sender.close(code, message, false, function (err) { + sender.close(code, message, false, function () { server.emit('close', code, message, flags); socket.end(); }); @@ -102,7 +103,6 @@ function invalidRequestHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); - return; } if (!req.headers['sec-websocket-key']) { @@ -131,7 +131,6 @@ function closeAfterConnectHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); - return; } if (!req.headers['sec-websocket-key']) { From 7214fc0b7273bb97a36d6d2738cdd6e1c4e39b8f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 18:09:55 +0200 Subject: [PATCH 095/489] [lint] Run eslint --fix on examples/* --- examples/fileapi/public/app.js | 16 +++--- examples/fileapi/public/uploader.js | 16 +++--- examples/fileapi/server.js | 50 ++++++++-------- examples/serverstats-express_3/server.js | 16 +++--- examples/serverstats/server.js | 16 +++--- examples/ssl.js | 72 ++++++++++-------------- 6 files changed, 87 insertions(+), 99 deletions(-) diff --git a/examples/fileapi/public/app.js b/examples/fileapi/public/app.js index e812cc3ea..db5f5c6d6 100644 --- a/examples/fileapi/public/app.js +++ b/examples/fileapi/public/app.js @@ -1,4 +1,4 @@ -function onFilesSelected(e) { +function onFilesSelected (e) { var button = e.srcElement; button.disabled = true; var progress = document.querySelector('div#progress'); @@ -8,12 +8,12 @@ function onFilesSelected(e) { var filesSent = 0; if (totalFiles) { var uploader = new Uploader('ws://localhost:8080', function () { - Array.prototype.slice.call(files, 0).forEach(function(file) { + Array.prototype.slice.call(files, 0).forEach(function (file) { if (file.name == '.') { --totalFiles; return; } - uploader.sendFile(file, function(error) { + uploader.sendFile(file, function (error) { if (error) { console.log(error); return; @@ -25,15 +25,15 @@ function onFilesSelected(e) { }); }); } - uploader.ondone = function() { + uploader.ondone = function () { uploader.close(); progress.innerHTML = '100% done, ' + totalFiles + ' files sent.'; - } + }; } -window.onload = function() { +window.onload = function () { var importButtons = document.querySelectorAll('[type="file"]'); - Array.prototype.slice.call(importButtons, 0).forEach(function(importButton) { + Array.prototype.slice.call(importButtons, 0).forEach(function (importButton) { importButton.addEventListener('change', onFilesSelected, false); }); -} +}; diff --git a/examples/fileapi/public/uploader.js b/examples/fileapi/public/uploader.js index 0c34a7fae..09d9850e1 100644 --- a/examples/fileapi/public/uploader.js +++ b/examples/fileapi/public/uploader.js @@ -1,4 +1,4 @@ -function Uploader(url, cb) { +function Uploader (url, cb) { this.ws = new WebSocket(url); if (cb) this.ws.onopen = cb; this.sendQueue = []; @@ -6,7 +6,7 @@ function Uploader(url, cb) { this.sendCallback = null; this.ondone = null; var self = this; - this.ws.onmessage = function(event) { + this.ws.onmessage = function (event) { var data = JSON.parse(event.data); if (data.event == 'complete') { if (data.path != self.sending.path) { @@ -22,7 +22,7 @@ function Uploader(url, cb) { if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); if (self.sendQueue.length > 0) { var args = self.sendQueue.pop(); - setTimeout(function() { self.sendFile.apply(self, args); }, 0); + setTimeout(function () { self.sendFile.apply(self, args); }, 0); } } else if (data.event == 'error') { @@ -34,10 +34,10 @@ function Uploader(url, cb) { if (callback) callback(error); if (self.ondone) self.ondone(error); } - } + }; } -Uploader.prototype.sendFile = function(file, cb) { +Uploader.prototype.sendFile = function (file, cb) { if (this.ws.readyState != WebSocket.OPEN) throw new Error('Not connected'); if (this.sending) { this.sendQueue.push(arguments); @@ -48,8 +48,8 @@ Uploader.prototype.sendFile = function(file, cb) { this.sendCallback = cb; this.ws.send(JSON.stringify(fileData)); this.ws.send(file); -} +}; -Uploader.prototype.close = function() { +Uploader.prototype.close = function () { this.ws.close(); -} +}; diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index badfeba7a..4fcf789bc 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -1,40 +1,40 @@ -var WebSocketServer = require('../../').Server - , express = require('express') - , fs = require('fs') - , http = require('http') - , util = require('util') - , path = require('path') - , app = express.createServer() - , events = require('events') - , ansi = require('ansi') - , cursor = ansi(process.stdout); +var WebSocketServer = require('../../').Server, + express = require('express'), + fs = require('fs'), + http = require('http'), + util = require('util'), + path = require('path'), + app = express.createServer(), + events = require('events'), + ansi = require('ansi'), + cursor = ansi(process.stdout); -function BandwidthSampler(ws, interval) { +function BandwidthSampler (ws, interval) { interval = interval || 2000; var previousByteCount = 0; var self = this; - var intervalId = setInterval(function() { + var intervalId = setInterval(function () { var byteCount = ws.bytesReceived; var bytesPerSec = (byteCount - previousByteCount) / (interval / 1000); previousByteCount = byteCount; self.emit('sample', bytesPerSec); }, interval); - ws.on('close', function() { + ws.on('close', function () { clearInterval(intervalId); }); } util.inherits(BandwidthSampler, events.EventEmitter); -function makePathForFile(filePath, prefix, cb) { +function makePathForFile (filePath, prefix, cb) { if (typeof cb !== 'function') throw new Error('callback is required'); filePath = path.dirname(path.normalize(filePath)).replace(/^(\/|\\)+/, ''); var pieces = filePath.split(/(\\|\/)/); var incrementalPath = prefix; - function step(error) { + function step (error) { if (error) return cb(error); if (pieces.length == 0) return cb(null, incrementalPath); incrementalPath += '/' + pieces.shift(); - fs.exists(incrementalPath, function(exists) { + fs.exists(incrementalPath, function (exists) { if (!exists) fs.mkdir(incrementalPath, step); else process.nextTick(step); }); @@ -47,33 +47,33 @@ app.use(express.static(__dirname + '/public')); var clientId = 0; var wss = new WebSocketServer({server: app}); -wss.on('connection', function(ws) { +wss.on('connection', function (ws) { var thisId = ++clientId; cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d connected', thisId); var sampler = new BandwidthSampler(ws); - sampler.on('sample', function(bps) { + sampler.on('sample', function (bps) { cursor.goto(1, 4 + thisId).eraseLine(); - console.log('WebSocket #%d incoming bandwidth: %d MB/s', thisId, Math.round(bps / (1024*1024))); + console.log('WebSocket #%d incoming bandwidth: %d MB/s', thisId, Math.round(bps / (1024 * 1024))); }); var filesReceived = 0; var currentFile = null; - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { if (!flags.binary) { currentFile = JSON.parse(data); // note: a real-world app would want to sanity check the data } else { if (currentFile == null) return; - makePathForFile(currentFile.path, __dirname + '/uploaded', function(error, path) { + makePathForFile(currentFile.path, __dirname + '/uploaded', function (error, path) { if (error) { console.log(error); ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); return; } - fs.writeFile(path + '/' + currentFile.name, data, function(error) { + fs.writeFile(path + '/' + currentFile.name, data, function (error) { ++filesReceived; // console.log('received %d bytes long file, %s', data.length, currentFile.path); ws.send(JSON.stringify({event: 'complete', path: currentFile.path})); @@ -83,18 +83,18 @@ wss.on('connection', function(ws) { } }); - ws.on('close', function() { + ws.on('close', function () { cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d disconnected. %d files received.', thisId, filesReceived); }); - ws.on('error', function(e) { + ws.on('error', function (e) { cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d error: %s', thisId, e.message); }); }); -fs.mkdir(__dirname + '/uploaded', function(error) { +fs.mkdir(__dirname + '/uploaded', function (error) { // ignore errors, most likely means directory exists console.log('Uploaded files will be saved to %s/uploaded.', __dirname); console.log('Remember to wipe this directory if you upload lots and lots.'); diff --git a/examples/serverstats-express_3/server.js b/examples/serverstats-express_3/server.js index 88bbc9ebf..c69db84fc 100644 --- a/examples/serverstats-express_3/server.js +++ b/examples/serverstats-express_3/server.js @@ -1,7 +1,7 @@ -var WebSocketServer = require('../../').Server - , http = require('http') - , express = require('express') - , app = express(); +var WebSocketServer = require('../../').Server, + http = require('http'), + express = require('express'), + app = express(); app.use(express.static(__dirname + '/public')); @@ -9,12 +9,12 @@ var server = http.createServer(app); server.listen(8080); var wss = new WebSocketServer({server: server}); -wss.on('connection', function(ws) { - var id = setInterval(function() { - ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ }); +wss.on('connection', function (ws) { + var id = setInterval(function () { + ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); }, 100); console.log('started client interval'); - ws.on('close', function() { + ws.on('close', function () { console.log('stopping client interval'); clearInterval(id); }); diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index d7845e0cb..9b7819e4c 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -1,18 +1,18 @@ -var WebSocketServer = require('../../').Server - , http = require('http') - , express = require('express') - , app = express.createServer(); +var WebSocketServer = require('../../').Server, + http = require('http'), + express = require('express'), + app = express.createServer(); app.use(express.static(__dirname + '/public')); app.listen(8080); var wss = new WebSocketServer({server: app}); -wss.on('connection', function(ws) { - var id = setInterval(function() { - ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ }); +wss.on('connection', function (ws) { + var id = setInterval(function () { + ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); }, 100); console.log('started client interval'); - ws.on('close', function() { + ws.on('close', function () { console.log('stopping client interval'); clearInterval(id); }); diff --git a/examples/ssl.js b/examples/ssl.js index bf1bf5303..f1f1dee44 100644 --- a/examples/ssl.js +++ b/examples/ssl.js @@ -1,59 +1,47 @@ -(function(){ +(function () { + 'use strict'; - "use strict"; - - var fs = require('fs'); + var fs = require('fs'); // you'll probably load configuration from config - var cfg = { - ssl: true, - port: 8080, - ssl_key: '/path/to/you/ssl.key', - ssl_cert: '/path/to/you/ssl.crt' - }; + var cfg = { + ssl: true, + port: 8080, + ssl_key: '/path/to/you/ssl.key', + ssl_cert: '/path/to/you/ssl.crt' + }; - var httpServ = ( cfg.ssl ) ? require('https') : require('http'); + var httpServ = (cfg.ssl) ? require('https') : require('http'); - var WebSocketServer = require('../').Server; + var WebSocketServer = require('../').Server; - var app = null; + var app = null; // dummy request processing - var processRequest = function( req, res ) { - - res.writeHead(200); - res.end("All glory to WebSockets!\n"); - }; - - if ( cfg.ssl ) { + var processRequest = function (req, res) { + res.writeHead(200); + res.end('All glory to WebSockets!\n'); + }; - app = httpServ.createServer({ + if (cfg.ssl) { + app = httpServ.createServer({ // providing server with SSL key/cert - key: fs.readFileSync( cfg.ssl_key ), - cert: fs.readFileSync( cfg.ssl_cert ) + key: fs.readFileSync(cfg.ssl_key), + cert: fs.readFileSync(cfg.ssl_cert) - }, processRequest ).listen( cfg.port ); - - } else { - - app = httpServ.createServer( processRequest ).listen( cfg.port ); - } + }, processRequest).listen(cfg.port); + } else { + app = httpServ.createServer(processRequest).listen(cfg.port); + } // passing or reference to web server so WS would knew port and SSL capabilities - var wss = new WebSocketServer( { server: app } ); - - - wss.on( 'connection', function ( wsConnect ) { - - wsConnect.on( 'message', function ( message ) { - - console.log( message ); - - }); + var wss = new WebSocketServer({ server: app }); + wss.on('connection', function (wsConnect) { + wsConnect.on('message', function (message) { + console.log(message); }); - - -}()); \ No newline at end of file + }); +}()); From bf8ab467fe391888a2ebed9b44cac66d94b2f4fa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 18:33:39 +0200 Subject: [PATCH 096/489] [list] Fix remaining lint issues on examples/* --- Makefile | 2 +- examples/fileapi/public/app.js | 3 +- examples/fileapi/public/uploader.js | 15 +++++----- examples/fileapi/server.js | 35 +++++++++++++----------- examples/serverstats-express_3/server.js | 11 ++++---- examples/serverstats/server.js | 10 +++---- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 920bc1ff1..2ae3d0fbe 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ ALL_TESTS = $(shell find test -name '*.test.js') ALL_INTEGRATION = $(shell find test -name '*.integration.js') lint: - @./node_modules/.bin/eslint lib index.js + @./node_modules/.bin/eslint . run-tests: @./node_modules/.bin/mocha \ diff --git a/examples/fileapi/public/app.js b/examples/fileapi/public/app.js index db5f5c6d6..f1a9ee32c 100644 --- a/examples/fileapi/public/app.js +++ b/examples/fileapi/public/app.js @@ -1,3 +1,4 @@ +/* global Uploader */ function onFilesSelected (e) { var button = e.srcElement; button.disabled = true; @@ -9,7 +10,7 @@ function onFilesSelected (e) { if (totalFiles) { var uploader = new Uploader('ws://localhost:8080', function () { Array.prototype.slice.call(files, 0).forEach(function (file) { - if (file.name == '.') { + if (file.name === '.') { --totalFiles; return; } diff --git a/examples/fileapi/public/uploader.js b/examples/fileapi/public/uploader.js index 09d9850e1..9b4f98e2f 100644 --- a/examples/fileapi/public/uploader.js +++ b/examples/fileapi/public/uploader.js @@ -1,3 +1,4 @@ +/* global WebSocket */ function Uploader (url, cb) { this.ws = new WebSocket(url); if (cb) this.ws.onopen = cb; @@ -8,15 +9,16 @@ function Uploader (url, cb) { var self = this; this.ws.onmessage = function (event) { var data = JSON.parse(event.data); - if (data.event == 'complete') { - if (data.path != self.sending.path) { + var callback; + if (data.event === 'complete') { + if (data.path !== self.sending.path) { self.sendQueue = []; self.sending = null; self.sendCallback = null; throw new Error('Got message for wrong file!'); } self.sending = null; - var callback = self.sendCallback; + callback = self.sendCallback; self.sendCallback = null; if (callback) callback(); if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); @@ -24,11 +26,10 @@ function Uploader (url, cb) { var args = self.sendQueue.pop(); setTimeout(function () { self.sendFile.apply(self, args); }, 0); } - } - else if (data.event == 'error') { + } else if (data.event === 'error') { self.sendQueue = []; self.sending = null; - var callback = self.sendCallback; + callback = self.sendCallback; self.sendCallback = null; var error = new Error('Server reported send error for file ' + data.path); if (callback) callback(error); @@ -38,7 +39,7 @@ function Uploader (url, cb) { } Uploader.prototype.sendFile = function (file, cb) { - if (this.ws.readyState != WebSocket.OPEN) throw new Error('Not connected'); + if (this.ws.readyState !== WebSocket.OPEN) throw new Error('Not connected'); if (this.sending) { this.sendQueue.push(arguments); return; diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index 4fcf789bc..e7d80f009 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -1,13 +1,12 @@ -var WebSocketServer = require('../../').Server, - express = require('express'), - fs = require('fs'), - http = require('http'), - util = require('util'), - path = require('path'), - app = express.createServer(), - events = require('events'), - ansi = require('ansi'), - cursor = ansi(process.stdout); +var WebSocketServer = require('../../').Server; +var express = require('express'); +var fs = require('fs'); +var util = require('util'); +var path = require('path'); +var app = express.createServer(); +var events = require('events'); +var ansi = require('ansi'); +var cursor = ansi(process.stdout); function BandwidthSampler (ws, interval) { interval = interval || 2000; @@ -32,7 +31,7 @@ function makePathForFile (filePath, prefix, cb) { var incrementalPath = prefix; function step (error) { if (error) return cb(error); - if (pieces.length == 0) return cb(null, incrementalPath); + if (pieces.length === 0) return cb(null, incrementalPath); incrementalPath += '/' + pieces.shift(); fs.exists(incrementalPath, function (exists) { if (!exists) fs.mkdir(incrementalPath, step); @@ -43,7 +42,7 @@ function makePathForFile (filePath, prefix, cb) { } cursor.eraseData(2).goto(1, 1); -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, '/public'))); var clientId = 0; var wss = new WebSocketServer({server: app}); @@ -64,16 +63,20 @@ wss.on('connection', function (ws) { if (!flags.binary) { currentFile = JSON.parse(data); // note: a real-world app would want to sanity check the data - } - else { + } else { if (currentFile == null) return; - makePathForFile(currentFile.path, __dirname + '/uploaded', function (error, path) { + makePathForFile(currentFile.path, path.join(__dirname, '/uploaded'), function (error, path) { if (error) { console.log(error); ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); return; } fs.writeFile(path + '/' + currentFile.name, data, function (error) { + if (error) { + console.log(error); + ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); + return; + } ++filesReceived; // console.log('received %d bytes long file, %s', data.length, currentFile.path); ws.send(JSON.stringify({event: 'complete', path: currentFile.path})); @@ -94,7 +97,7 @@ wss.on('connection', function (ws) { }); }); -fs.mkdir(__dirname + '/uploaded', function (error) { +fs.mkdir(path.join(__dirname, '/uploaded'), function () { // ignore errors, most likely means directory exists console.log('Uploaded files will be saved to %s/uploaded.', __dirname); console.log('Remember to wipe this directory if you upload lots and lots.'); diff --git a/examples/serverstats-express_3/server.js b/examples/serverstats-express_3/server.js index c69db84fc..16e584f95 100644 --- a/examples/serverstats-express_3/server.js +++ b/examples/serverstats-express_3/server.js @@ -1,9 +1,10 @@ -var WebSocketServer = require('../../').Server, - http = require('http'), - express = require('express'), - app = express(); +var WebSocketServer = require('../../').Server; +var http = require('http'); +var express = require('express'); +var path = require('path'); +var app = express(); -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, '/public'))); var server = http.createServer(app); server.listen(8080); diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index 9b7819e4c..73e4fcf8f 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -1,9 +1,9 @@ -var WebSocketServer = require('../../').Server, - http = require('http'), - express = require('express'), - app = express.createServer(); +var WebSocketServer = require('../../').Server; +var express = require('express'); +var path = require('path'); +var app = express.createServer(); -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, '/public'))); app.listen(8080); var wss = new WebSocketServer({server: app}); From 47f8b9b8eb992519a23fe50ead62d86e01973f6e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 27 Oct 2016 12:40:12 +0200 Subject: [PATCH 097/489] [test] Clean up tests and remove should dependency --- package.json | 1 - test/Extensions.test.js | 42 +- test/PerMessageDeflate.test.js | 271 ++++--- test/Receiver.test.js | 8 +- test/Sender.test.js | 106 +-- test/Validation.test.js | 49 +- test/WebSocket.integration.js | 25 +- test/WebSocket.test.js | 442 ++++++------ test/WebSocketServer.test.js | 1239 ++++++++++++++++---------------- test/hybi-util.js | 4 +- test/testserver.js | 35 +- 11 files changed, 1166 insertions(+), 1056 deletions(-) diff --git a/package.json b/package.json index 1b0a893a1..8d33af457 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", "mocha": "3.1.x", - "should": "8.0.x", "utf-8-validate": "1.2.x" } } diff --git a/test/Extensions.test.js b/test/Extensions.test.js index ce0af8381..8b8fe2fe9 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -1,31 +1,38 @@ -var Extensions = require('../lib/Extensions'); -require('should'); +'use strict'; + +const assert = require('assert'); + +const Extensions = require('../lib/Extensions'); describe('Extensions', function () { describe('parse', function () { it('should parse', function () { - var extensions = Extensions.parse('foo'); - extensions.should.eql({ foo: [{}] }); + const extensions = Extensions.parse('foo'); + + assert.deepStrictEqual(extensions, { foo: [{}] }); }); it('should parse params', function () { - var extensions = Extensions.parse('foo; bar; baz=1; bar=2'); - extensions.should.eql({ + const extensions = Extensions.parse('foo; bar; baz=1; bar=2'); + + assert.deepStrictEqual(extensions, { foo: [{ bar: [true, '2'], baz: ['1'] }] }); }); it('should parse multiple extensions', function () { - var extensions = Extensions.parse('foo, bar; baz, foo; baz'); - extensions.should.eql({ + const extensions = Extensions.parse('foo, bar; baz, foo; baz'); + + assert.deepStrictEqual(extensions, { foo: [{}, { baz: [true] }], bar: [{ baz: [true] }] }); }); it('should parse quoted params', function () { - var extensions = Extensions.parse('foo; bar="hi"'); - extensions.should.eql({ + const extensions = Extensions.parse('foo; bar="hi"'); + + assert.deepStrictEqual(extensions, { foo: [{ bar: ['hi'] }] }); }); @@ -33,21 +40,24 @@ describe('Extensions', function () { describe('format', function () { it('should format', function () { - var extensions = Extensions.format({ foo: {} }); - extensions.should.eql('foo'); + const extensions = Extensions.format({ foo: {} }); + + assert.strictEqual(extensions, 'foo'); }); it('should format params', function () { - var extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); - extensions.should.eql('foo; bar; bar=2; baz=1'); + const extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); + + assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); it('should format multiple extensions', function () { - var extensions = Extensions.format({ + const extensions = Extensions.format({ foo: [{}, { baz: true }], bar: { baz: true } }); - extensions.should.eql('foo, foo; baz, bar; baz'); + + assert.strictEqual(extensions, 'foo, foo; baz, bar; baz'); }); }); }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 73e324c16..fe570f4b2 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -1,35 +1,36 @@ 'use strict'; +const assert = require('assert'); + const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Extensions = require('../lib/Extensions'); -require('should'); describe('PerMessageDeflate', function () { describe('#ctor', function () { - it('throws TypeError when called without new', function (done) { - try { - PerMessageDeflate(); - } catch (e) { - e.should.be.instanceof(TypeError); - done(); - } + it('throws TypeError when called without new', function () { + assert.throws(PerMessageDeflate, TypeError); }); }); describe('#offer', function () { it('should create default params', function () { - var perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.offer().should.eql({ client_max_window_bits: true }); + const perMessageDeflate = new PerMessageDeflate(); + + assert.deepStrictEqual( + perMessageDeflate.offer(), + { client_max_window_bits: true } + ); }); it('should create params from options', function () { - var perMessageDeflate = new PerMessageDeflate({ + const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, serverMaxWindowBits: 10, clientMaxWindowBits: 11 }); - perMessageDeflate.offer().should.eql({ + + assert.deepStrictEqual(perMessageDeflate.offer(), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, @@ -41,14 +42,20 @@ describe('PerMessageDeflate', function () { describe('#accept', function () { describe('as server', function () { it('should accept empty offer', function () { - var perMessageDeflate = new PerMessageDeflate({}, true); - perMessageDeflate.accept([{}]).should.eql({}); + const perMessageDeflate = new PerMessageDeflate({}, true); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); it('should accept offer', function () { - var perMessageDeflate = new PerMessageDeflate({}, true); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const perMessageDeflate = new PerMessageDeflate({}, true); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, @@ -57,14 +64,17 @@ describe('PerMessageDeflate', function () { }); it('should prefer configuration than offer', function () { - var perMessageDeflate = new PerMessageDeflate({ + const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, serverMaxWindowBits: 12, clientMaxWindowBits: 11 }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=14; client_max_window_bits=13'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const extensions = Extensions.parse( + 'permessage-deflate; server_max_window_bits=14; client_max_window_bits=13' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 12, @@ -73,56 +83,61 @@ describe('PerMessageDeflate', function () { }); it('should fallback', function () { - var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10, permessage-deflate'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); + const extensions = Extensions.parse( + 'permessage-deflate; server_max_window_bits=10, permessage-deflate' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_max_window_bits: 11 }); }); it('should throw an error if server_no_context_takeover is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); + const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); + const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is less than configuration', function () { - var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); + const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported on client', function () { - var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); - var extensions = Extensions.parse('permessage-deflate'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); + const extensions = Extensions.parse('permessage-deflate'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); }); describe('as client', function () { it('should accept empty response', function () { - var perMessageDeflate = new PerMessageDeflate({}); - perMessageDeflate.accept([{}]).should.eql({}); + const perMessageDeflate = new PerMessageDeflate({}); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); it('should accept response parameter', function () { - var perMessageDeflate = new PerMessageDeflate({}); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const perMessageDeflate = new PerMessageDeflate({}); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, @@ -131,109 +146,110 @@ describe('PerMessageDeflate', function () { }); it('should throw an error if client_no_context_takeover is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); - var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); + const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); - var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); + const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is greater than configuration', function () { - var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); - var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); + const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); }); describe('validate parameters', function () { it('should throw an error if a parameter has multiple values', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; server_no_context_takeover'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' + ); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if a parameter is undefined', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; foo;'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; foo;'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_no_context_takeover has a value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_no_context_takeover has a value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits has an invalid value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits has an invalid value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); }); }); describe('#compress/#decompress', function () { it('should compress/decompress data', function (done) { - var perMessageDeflate = new PerMessageDeflate(); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + perMessageDeflate.accept([{}]); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { + perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function (err, data) { + + perMessageDeflate.decompress(compressed, true, (err, data) => { if (err) return done(err); - data.should.eql(new Buffer([1, 2, 3])); + + assert.ok(data.equals(Buffer.from([1, 2, 3]))); done(); }); }); }); it('should compress/decompress fragments', function (done) { - var perMessageDeflate = new PerMessageDeflate(); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const buf = Buffer.from([1, 2, 3, 4]); + perMessageDeflate.accept([{}]); - var buf = new Buffer([1, 2, 3, 4]); - perMessageDeflate.compress(buf.slice(0, 2), false, function (err, compressed1) { + perMessageDeflate.compress(buf.slice(0, 2), false, (err, compressed1) => { if (err) return done(err); - perMessageDeflate.compress(buf.slice(2), true, function (err, compressed2) { + + perMessageDeflate.compress(buf.slice(2), true, (err, compressed2) => { if (err) return done(err); - perMessageDeflate.decompress(compressed1, false, function (err, data1) { + + perMessageDeflate.decompress(compressed1, false, (err, data1) => { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function (err, data2) { + + perMessageDeflate.decompress(compressed2, true, (err, data2) => { if (err) return done(err); - Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); + + assert.ok(Buffer.concat([data1, data2]).equals(Buffer.from([1, 2, 3, 4]))); done(); }); }); @@ -242,34 +258,53 @@ describe('PerMessageDeflate', function () { }); it('should compress/decompress data with parameters', function (done) { - var perMessageDeflate = new PerMessageDeflate({ memLevel: 5 }); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); + const perMessageDeflate = new PerMessageDeflate({ + threshold: 0, + memLevel: 5 + }); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { + + perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function (err, data) { + + perMessageDeflate.decompress(compressed, true, (err, data) => { if (err) return done(err); - data.should.eql(new Buffer([1, 2, 3])); + + assert.ok(data.equals(Buffer.from([1, 2, 3]))); done(); }); }); }); it('should compress/decompress data with no context takeover', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover'); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; client_no_context_takeover' + ); + const buf = Buffer.from('foofoo'); + perMessageDeflate.accept(extensions['permessage-deflate']); - var buf = new Buffer('foofoo'); - perMessageDeflate.compress(buf, true, function (err, compressed1) { + + perMessageDeflate.compress(buf, true, (err, compressed1) => { if (err) return done(err); - perMessageDeflate.decompress(compressed1, true, function (err, data) { + + perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); - perMessageDeflate.compress(data, true, function (err, compressed2) { + + perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function (err, data) { + + perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); - compressed2.length.should.equal(compressed1.length); - data.should.eql(buf); + + assert.strictEqual(compressed2.length, compressed1.length); + assert.ok(data.equals(buf)); done(); }); }); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index f9f017d2b..0f7b1b077 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -165,10 +165,10 @@ describe('Receiver', function () { it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); - var pingMessage = 'Hello'; + const pingMessage = 'Hello'; - var fragment1 = msg.substr(0, 150); - var fragment2 = msg.substr(150); + const fragment1 = msg.substr(0, 150); + const fragment2 = msg.substr(150); const mask = '3483a868'; const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + @@ -196,7 +196,7 @@ describe('Receiver', function () { assert.strictEqual(data.toString(), pingMessage); }; - for (var i = 0; i < buffers.length; ++i) { + for (let i = 0; i < buffers.length; ++i) { p.add(buffers[i]); } }); diff --git a/test/Sender.test.js b/test/Sender.test.js index f81b96d64..acb16918a 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -1,96 +1,97 @@ 'use strict'; -const Sender = require('../lib/Sender'); +const assert = require('assert'); + const PerMessageDeflate = require('../lib/PerMessageDeflate'); -require('should'); +const Sender = require('../lib/Sender'); describe('Sender', function () { describe('#ctor', function () { - it('throws TypeError when called without new', function (done) { - try { - Sender({ write: function () {} }); - } catch (e) { - e.should.be.instanceof(TypeError); - done(); - } + it('throws TypeError when called without new', function () { + assert.throws(Sender, TypeError); }); }); describe('#frameAndSend', function () { it('does not modify a masked binary buffer', function () { - var sender = new Sender({ write: function () {} }); - var buf = new Buffer([1, 2, 3, 4, 5]); + const sender = new Sender({ write: () => {} }); + const buf = Buffer.from([1, 2, 3, 4, 5]); + sender.frameAndSend(2, buf, true, true); - buf[0].should.eql(1); - buf[1].should.eql(2); - buf[2].should.eql(3); - buf[3].should.eql(4); - buf[4].should.eql(5); + + assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); it('does not modify a masked text buffer', function () { - var sender = new Sender({ write: function () {} }); - var text = 'hi there'; - sender.frameAndSend(1, Buffer.from(text), true, true); - text.should.eql('hi there'); + const sender = new Sender({ write: () => {} }); + const text = Buffer.from('hi there'); + + sender.frameAndSend(1, text, true, true); + + assert.ok(text.equals(Buffer.from('hi there'))); }); it('sets rsv1 flag if compressed', function (done) { - var sender = new Sender({ - write: function (data) { - (data[0] & 0x40).should.equal(0x40); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data[0] & 0x40, 0x40); done(); } }); + sender.frameAndSend(1, Buffer.from('hi'), true, false, true); }); }); describe('#send', function () { it('compresses data if compress option is enabled', function (done) { - var perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - perMessageDeflate.accept([{}]); - - var sender = new Sender({ - write: function (data) { - (data[0] & 0x40).should.equal(0x40); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data[0] & 0x40, 0x40); done(); } }, { 'permessage-deflate': perMessageDeflate }); + + perMessageDeflate.accept([{}]); + sender.send('hi', { compress: true }); }); it('does not compress data for small payloads', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - var sender = new Sender({ - write: function (data) { - (data[0] & 0x40).should.not.equal(0x40); + const perMessageDeflate = new PerMessageDeflate(); + const sender = new Sender({ + write: (data) => { + assert.notStrictEqual(data[0] & 0x40, 0x40); done(); } }, { 'permessage-deflate': perMessageDeflate }); + + perMessageDeflate.accept([{}]); + sender.send('hi', { compress: true }); }); it('Should be able to handle many send calls while processing without crashing on flush', function (done) { - var messageCount = 0; - var maxMessages = 5000; + const maxMessages = 5000; + let messageCount = 0; - var sender = new Sender({ - write: function (data) { + const sender = new Sender({ + write: (data) => { messageCount++; if (messageCount > maxMessages) return done(); } }); - for (var i = 0; i < maxMessages; i++) { + + for (let i = 0; i < maxMessages; i++) { sender.processing = true; sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); } + sender.processing = false; sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); }); @@ -98,22 +99,23 @@ describe('Sender', function () { describe('#close', function () { it('should consume all data before closing', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - var count = 0; - var sender = new Sender({ - write: function (data) { - count++; - } + let count = 0; + const sender = new Sender({ + write: (data) => count++ }, { 'permessage-deflate': perMessageDeflate }); - sender.send('foo', {compress: true}); - sender.send('bar', {compress: true}); - sender.send('baz', {compress: true}); - sender.close(1000, null, false, function (err) { - count.should.be.equal(4); + + perMessageDeflate.accept([{}]); + + sender.send('foo', { compress: true }); + sender.send('bar', { compress: true }); + sender.send('baz', { compress: true }); + + sender.close(1000, null, false, (err) => { + assert.strictEqual(count, 4); done(err); }); }); diff --git a/test/Validation.test.js b/test/Validation.test.js index 8038a7c63..d09b6631d 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -1,23 +1,52 @@ -var Validation = require('../lib/Validation').Validation; -require('should'); +'use strict'; + +const assert = require('assert'); + +const validation = require('../lib/Validation'); + +const Validation = validation.Validation; describe('Validation', function () { describe('isValidUTF8', function () { it('should return true for a valid utf8 string', function () { - var validBuffer = new Buffer('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque gravida mattis rhoncus. Donec iaculis, metus quis varius accumsan, erat mauris condimentum diam, et egestas erat enim ut ligula. Praesent sollicitudin tellus eget dolor euismod euismod. Nullam ac augue nec neque varius luctus. Curabitur elit mi, consequat ultricies adipiscing mollis, scelerisque in erat. Phasellus facilisis fermentum ullamcorper. Nulla et sem eu arcu pharetra pellentesque. Praesent consectetur tempor justo, vel iaculis dui ullamcorper sit amet. Integer tristique viverra ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, lacus lectus feugiat libero, non fermentum erat nisi at risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pulvinar dignissim tellus, eu dignissim lorem vulputate quis. Morbi ut pulvinar augue.'); - Validation.isValidUTF8(validBuffer).should.be.ok; + const validBuffer = Buffer.from( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + + 'Quisque gravida mattis rhoncus. Donec iaculis, metus ' + + 'quis varius accumsan, erat mauris condimentum diam, et ' + + 'egestas erat enim ut ligula. Praesent sollicitudin tellus ' + + 'eget dolor euismod euismod. Nullam ac augue nec neque ' + + 'varius luctus. Curabitur elit mi, consequat ultricies ' + + 'adipiscing mollis, scelerisque in erat. Phasellus facilisis ' + + ' fermentum ullamcorper. Nulla et sem eu arcu pharetra ' + + 'pellentesque. Praesent consectetur tempor justo, vel ' + + 'iaculis dui ullamcorper sit amet. Integer tristique viverra ' + + 'ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, ' + + 'lacus lectus feugiat libero, non fermentum erat nisi at ' + + 'risus. Lorem ipsum dolor sit amet, consectetur adipiscing ' + + 'elit. Ut pulvinar dignissim tellus, eu dignissim lorem ' + + 'vulputate quis. Morbi ut pulvinar augue.' + ); + + assert.ok(Validation.isValidUTF8(validBuffer)); }); + it('should return false for an erroneous string', function () { - var invalidBuffer = new Buffer([0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64]); - Validation.isValidUTF8(invalidBuffer).should.not.be.ok; + const invalidBuffer = Buffer.from([ + 0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, + 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, + 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 + ]); + + assert.ok(!Validation.isValidUTF8(invalidBuffer)); }); + it('should return true for valid cases from the autobahn test suite', function () { - Validation.isValidUTF8(new Buffer('\xf0\x90\x80\x80')).should.be.ok; - Validation.isValidUTF8(new Buffer([0xf0, 0x90, 0x80, 0x80])).should.be.ok; + assert.ok(Validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); + assert.ok(Validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); }); + it('should return false for erroneous autobahn strings', function () { - Validation.isValidUTF8(new Buffer([0xce, 0xba, 0xe1, 0xbd])).should.not.be.ok; + assert.ok(!Validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); }); }); }); - diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index 228ffcb93..237ac5cfc 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -1,27 +1,28 @@ 'use strict'; const assert = require('assert'); -const WebSocket = require('../'); + +const WebSocket = require('..'); describe('WebSocket', function () { it('communicates successfully with echo service', function (done) { - var ws = new WebSocket('ws://echo.websocket.org/', { + const ws = new WebSocket('ws://echo.websocket.org/', { origin: 'http://websocket.org', protocolVersion: 13 }); - var str = Date.now().toString(); - var dataReceived = false; - ws.on('open', function () { - ws.send(str, {mask: true}); - }); - ws.on('close', function () { - assert.equal(true, dataReceived); + const str = Date.now().toString(); + + let dataReceived = false; + + ws.on('open', () => ws.send(str, { mask: true })); + ws.on('close', () => { + assert.ok(dataReceived); done(); }); - ws.on('message', function (data, flags) { - assert.equal(str, data); - ws.terminate(); + ws.on('message', (data) => { dataReceived = true; + assert.strictEqual(data, str); + ws.close(); }); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 425689e73..ad05a9848 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -3,23 +3,23 @@ 'use strict'; const assert = require('assert'); +const crypto = require('crypto'); const https = require('https'); const http = require('http'); -const WebSocket = require('../'); const fs = require('fs'); const os = require('os'); + const server = require('./testserver'); -const crypto = require('crypto'); +const WebSocket = require('..'); const WebSocketServer = WebSocket.Server; - let port = 20000; function getArrayBuffer (buf) { - var l = buf.length; - var arrayBuf = new ArrayBuffer(l); - var uint8View = new Uint8Array(arrayBuf); - for (var i = 0; i < l; i++) { + const l = buf.length; + const arrayBuf = new ArrayBuffer(l); + const uint8View = new Uint8Array(arrayBuf); + for (let i = 0; i < l; i++) { uint8View[i] = buf[i]; } return uint8View.buffer; @@ -27,7 +27,7 @@ function getArrayBuffer (buf) { function areArraysEqual (x, y) { if (x.length !== y.length) return false; - for (var i = 0, l = x.length; i < l; ++i) { + for (let i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; } return true; @@ -36,7 +36,7 @@ function areArraysEqual (x, y) { describe('WebSocket', function () { describe('#ctor', function () { it('should return a new instance if called without new', function (done) { - var ws = WebSocket('ws://localhost'); + const ws = WebSocket('ws://localhost'); assert.ok(ws instanceof WebSocket); ws.on('error', () => done()); @@ -91,8 +91,8 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: false }); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); ws.on('message', function () { assert.strictEqual(ws.bytesReceived, 8); wss.close(); @@ -106,8 +106,8 @@ describe('WebSocket', function () { it('#url exposes the server url', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); assert.equal(url, ws.url); ws.terminate(); ws.on('close', function () { @@ -119,8 +119,8 @@ describe('WebSocket', function () { it('#protocolVersion exposes the protocol version', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); assert.equal(13, ws.protocolVersion); ws.terminate(); ws.on('close', function () { @@ -133,8 +133,8 @@ describe('WebSocket', function () { describe('#bufferedAmount', function () { it('defaults to zero', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); assert.equal(0, ws.bufferedAmount); ws.terminate(); ws.on('close', function () { @@ -146,8 +146,8 @@ describe('WebSocket', function () { it('defaults to zero upon "open"', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); ws.onopen = function () { assert.equal(0, ws.bufferedAmount); ws.terminate(); @@ -176,11 +176,11 @@ describe('WebSocket', function () { describe('Custom headers', function () { it('request has an authorization header', function (done) { - var auth = 'test:testpass'; - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv}); + const auth = 'test:testpass'; + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv}); srv.listen(++port); - var ws = new WebSocket('ws://' + auth + '@localhost:' + port); + const ws = new WebSocket('ws://' + auth + '@localhost:' + port); srv.on('upgrade', function (req, socket, head) { assert(req.headers.authorization, 'auth header exists'); assert.equal(req.headers.authorization, 'Basic ' + new Buffer(auth).toString('base64')); @@ -194,11 +194,11 @@ describe('WebSocket', function () { }); it('accepts custom headers', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv}); srv.listen(++port); - var ws = new WebSocket('ws://localhost:' + port, { + const ws = new WebSocket(`ws://localhost:${port}`, { headers: { 'Cookie': 'foo=bar' } @@ -221,7 +221,7 @@ describe('WebSocket', function () { describe('#readyState', function () { it('defaults to connecting', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); assert.equal(WebSocket.CONNECTING, ws.readyState); ws.terminate(); ws.on('close', function () { @@ -233,7 +233,7 @@ describe('WebSocket', function () { it('set to open once connection is established', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.equal(WebSocket.OPEN, ws.readyState); srv.close(); @@ -244,7 +244,7 @@ describe('WebSocket', function () { it('set to closed once connection is closed', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.close(1001); ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); @@ -256,7 +256,7 @@ describe('WebSocket', function () { it('set to closed once connection is terminated', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.terminate(); ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); @@ -271,7 +271,7 @@ describe('WebSocket', function () { * Ready state constants */ - var readyStates = { + const readyStates = { CONNECTING: 0, OPEN: 1, CLOSING: 2, @@ -285,7 +285,7 @@ describe('WebSocket', function () { Object.keys(readyStates).forEach(function (state) { describe('.' + state, function () { it('is enumerable property of class', function () { - var propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); + const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); assert.equal(readyStates[state], propertyDescripter.value); assert.equal(true, propertyDescripter.enumerable); }); @@ -293,7 +293,7 @@ describe('WebSocket', function () { }); server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); Object.keys(readyStates).forEach(function (state) { describe('.' + state, function () { it('is property of instance', function () { @@ -306,11 +306,11 @@ describe('WebSocket', function () { describe('events', function () { it('emits a ping event', function (done) { - var wss = new WebSocketServer({port: ++port}); + const wss = new WebSocketServer({port: ++port}); wss.on('connection', function (client) { client.ping(); }); - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('ping', function () { ws.terminate(); wss.close(); @@ -319,11 +319,11 @@ describe('WebSocket', function () { }); it('emits a pong event', function (done) { - var wss = new WebSocketServer({port: ++port}); + const wss = new WebSocketServer({port: ++port}); wss.on('connection', function (client) { client.pong(); }); - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('pong', function () { ws.terminate(); wss.close(); @@ -335,7 +335,7 @@ describe('WebSocket', function () { describe('connection establishing', function () { it('can disconnect before connection is established', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.terminate(); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); @@ -349,7 +349,7 @@ describe('WebSocket', function () { it('can close before connection is established', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.close(1001); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); @@ -365,11 +365,11 @@ describe('WebSocket', function () { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded ++port; - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - var errorCallBackFired = false; + let errorCallBackFired = false; ws.on('error', function () { errorCallBackFired = true; }); @@ -384,7 +384,7 @@ describe('WebSocket', function () { it('invalid server key is denied', function (done) { server.createServer(++port, server.handlers.invalidKey, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () { srv.close(); done(); @@ -394,7 +394,7 @@ describe('WebSocket', function () { it('close event is raised when server closes connection', function (done) { server.createServer(++port, server.handlers.closeAfterConnect, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', function () { srv.close(); done(); @@ -404,7 +404,7 @@ describe('WebSocket', function () { it('error is emitted if server aborts connection', function (done) { server.createServer(++port, server.handlers.return401, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); @@ -417,14 +417,14 @@ describe('WebSocket', function () { it('unexpected response can be read when sent by server', function (done) { server.createServer(++port, server.handlers.return401, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); ws.on('unexpected-response', function (req, res) { assert.equal(res.statusCode, 401); - var data = ''; + let data = ''; res.on('data', function (v) { data += v; @@ -444,7 +444,7 @@ describe('WebSocket', function () { it('request can be aborted when unexpected response is sent by server', function (done) { server.createServer(++port, server.handlers.return401, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); @@ -467,8 +467,8 @@ describe('WebSocket', function () { describe('connection with query string', function () { it('connects when pathname is not null', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); ws.on('open', function () { wss.close(done); }); @@ -476,8 +476,8 @@ describe('WebSocket', function () { }); it('connects when pathname is null', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); ws.on('open', function () { wss.close(done); }); @@ -491,12 +491,12 @@ describe('WebSocket', function () { // to localhost can cause the test to succeed even when the stream pausing // isn't working as intended. that is an extremely unlikely scenario, though // and an acceptable risk for the test. - var client; - var serverClient; - var openCount = 0; + let client; + let serverClient; + let openCount = 0; function onOpen () { if (++openCount === 2) { - var paused = true; + let paused = true; serverClient.on('message', function () { assert.ok(!paused); wss.close(); @@ -510,8 +510,8 @@ describe('WebSocket', function () { client.send('foo'); } } - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); serverClient = ws; serverClient.on('open', onOpen); }); @@ -525,7 +525,7 @@ describe('WebSocket', function () { describe('#ping', function () { it('before connect should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); try { ws.ping(); @@ -539,7 +539,7 @@ describe('WebSocket', function () { it('before connect can silently fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.ping('', {}, true); srv.close(); @@ -550,7 +550,7 @@ describe('WebSocket', function () { it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping(); }); @@ -564,7 +564,7 @@ describe('WebSocket', function () { it('with message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping('hi'); }); @@ -579,7 +579,7 @@ describe('WebSocket', function () { it('can send safely receive numbers as ping payload', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping(200); @@ -596,7 +596,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping('hi', {mask: true}); }); @@ -614,7 +614,7 @@ describe('WebSocket', function () { describe('#pong', function () { it('before connect should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); try { ws.pong(); @@ -628,7 +628,7 @@ describe('WebSocket', function () { it('before connect can silently fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.pong('', {}, true); srv.close(); @@ -639,7 +639,7 @@ describe('WebSocket', function () { it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.pong(); }); @@ -653,7 +653,7 @@ describe('WebSocket', function () { it('with message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.pong('hi'); }); @@ -668,7 +668,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.pong('hi', {mask: true}); }); @@ -686,9 +686,9 @@ describe('WebSocket', function () { describe('#send', function () { it('very long binary data can be sent and received (with echoing server)', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5 * 1024 * 1024); - for (var i = 0; i < array.length; ++i) array[i] = i / 5; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5 * 1024 * 1024); + for (let i = 0; i < array.length; ++i) array[i] = i / 5; ws.on('open', function () { ws.send(array, {binary: true}); }); @@ -704,7 +704,7 @@ describe('WebSocket', function () { it('can send and receive text data', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi'); }); @@ -737,10 +737,10 @@ describe('WebSocket', function () { it('send and receive binary data as an array', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(6); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; - var partial = array.subarray(2, 5); + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(6); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; + const partial = array.subarray(2, 5); ws.on('open', function () { ws.send(partial, {binary: true}); }); @@ -756,8 +756,8 @@ describe('WebSocket', function () { it('binary data can be sent and received as buffer', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var buf = new Buffer('foobar'); + const ws = new WebSocket(`ws://localhost:${port}`); + const buf = new Buffer('foobar'); ws.on('open', function () { ws.send(buf, {binary: true}); }); @@ -773,9 +773,9 @@ describe('WebSocket', function () { it('ArrayBuffer is auto-detected without binary flag', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; ws.on('open', function () { ws.send(array.buffer); }); @@ -791,8 +791,8 @@ describe('WebSocket', function () { it('Buffer is auto-detected without binary flag', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var buf = new Buffer('foobar'); + const ws = new WebSocket(`ws://localhost:${port}`); + const buf = new Buffer('foobar'); ws.on('open', function () { ws.send(buf); }); @@ -808,7 +808,7 @@ describe('WebSocket', function () { it('before connect should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); try { ws.send('hi'); @@ -822,7 +822,7 @@ describe('WebSocket', function () { it('before connect should pass error through callback, if present', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.send('hi', function (error) { assert.ok(error instanceof Error); @@ -835,7 +835,7 @@ describe('WebSocket', function () { it('without data should be successful', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send(); }); @@ -850,7 +850,7 @@ describe('WebSocket', function () { it('calls optional callback when flushed', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi', function () { srv.close(); @@ -863,7 +863,7 @@ describe('WebSocket', function () { it('with unencoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi'); }); @@ -878,7 +878,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi', {mask: true}); }); @@ -894,9 +894,9 @@ describe('WebSocket', function () { it('with unencoded binary message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; ws.on('open', function () { ws.send(array, {binary: true}); }); @@ -912,9 +912,9 @@ describe('WebSocket', function () { it('with encoded binary message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; ws.on('open', function () { ws.send(array, {mask: true, binary: true}); }); @@ -931,10 +931,10 @@ describe('WebSocket', function () { it('with binary stream will send fragmented data', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var callbackFired = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let callbackFired = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true}, function (error) { assert.equal(null, error); callbackFired = true; @@ -955,10 +955,10 @@ describe('WebSocket', function () { it('with text stream will send fragmented data', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var callbackFired = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let callbackFired = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, {binary: false}, function (error) { assert.equal(null, error); callbackFired = true; @@ -979,14 +979,14 @@ describe('WebSocket', function () { it('will cause intermittent send to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.send('foobar'); ws.send('baz'); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1008,18 +1008,18 @@ describe('WebSocket', function () { it('will cause intermittent stream to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) send('foo'); else send('bar', true); }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1038,13 +1038,13 @@ describe('WebSocket', function () { it('will cause intermittent ping to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.ping('foobar'); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); @@ -1067,13 +1067,13 @@ describe('WebSocket', function () { it('will cause intermittent pong to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.pong('foobar'); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); @@ -1096,9 +1096,9 @@ describe('WebSocket', function () { it('will cause intermittent close to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.close(1000, 'foobar'); }); @@ -1123,19 +1123,19 @@ describe('WebSocket', function () { describe('#stream', function () { it('very long binary data can be streamed', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var buffer = new Buffer(10 * 1024); - for (var i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; + const ws = new WebSocket(`ws://localhost:${port}`); + const buffer = new Buffer(10 * 1024); + for (let i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; ws.on('open', function () { - var i = 0; - var blockSize = 800; - var bufLen = buffer.length; + let i = 0; + const blockSize = 800; + const bufLen = buffer.length; ws.stream({binary: true}, function (error, send) { assert.ok(!error); - var start = i * blockSize; - var toSend = Math.min(blockSize, bufLen - (i * blockSize)); - var end = start + toSend; - var isFinal = toSend < blockSize; + const start = i * blockSize; + const toSend = Math.min(blockSize, bufLen - (i * blockSize)); + const end = start + toSend; + const isFinal = toSend < blockSize; send(buffer.slice(start, end), isFinal); i += 1; }); @@ -1152,7 +1152,7 @@ describe('WebSocket', function () { it('before connect should pass error through callback', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.stream(function (error) { assert.ok(error instanceof Error); @@ -1165,7 +1165,7 @@ describe('WebSocket', function () { it('without callback should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { try { ws.stream(); @@ -1180,10 +1180,10 @@ describe('WebSocket', function () { it('will cause intermittent send to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { @@ -1195,7 +1195,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1217,15 +1217,15 @@ describe('WebSocket', function () { it('will cause intermittent stream to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { send(payload.substr(0, 5)); - var i2 = 0; + let i2 = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i2 === 1) send('foo'); @@ -1237,7 +1237,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1263,10 +1263,10 @@ describe('WebSocket', function () { it('will cause intermittent ping to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { @@ -1277,7 +1277,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); @@ -1300,10 +1300,10 @@ describe('WebSocket', function () { it('will cause intermittent pong to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { @@ -1314,7 +1314,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); @@ -1337,11 +1337,11 @@ describe('WebSocket', function () { it('will cause intermittent close to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; - var errorGiven = false; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; + let errorGiven = false; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { if (++i === 1) { send(payload.substr(0, 5)); @@ -1375,10 +1375,10 @@ describe('WebSocket', function () { describe('#close', function () { it('will raise error callback, if any, if called during send stream', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var errorGiven = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let errorGiven = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, function (error) { errorGiven = error != null; }); @@ -1397,7 +1397,7 @@ describe('WebSocket', function () { it('without invalid first argument throws exception', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { try { ws.close('error'); @@ -1412,7 +1412,7 @@ describe('WebSocket', function () { it('without reserved error code 1004 throws exception', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { try { ws.close(1004); @@ -1427,7 +1427,7 @@ describe('WebSocket', function () { it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.close(1000); }); @@ -1442,7 +1442,7 @@ describe('WebSocket', function () { it('with message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.close(1000, 'some reason'); }); @@ -1458,7 +1458,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.close(1000, 'some reason', {mask: true}); }); @@ -1474,8 +1474,8 @@ describe('WebSocket', function () { it('ends connection to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var connectedOnce = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let connectedOnce = false; ws.on('open', function () { connectedOnce = true; ws.close(1000, 'some reason', {mask: true}); @@ -1490,15 +1490,15 @@ describe('WebSocket', function () { }); it('consumes all data when the server socket closed', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({port: ++port}, function () { wss.on('connection', function (conn) { conn.send('foo'); conn.send('bar'); conn.send('baz'); conn.close(); }); - var ws = new WebSocket('ws://localhost:' + port); - var messages = []; + const ws = new WebSocket(`ws://localhost:${port}`); + const messages = []; ws.on('message', function (message) { messages.push(message); if (messages.length === 3) { @@ -1512,8 +1512,8 @@ describe('WebSocket', function () { }); it('allows close code 1013', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket(`ws://localhost:${port}`); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', function (code) { assert.strictEqual(code, 1013); wss.close(done); @@ -1529,8 +1529,8 @@ describe('WebSocket', function () { describe('W3C API emulation', function () { it('should not throw errors when getting and setting', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var listener = function () {}; + const ws = new WebSocket(`ws://localhost:${port}`); + const listener = function () {}; ws.onmessage = listener; ws.onerror = listener; @@ -1554,10 +1554,10 @@ describe('WebSocket', function () { it('should work the same as the EventEmitter api', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var message = 0; - var close = 0; - var open = 0; + const ws = new WebSocket(`ws://localhost:${port}`); + let message = 0; + let close = 0; + let open = 0; ws.onmessage = function (messageEvent) { assert.ok(!!messageEvent.data); @@ -1593,7 +1593,7 @@ describe('WebSocket', function () { it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', function () { ws.send('hi'); }); @@ -1607,8 +1607,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', function (closeEvent) { assert.equal(true, closeEvent.wasClean); assert.equal(1000, closeEvent.code); @@ -1623,8 +1623,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1001', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', function (closeEvent) { assert.equal(false, closeEvent.wasClean); assert.equal(1001, closeEvent.code); @@ -1640,8 +1640,8 @@ describe('WebSocket', function () { }); it('should have target set on Events', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', function (openEvent) { assert.equal(ws, openEvent.target); }); @@ -1666,8 +1666,8 @@ describe('WebSocket', function () { }); it('should have type set on Events', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', function (openEvent) { assert.equal('open', openEvent.type); }); @@ -1693,8 +1693,8 @@ describe('WebSocket', function () { it('should pass binary data as a node.js Buffer by default', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Uint8Array(4096); + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Uint8Array(4096); ws.onopen = function () { ws.send(array, {binary: true}); @@ -1712,9 +1712,9 @@ describe('WebSocket', function () { it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; - var array = new Uint8Array(4096); + const array = new Uint8Array(4096); ws.onopen = function () { ws.send(array, {binary: true}); @@ -1731,7 +1731,7 @@ describe('WebSocket', function () { it('should ignore binaryType for text messages', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; ws.onopen = function () { @@ -1927,8 +1927,8 @@ describe('WebSocket', function () { it('excludes default ports from host header', function () { // can't create a server listening on ports 80 or 443 // so we need to expose the method that does this - var buildHostHeader = WebSocket.buildHostHeader; - var host = buildHostHeader(false, 'localhost', 80); + const buildHostHeader = WebSocket.buildHostHeader; + let host = buildHostHeader(false, 'localhost', 80); assert.equal('localhost', host); host = buildHostHeader(false, 'localhost', 88); assert.equal('localhost:88', host); @@ -1941,10 +1941,10 @@ describe('WebSocket', function () { describe('permessage-deflate', function () { it('is enabled by default', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); srv.on('upgrade', function (req, socket, head) { assert.ok(~req.headers['sec-websocket-extensions'].indexOf('permessage-deflate')); }); @@ -1958,10 +1958,10 @@ describe('WebSocket', function () { }); it('can be disabled', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); srv.on('upgrade', function (req, socket, head) { assert.ok(!req.headers['sec-websocket-extensions']); ws.terminate(); @@ -1972,10 +1972,10 @@ describe('WebSocket', function () { }); it('can send extension parameters', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port, { + const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -1984,7 +1984,7 @@ describe('WebSocket', function () { } }); srv.on('upgrade', function (req, socket, head) { - var extensions = req.headers['sec-websocket-extensions']; + const extensions = req.headers['sec-websocket-extensions']; assert.ok(~extensions.indexOf('permessage-deflate')); assert.ok(~extensions.indexOf('server_no_context_takeover')); assert.ok(~extensions.indexOf('client_no_context_takeover')); @@ -1998,8 +1998,8 @@ describe('WebSocket', function () { }); it('can send and receive text data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); ws.on('open', function () { ws.send('hi', {compress: true}); }); @@ -2018,10 +2018,10 @@ describe('WebSocket', function () { }); it('can send and receive a typed array', function (done) { - var array = new Float32Array(5); - for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const array = new Float32Array(5); + for (let i = 0; i < array.length; i++) array[i] = i / 2; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); ws.on('open', function () { ws.send(array, {compress: true}); }); @@ -2040,10 +2040,10 @@ describe('WebSocket', function () { }); it('can send and receive ArrayBuffer', function (done) { - var array = new Float32Array(5); - for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const array = new Float32Array(5); + for (let i = 0; i < array.length; i++) array[i] = i / 2; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); ws.on('open', function () { ws.send(array.buffer, {compress: true}); }); @@ -2062,11 +2062,11 @@ describe('WebSocket', function () { }); it('with binary stream will send fragmented data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - var callbackFired = false; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + let callbackFired = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true, compress: true}, function (error) { assert.equal(null, error); callbackFired = true; @@ -2089,8 +2089,8 @@ describe('WebSocket', function () { describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); ws.on('open', function () { ws.send('hi', {compress: true}); }); @@ -2111,9 +2111,9 @@ describe('WebSocket', function () { describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - var errorGiven = false; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + let errorGiven = false; ws.on('open', function () { ws.send('hi', function (error) { errorGiven = error != null; @@ -2134,9 +2134,9 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: { threshold: 0 }}); - var errorGiven = false; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: { threshold: 0 }}); + let errorGiven = false; ws.on('open', function () { ws.send('hi', function (error) { errorGiven = error != null; @@ -2155,10 +2155,10 @@ describe('WebSocket', function () { }); it('can call during receiving data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); wss.on('connection', function (client) { - for (var i = 0; i < 10; i++) { + for (let i = 0; i < 10; i++) { client.send('hi'); } client.send('hi', function () { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 35f4d9c5b..9d035c375 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -3,14 +3,13 @@ 'use strict'; const assert = require('assert'); -const WebSocket = require('../'); const https = require('https'); const http = require('http'); const fs = require('fs'); -require('should'); -const WebSocketServer = WebSocket.Server; +const WebSocket = require('..'); +const WebSocketServer = WebSocket.Server; let port = 8000; describe('WebSocketServer', function () { @@ -24,25 +23,25 @@ describe('WebSocketServer', function () { }); it('should return a new instance if called without new', function () { - var wss = WebSocketServer({ noServer: true }); + const wss = WebSocketServer({ noServer: true }); assert.ok(wss instanceof WebSocketServer); }); it('emits an error if http server bind fails', function (done) { - var wss1 = new WebSocketServer({ port: 50003 }); - var wss2 = new WebSocketServer({ port: 50003 }); - wss2.on('error', function () { + const wss1 = new WebSocketServer({ port: 50003 }); + const wss2 = new WebSocketServer({ port: 50003 }); + wss2.on('error', () => { wss1.close(); done(); }); }); it('starts a server on a given port', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { + wss.on('connection', (client) => { wss.close(); done(); }); @@ -55,7 +54,7 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ server }); const ws = new WebSocket(`ws://localhost:${port}`); - wss.on('connection', function (client) { + wss.on('connection', (client) => { wss.close(); server.close(done); }); @@ -63,13 +62,13 @@ describe('WebSocketServer', function () { }); it('426s for non-Upgrade requests', function (done) { - var wss = new WebSocketServer({ port: ++port }, function () { - http.get('http://localhost:' + port, function (res) { - var body = ''; + const wss = new WebSocketServer({ port: ++port }, () => { + http.get(`http://localhost:${port}`, (res) => { + let body = ''; assert.strictEqual(res.statusCode, 426); - res.on('data', function (chunk) { body += chunk; }); - res.on('end', function () { + res.on('data', (chunk) => { body += chunk; }); + res.on('end', () => { assert.strictEqual(body, http.STATUS_CODES[426]); wss.close(); done(); @@ -170,7 +169,7 @@ describe('WebSocketServer', function () { describe('#close', function () { it('does not thrown when called twice', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { wss.close(); wss.close(); wss.close(); @@ -180,15 +179,15 @@ describe('WebSocketServer', function () { }); it('will close all clients', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); - ws.on('close', function () { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + ws.on('close', () => { if (++closes === 2) done(); }); }); - var closes = 0; - wss.on('connection', function (client) { - client.on('close', function () { + let closes = 0; + wss.on('connection', (client) => { + client.on('close', () => { if (++closes === 2) done(); }); wss.close(); @@ -205,7 +204,7 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ server }); - wss.on('connection', function (ws) { + wss.on('connection', (ws) => { wss.close(); server.close = realClose; server.close(done); @@ -232,81 +231,91 @@ describe('WebSocketServer', function () { }); it('cleans up websocket data on a precreated server', function (done) { - const srv = http.createServer(); - srv.listen(++port, () => { - const wss1 = new WebSocketServer({server: srv, path: '/wss1'}); - const wss2 = new WebSocketServer({server: srv, path: '/wss2'}); - (typeof srv._webSocketPaths).should.eql('object'); - Object.keys(srv._webSocketPaths).length.should.eql(2); + const server = http.createServer(); + + server.listen(++port, () => { + const wss1 = new WebSocketServer({ server, path: '/wss1' }); + const wss2 = new WebSocketServer({ server, path: '/wss2' }); + + assert.strictEqual(typeof server._webSocketPaths, 'object'); + assert.strictEqual(Object.keys(server._webSocketPaths).length, 2); + wss1.close(); - Object.keys(srv._webSocketPaths).length.should.eql(1); + + assert.strictEqual(Object.keys(server._webSocketPaths).length, 1); + wss2.close(); - (typeof srv._webSocketPaths).should.eql('undefined'); - srv.close(done); + + assert.strictEqual(typeof server._webSocketPaths, 'undefined'); + server.close(done); }); }); }); describe('#clients', function () { it('returns a list of connected clients', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port }, () => { + assert.strictEqual(wss.clients.size, 0); + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); + + wss.on('connection', (client) => { + assert.strictEqual(wss.clients.size, 1); wss.close(); done(); }); }); it('can be disabled', function (done) { - var wss = new WebSocketServer({port: ++port, clientTracking: false}, function () { - wss.should.not.have.property('clients'); - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port, clientTracking: false }, () => { + assert.strictEqual(wss.clients, undefined); + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { - wss.should.not.have.property('clients'); + + wss.on('connection', (client) => { + assert.strictEqual(wss.clients, undefined); wss.close(); done(); }); }); it('is updated when client terminates the connection', function (done) { - var ws; - var wss = new WebSocketServer({port: ++port}, function () { - ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - client.on('close', function () { - wss.clients.size.should.eql(0); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + wss.on('connection', (client) => { + client.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(); + done(); + }); + + ws.close(); }); - ws.terminate(); }); }); it('is updated when client closes the connection', function (done) { - var ws; - var wss = new WebSocketServer({port: ++port}, function () { - ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - client.on('close', function () { - wss.clients.size.should.eql(0); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + wss.on('connection', (client) => { + client.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(); + done(); + }); + + ws.close(); }); - ws.close(); }); }); }); describe('#options', function () { it('exposes options passed to constructor', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - wss.options.port.should.eql(port); + const wss = new WebSocketServer({ port: ++port }, () => { + assert.strictEqual(wss.options.port, port); wss.close(); done(); }); @@ -315,41 +324,43 @@ describe('WebSocketServer', function () { describe('#maxpayload', function () { it('maxpayload is passed on to clients,', function (done) { - var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); - client.maxPayload.should.eql(_maxPayload); + const maxPayload = 20480; + const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (client) => { + assert.strictEqual(client.maxPayload, maxPayload); wss.close(); done(); }); }); + it('maxpayload is passed on to hybi receivers', function (done) { - var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); - client._receiver.maxPayload.should.eql(_maxPayload); + const maxPayload = 20480; + const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (client) => { + assert.strictEqual(client._receiver.maxPayload, maxPayload); wss.close(); done(); }); }); + it('maxpayload is passed on to permessage-deflate', function (done) { - var PerMessageDeflate = require('../lib/PerMessageDeflate'); - var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); - client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); + const PerMessageDeflate = require('../lib/PerMessageDeflate'); + const maxPayload = 20480; + const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (client) => { + assert.strictEqual( + client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload, + maxPayload + ); wss.close(); done(); }); @@ -358,627 +369,648 @@ describe('WebSocketServer', function () { describe('#handleUpgrade', function () { it('can be used for a pre-existing server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({noServer: true}); - srv.on('upgrade', function (req, socket, upgradeHead) { - wss.handleUpgrade(req, socket, upgradeHead, function (client) { - client.send('hello'); - }); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ noServer: true }); + + server.on('upgrade', (req, socket, head) => { + wss.handleUpgrade(req, socket, head, (client) => client.send('hello')); }); - var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function (message) { - message.should.eql('hello'); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('message', (message) => { + assert.strictEqual(message, 'hello'); wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); it('closes the connection when path does not match', function (done) { - var wss = new WebSocketServer({port: ++port, path: '/ws'}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port, path: '/ws' }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); }); it('closes the connection when protocol version is Hixie-76', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 'Sec-WebSocket-Protocol': 'sample' - } - }; - var req = http.request(options); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + req.end(); }); }); }); - describe('hybi mode', function () { - describe('connection establishing', function () { - it('does not accept connections with no sec-websocket-key', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); - }); - - it('does not accept connections with no sec-websocket-version', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); - }); - - it('does not accept connections with invalid sec-websocket-version', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 12 - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); + describe('connection establishing', function () { + it('does not accept connections with no sec-websocket-key', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + wss.close(); + done(); }); - wss.on('error', function () {}); - }); - - it('client can be denied', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o) => false, - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(); - done(); - }); + req.end(); + }); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('does not accept connections with no sec-websocket-version', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + wss.close(); + done(); }); + + req.end(); }); - it('client can be accepted', function (done) { - var wss = new WebSocketServer({ - port: ++port, - verifyClient: (o) => true - }, () => { - var req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - } - }); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('does not accept connections with invalid sec-websocket-version', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 12 + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - ws.terminate(); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); - it('verifyClient gets client origin', function (done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({ - verifyClient: (info) => { - info.origin.should.eql('http://foobarbaz.com'); - verifyClientCalled = true; - return false; + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be denied', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o) => false, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' }, - port: ++port - }, () => { - var req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - } - }); - req.on('response', (res) => { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - req.end(); + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); }); + + req.end(); }); - it('verifyClient gets original request', function (done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({ - verifyClient: (info) => { - info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); - verifyClientCalled = true; - return false; + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be accepted', function (done) { + const wss = new WebSocketServer({ + port: ++port, + verifyClient: (o) => true + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' }, - port: ++port - }, () => { - var req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - } - }); - req.on('response', (res) => { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - req.end(); + host: '127.0.0.1', + port }); + + req.end(); }); - it('verifyClient has secure:true for ssl connections', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var success = false; - var wss = new WebSocketServer({ - server: app, - verifyClient: function (info) { - success = info.secure === true; - return true; - } - }); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); + wss.on('connection', (ws) => { + wss.close(); + done(); + }); + }); + + it('verifyClient gets client origin', function (done) { + let verifyClientCalled = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { + assert.strictEqual(info.origin, 'http://foobarbaz.com'); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobarbaz.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + + req.on('response', (res) => { + assert.ok(verifyClientCalled); wss.close(); - success.should.be.ok; done(); }); + + req.end(); }); + }); - it('verifyClient has secure:false for non-ssl connections', function (done) { - var app = http.createServer(function (req, res) { - res.writeHead(200); - res.end(); - }); - var success = false; - var wss = new WebSocketServer({ - server: app, - verifyClient: function (info) { - success = info.secure === false; - return true; - } - }); - app.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port); + it('verifyClient gets original request', function (done) { + let verifyClientCalled = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { + assert.strictEqual( + info.req.headers['sec-websocket-key'], + 'dGhlIHNhbXBsZSBub25jZQ==' + ); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobarbaz.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + + req.on('response', (res) => { + assert.ok(verifyClientCalled); wss.close(); - success.should.be.ok; done(); }); + + req.end(); }); + }); - it('client can be denied asynchronously', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o, cb) => process.nextTick(cb, false), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - res.statusCode.should.eql(401); - wss.close(); - done(); - }); - req.end(); - }); + it('verifyClient has secure:true for ssl connections', function (done) { + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') + }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); + let success = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { + success = info.secure === true; + return true; + }, + server }); - it('client can be denied asynchronously with custom response code', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o, cb) => process.nextTick(cb, false, 404), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - res.statusCode.should.eql(404); - wss.close(); - done(); - }); - req.end(); + wss.on('connection', (ws) => { + assert.ok(success); + wss.close(); + server.close(done); + }); + + server.listen(++port, () => { + const ws = new WebSocket('wss://localhost:' + port); + }); + }); + + it('verifyClient has secure:false for non-ssl connections', function (done) { + const server = http.createServer(); + + let success = false; + const wss = new WebSocketServer({ + server: server, + verifyClient: (info) => { + success = info.secure === false; + return true; + } + }); + + wss.on('connection', (ws) => { + assert.ok(success); + wss.close(); + server.close(done); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + }); + + it('client can be denied asynchronously', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); }); + + req.end(); }); - it('client can be accepted asynchronously', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o, cb) => process.nextTick(cb, true), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - } - }); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be denied asynchronously with custom response code', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false, 404), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - ws.terminate(); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 404); wss.close(); done(); }); + + req.end(); }); - it('handles messages passed along with the upgrade request (upgrade head)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - } - }); - req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be accepted asynchronously', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, true), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - ws.on('message', (data) => { - data.should.eql('Hello'); - ws.terminate(); - wss.close(); - done(); - }); + req.end(); + }); + + wss.on('connection', (ws) => { + wss.close(); + done(); + }); + }); + + it('handles messages passed along with the upgrade request (upgrade head)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); + + req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); + req.end(); }); - it('selects the first protocol by default', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - ws.protocol.should.eql('prot1'); - wss.close(); - done(); - }); + wss.on('connection', (ws) => { + ws.on('message', (data) => { + assert.strictEqual(data, 'Hello'); + wss.close(); + done(); }); }); + }); - it('selects the last protocol via protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('selects the first protocol by default', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => { - ws.protocol.should.eql('prot2'); - wss.close(); - done(); - }); + ws.on('open', (client) => { + assert.strictEqual(ws.protocol, 'prot1'); + wss.close(); + done(); }); }); + }); - it('client detects invalid server protocol', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, 'prot3'), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('selects the last protocol via protocol handler', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); + ws.on('open', () => { + assert.strictEqual(ws.protocol, 'prot2'); + wss.close(); + done(); }); }); + }); - it('client detects no server protocol', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('client detects invalid server protocol', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, 'prot3'), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); }); + }); - it('client refuses server protocols', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('client detects no server protocol', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); }); + }); - it('server detects unauthorized protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(); - done(); - }); - req.end(); + it('client refuses server protocols', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); }); + }); - it('server detects invalid protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => { - // not calling callback is an error and shouldn't timeout + it('server detects unauthorized protocol handler', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Origin': 'http://foobar.com' }, - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 501); - wss.close(); - done(); - }); - req.end(); + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); }); + + req.end(); }); + }); - it('accept connections with sec-websocket-extensions', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' - } - }; - var req = http.request(options); - req.end(); + it('server detects invalid protocol handler', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => { + // not calling callback is an error and shouldn't timeout + }, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - ws.terminate(); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 501); wss.close(); done(); }); - wss.on('error', function () {}); + + req.end(); }); }); - describe('messaging', function () { - it('can send and receive data', function (done) { - var data = new Array(65 * 1024); - for (var i = 0; i < data.length; ++i) { - data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); - } - data = data.join(''); - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function (message, flags) { - ws.send(message); - }); + it('accept connections with sec-websocket-extensions', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (client) { - client.on('message', function (message) { - message.should.eql(data); - wss.close(); - done(); - }); - client.send(data); + + req.end(); + }); + + wss.on('connection', (ws) => { + wss.close(); + done(); + }); + }); + }); + + describe('messaging', function () { + it('can send and receive data', function (done) { + let data = new Array(65 * 1024); + + for (let i = 0; i < data.length; ++i) { + data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); + } + data = data.join(''); + + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('message', (message, flags) => ws.send(message)); + }); + + wss.on('connection', (client) => { + client.on('message', (message) => { + assert.strictEqual(message, data); + wss.close(); + done(); }); + + client.send(data); }); }); }); describe('client properties', function () { it('protocol is exposed', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, 'hi'); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, 'hi'); }); - wss.on('connection', function (client) { - client.protocol.should.eql('hi'); + + wss.on('connection', (client) => { + assert.strictEqual(client.protocol, 'hi'); wss.close(); done(); }); }); it('protocolVersion is exposed', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); }); - wss.on('connection', function (client) { - client.protocolVersion.should.eql(8); + + wss.on('connection', (client) => { + assert.strictEqual(client.protocolVersion, 8); wss.close(); done(); }); }); it('upgradeReq is the original request object', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); }); - wss.on('connection', function (client) { - client.upgradeReq.httpVersion.should.eql('1.1'); + + wss.on('connection', (client) => { + assert.strictEqual(client.upgradeReq.httpVersion, '1.1'); wss.close(); done(); }); @@ -987,81 +1019,82 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('accept connections with permessage-deflate extension', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits=8; server_max_window_bits=8; client_no_context_takeover; server_no_context_takeover' - } - }; - var req = http.request(options); + }, + host: '127.0.0.1', + port + }); + req.end(); }); - wss.on('connection', function (ws) { - ws.terminate(); + + wss.on('connection', (ws) => { wss.close(); done(); }); - wss.on('error', function () {}); }); it('does not accept connections with not defined extension parameter', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; foo=15' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('does not accept connections with invalid extension parameter', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; server_max_window_bits=foo' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); }); }); diff --git a/test/hybi-util.js b/test/hybi-util.js index 47446b0de..cdff71f5a 100644 --- a/test/hybi-util.js +++ b/test/hybi-util.js @@ -14,7 +14,7 @@ function mask (buf, maskString) { buf = Buffer.from(buf); - for (var i = 0; i < buf.length; ++i) { + for (let i = 0; i < buf.length; ++i) { buf[i] ^= _mask[i % 4]; } @@ -39,7 +39,7 @@ function pack (length, number) { * Returns a hex string representing the length of a message. */ function getHybiLengthAsHexString (len, masked) { - var s; + let s; masked = masked ? 0x80 : 0; diff --git a/test/testserver.js b/test/testserver.js index 3f24964c4..4f03b79c8 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,11 +1,12 @@ 'use strict'; -const http = require('http'); -const util = require('util'); const crypto = require('crypto'); const events = require('events'); -const Sender = require('../lib/Sender'); +const http = require('http'); +const util = require('util'); + const Receiver = require('../lib/Receiver'); +const Sender = require('../lib/Sender'); module.exports = { handlers: { @@ -19,11 +20,11 @@ module.exports = { cb = handler; handler = null; } - var webServer = http.createServer(function (req, res) { + const webServer = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('okay'); }); - var srv = new Server(webServer); + const srv = new Server(webServer); webServer.on('upgrade', function (req, socket) { webServer._socket = socket; (handler || validServer)(srv, req, socket); @@ -48,12 +49,12 @@ function validServer (server, req, socket) { } // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); + let key = req.headers['sec-websocket-key']; + const shasum = crypto.createHash('sha1'); shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -64,8 +65,8 @@ function validServer (server, req, socket) { socket.setTimeout(0); socket.setNoDelay(true); - var sender = new Sender(socket); - var receiver = new Receiver(); + const sender = new Sender(socket); + const receiver = new Receiver(); receiver.ontext = function (message, flags) { server.emit('message', message, flags); sender.send(message); @@ -111,12 +112,12 @@ function invalidRequestHandler (server, req, socket) { } // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); + let key = req.headers['sec-websocket-key']; + const shasum = crypto.createHash('sha1'); shasum.update(key + 'bogus', 'binary'); key = shasum.digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -139,12 +140,12 @@ function closeAfterConnectHandler (server, req, socket) { } // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); + let key = req.headers['sec-websocket-key']; + const shasum = crypto.createHash('sha1'); shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -156,7 +157,7 @@ function closeAfterConnectHandler (server, req, socket) { } function return401 (server, req, socket) { - var headers = [ + const headers = [ 'HTTP/1.1 401 Unauthorized', 'Content-type: text/html' ]; From 32750aa86445eb6d7efa6a57498c5cc754521f5a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 27 Oct 2016 20:57:05 +0200 Subject: [PATCH 098/489] [test] Clean up tests further --- test/WebSocket.test.js | 1875 +++++++++++++++++++++------------------- test/testserver.js | 168 ++-- 2 files changed, 1047 insertions(+), 996 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index ad05a9848..888e35d60 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -15,24 +15,6 @@ const WebSocket = require('..'); const WebSocketServer = WebSocket.Server; let port = 20000; -function getArrayBuffer (buf) { - const l = buf.length; - const arrayBuf = new ArrayBuffer(l); - const uint8View = new Uint8Array(arrayBuf); - for (let i = 0; i < l; i++) { - uint8View[i] = buf[i]; - } - return uint8View.buffer; -} - -function areArraysEqual (x, y) { - if (x.length !== y.length) return false; - for (let i = 0, l = x.length; i < l; ++i) { - if (x[i] !== y[i]) return false; - } - return true; -} - describe('WebSocket', function () { describe('#ctor', function () { it('should return a new instance if called without new', function (done) { @@ -72,7 +54,7 @@ describe('WebSocket', function () { }); }); - const wss = new WebSocketServer({ port: ++port }, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { localAddress: localAddresses[0] }); @@ -91,77 +73,70 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); - ws.on('message', function () { + ws.on('message', () => { assert.strictEqual(ws.bytesReceived, 8); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.send('foobar'); - }); + wss.on('connection', (ws) => ws.send('foobar')); }); it('#url exposes the server url', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const url = `ws://localhost:${port}`; const ws = new WebSocket(url); - assert.equal(url, ws.url); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + + assert.strictEqual(ws.url, url); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); it('#protocolVersion exposes the protocol version', function (done) { - server.createServer(++port, function (srv) { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - assert.equal(13, ws.protocolVersion); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.protocolVersion, 13); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); describe('#bufferedAmount', function () { it('defaults to zero', function (done) { - server.createServer(++port, function (srv) { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - assert.equal(0, ws.bufferedAmount); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.bufferedAmount, 0); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); it('defaults to zero upon "open"', function (done) { - server.createServer(++port, function (srv) { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - ws.onopen = function () { - assert.equal(0, ws.bufferedAmount); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.onopen = () => { + assert.strictEqual(ws.bufferedAmount, 0); + + ws.on('close', () => srv.close(done)); + ws.close(); }; }); }); it('stress kernel write buffer', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: false + }); }); wss.on('connection', (ws) => { @@ -176,43 +151,41 @@ describe('WebSocket', function () { describe('Custom headers', function () { it('request has an authorization header', function (done) { + const server = http.createServer(); + const wss = new WebSocketServer({ server }); const auth = 'test:testpass'; - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv}); - srv.listen(++port); - const ws = new WebSocket('ws://' + auth + '@localhost:' + port); - srv.on('upgrade', function (req, socket, head) { - assert(req.headers.authorization, 'auth header exists'); - assert.equal(req.headers.authorization, 'Basic ' + new Buffer(auth).toString('base64')); - ws.terminate(); - ws.on('close', function () { - srv.close(); - wss.close(); - done(); - }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://${auth}@localhost:${port}`); + }); + + server.on('upgrade', (req, socket, head) => { + assert.ok(req.headers.authorization); + assert.strictEqual( + req.headers.authorization, + `Basic ${new Buffer(auth).toString('base64')}` + ); + + wss.close(); + server.close(done); }); }); it('accepts custom headers', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv}); - srv.listen(++port); + const server = http.createServer(); + const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws://localhost:${port}`, { - headers: { - 'Cookie': 'foo=bar' - } - }); + server.on('upgrade', (req, socket, head) => { + assert.ok(req.headers.cookie); + assert.strictEqual(req.headers.cookie, 'foo=bar'); - srv.on('upgrade', function (req, socket, head) { - assert(req.headers.cookie, 'auth header exists'); - assert.equal(req.headers.cookie, 'foo=bar'); + wss.close(); + server.close(done); + }); - ws.terminate(); - ws.on('close', function () { - srv.close(); - wss.close(); - done(); + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + headers: { 'Cookie': 'foo=bar' } }); }); }); @@ -220,49 +193,52 @@ describe('WebSocket', function () { describe('#readyState', function () { it('defaults to connecting', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - assert.equal(WebSocket.CONNECTING, ws.readyState); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + + assert.strictEqual(ws.readyState, WebSocket.CONNECTING); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); it('set to open once connection is established', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.equal(WebSocket.OPEN, ws.readyState); - srv.close(); - done(); + + ws.on('open', () => { + assert.strictEqual(ws.readyState, WebSocket.OPEN); + ws.close(); }); + + ws.on('close', () => srv.close(done)); }); }); it('set to closed once connection is closed', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.close(1001); - ws.on('close', function () { - assert.equal(WebSocket.CLOSED, ws.readyState); - srv.close(); - done(); + + ws.on('close', () => { + assert.strictEqual(ws.readyState, WebSocket.CLOSED); + srv.close(done); }); + + ws.close(1001); }); }); it('set to closed once connection is terminated', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.terminate(); - ws.on('close', function () { - assert.equal(WebSocket.CLOSED, ws.readyState); - srv.close(); - done(); + + ws.on('close', () => { + assert.strictEqual(ws.readyState, WebSocket.CLOSED); + srv.close(done); }); + + ws.terminate(); }); }); }); @@ -282,23 +258,20 @@ describe('WebSocket', function () { * Ready state constant tests */ - Object.keys(readyStates).forEach(function (state) { - describe('.' + state, function () { + Object.keys(readyStates).forEach((state) => { + describe(`.${state}`, function () { it('is enumerable property of class', function () { const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); - assert.equal(readyStates[state], propertyDescripter.value); - assert.equal(true, propertyDescripter.enumerable); + + assert.strictEqual(propertyDescripter.value, readyStates[state]); + assert.strictEqual(propertyDescripter.enumerable, true); }); - }); - }); - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - Object.keys(readyStates).forEach(function (state) { - describe('.' + state, function () { - it('is property of instance', function () { - assert.equal(readyStates[state], ws[state]); - }); + it('is property of instance', function () { + const ws = new WebSocket('ws://localhost'); + ws.on('error', () => {}); + + assert.strictEqual(ws[state], readyStates[state]); }); }); }); @@ -306,181 +279,138 @@ describe('WebSocket', function () { describe('events', function () { it('emits a ping event', function (done) { - const wss = new WebSocketServer({port: ++port}); - wss.on('connection', function (client) { - client.ping(); - }); - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('ping', function () { - ws.terminate(); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + ws.on('ping', function () { + wss.close(); + done(); + }); }); + + wss.on('connection', (client) => client.ping()); }); it('emits a pong event', function (done) { - const wss = new WebSocketServer({port: ++port}); - wss.on('connection', function (client) { - client.pong(); - }); - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('pong', function () { - ws.terminate(); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + ws.on('pong', () => { + wss.close(); + done(); + }); }); + + wss.on('connection', (client) => client.pong()); }); }); describe('connection establishing', function () { it('can disconnect before connection is established', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('close', () => srv.close(done)); ws.terminate(); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('close', function () { - srv.close(); - done(); - }); }); }); it('can close before connection is established', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('close', () => srv.close(done)); ws.close(1001); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('close', function () { - srv.close(); - done(); - }); }); }); it('can handle error before request is upgraded', function (done) { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded - ++port; - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - let errorCallBackFired = false; - ws.on('error', function () { - errorCallBackFired = true; - }); - ws.on('close', function () { - setTimeout(function () { - assert.equal(true, errorCallBackFired); - assert.equal(ws.readyState, WebSocket.CLOSED); - done(); - }, 50); - }); + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => done()); }); it('invalid server key is denied', function (done) { - server.createServer(++port, server.handlers.invalidKey, function (srv) { + server.createServer(++port, server.handlers.invalidKey, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () { - srv.close(); - done(); - }); + + ws.on('error', () => srv.close(done)); }); }); it('close event is raised when server closes connection', function (done) { - server.createServer(++port, server.handlers.closeAfterConnect, function (srv) { + server.createServer(++port, server.handlers.closeAfterConnect, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('close', function () { - srv.close(); - done(); - }); + + ws.on('close', () => srv.close(done)); }); }); it('error is emitted if server aborts connection', function (done) { - server.createServer(++port, server.handlers.return401, function (srv) { + server.createServer(++port, server.handlers.return401, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('error', function () { - srv.close(); - done(); - }); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => srv.close(done)); }); }); it('unexpected response can be read when sent by server', function (done) { - server.createServer(++port, server.handlers.return401, function (srv) { + server.createServer(++port, server.handlers.return401, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('unexpected-response', function (req, res) { - assert.equal(res.statusCode, 401); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('unexpected-response', (req, res) => { + assert.strictEqual(res.statusCode, 401); let data = ''; - res.on('data', function (v) { + res.on('data', (v) => { data += v; }); - res.on('end', function () { - assert.equal(data, 'Not allowed!'); - srv.close(); - done(); + res.on('end', () => { + assert.strictEqual(data, 'Not allowed!'); + srv.close(done); }); }); - ws.on('error', function () { - assert.fail('error shouldnt be raised here'); - }); }); }); it('request can be aborted when unexpected response is sent by server', function (done) { - server.createServer(++port, server.handlers.return401, function (srv) { + server.createServer(++port, server.handlers.return401, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('unexpected-response', function (req, res) { - assert.equal(res.statusCode, 401); - res.on('end', function () { - srv.close(); - done(); - }); + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('unexpected-response', (req, res) => { + assert.strictEqual(res.statusCode, 401); + res.on('end', () => srv.close(done)); req.abort(); }); - ws.on('error', function () { - assert.fail('error shouldnt be raised here'); - }); }); }); }); describe('connection with query string', function () { it('connects when pathname is not null', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); - ws.on('open', function () { - wss.close(done); - }); + + ws.on('open', () => wss.close(done)); }); }); it('connects when pathname is null', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); - ws.on('open', function () { - wss.close(done); - }); + + ws.on('open', () => wss.close(done)); }); }); }); @@ -491,31 +421,37 @@ describe('WebSocket', function () { // to localhost can cause the test to succeed even when the stream pausing // isn't working as intended. that is an extremely unlikely scenario, though // and an acceptable risk for the test. - let client; - let serverClient; let openCount = 0; - function onOpen () { - if (++openCount === 2) { - let paused = true; - serverClient.on('message', function () { - assert.ok(!paused); - wss.close(); - done(); - }); - serverClient.pause(); - setTimeout(function () { - paused = false; - serverClient.resume(); - }, 200); - client.send('foo'); - } - } - const wss = new WebSocketServer({port: ++port}, function () { + let serverClient; + let client; + + const onOpen = () => { + if (++openCount !== 2) return; + + let paused = true; + serverClient.on('message', () => { + assert.ok(!paused); + wss.close(); + done(); + }); + serverClient.pause(); + + setTimeout(() => { + paused = false; + serverClient.resume(); + }, 200); + + client.send('foo'); + }; + + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); + serverClient = ws; serverClient.on('open', onOpen); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { client = ws; onOpen(); }); @@ -524,195 +460,190 @@ describe('WebSocket', function () { describe('#ping', function () { it('before connect should fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); + try { ws.ping(); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); it('before connect can silently fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); ws.ping('', {}, true); - srv.close(); + + srv.close(done); ws.terminate(); - done(); }); }); it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping(); - }); - srv.on('ping', function () { - srv.close(); + srv.on('ping', () => { + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.ping()); }); }); it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping('hi'); - }); - srv.on('ping', function (message) { - assert.equal('hi', message); - srv.close(); + server.createServer(++port, (srv) => { + srv.on('ping', (message) => { + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); - }); - }); - it('can send safely receive numbers as ping payload', function (done) { - server.createServer(++port, function (srv) { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping(200); - }); + ws.on('open', () => ws.ping('hi')); + }); + }); - srv.on('ping', function (message) { - assert.equal('200', message); - srv.close(); + it('can send safely receive numbers as ping payload', function (done) { + server.createServer(++port, (srv) => { + srv.on('ping', (message) => { + assert.strictEqual(message.toString(), '200'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.ping(200)); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping('hi', {mask: true}); - }); - srv.on('ping', function (message, flags) { + server.createServer(++port, (srv) => { + srv.on('ping', (message, flags) => { assert.ok(flags.masked); - assert.equal('hi', message); - srv.close(); + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.ping('hi', { mask: true })); }); }); }); describe('#pong', function () { - it('before connect should fail', function (done) { - server.createServer(++port, function (srv) { + it('before connect should fail', (done) => { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); + try { ws.pong(); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); it('before connect can silently fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); ws.pong('', {}, true); - srv.close(); + + srv.close(done); ws.terminate(); - done(); }); }); it('without message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.pong(); - }); - srv.on('pong', function () { - srv.close(); + server.createServer(++port, (srv) => { + srv.on('pong', () => { + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong()); }); }); it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.pong('hi'); - }); - srv.on('pong', function (message) { - assert.equal('hi', message); - srv.close(); + server.createServer(++port, (srv) => { + srv.on('pong', (message) => { + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong('hi')); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.pong('hi', {mask: true}); - }); - srv.on('pong', function (message, flags) { + server.createServer(++port, (srv) => { + srv.on('pong', (message, flags) => { assert.ok(flags.masked); - assert.equal('hi', message); - srv.close(); + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong('hi', { mask: true })); }); }); }); describe('#send', function () { - it('very long binary data can be sent and received (with echoing server)', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + it('very long binary data can be sent and received (with echoing server)', (done) => { + server.createServer(++port, (srv) => { const array = new Float32Array(5 * 1024 * 1024); - for (let i = 0; i < array.length; ++i) array[i] = i / 5; - ws.on('open', function () { - ws.send(array, {binary: true}); - }); - ws.on('message', function (message, flags) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 5; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array, { binary: true })); + ws.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); + assert.ok(message.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('can send and receive text data', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi'); - }); - ws.on('message', function (message, flags) { - assert.equal('hi', message); + + ws.on('open', () => ws.send('hi')); + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); @@ -736,17 +667,23 @@ describe('WebSocket', function () { }); it('send and receive binary data as an array', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(6); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + const partial = array.subarray(2, 5); - ws.on('open', function () { - ws.send(partial, {binary: true}); - }); - ws.on('message', function (message, flags) { + const buf = Buffer.from(partial.buffer) + .slice(partial.byteOffset, partial.byteOffset + partial.byteLength); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(partial, { binary: true })); + ws.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(partial, new Float32Array(getArrayBuffer(message)))); + assert.ok(message.equals(buf)); ws.terminate(); srv.close(); done(); @@ -755,366 +692,428 @@ describe('WebSocket', function () { }); it('binary data can be sent and received as buffer', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { + const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${port}`); - const buf = new Buffer('foobar'); - ws.on('open', function () { - ws.send(buf, {binary: true}); - }); - ws.on('message', function (message, flags) { + + ws.on('open', () => ws.send(buf, { binary: true })); + ws.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(buf, message)); + assert.ok(message.equals(buf)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('ArrayBuffer is auto-detected without binary flag', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(5); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function () { - ws.send(array.buffer); - }); - ws.onmessage = function (event) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array.buffer)); + ws.onmessage = (event) => { assert.ok(event.binary); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(event.data)))); + assert.ok(event.data.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('Buffer is auto-detected without binary flag', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { + const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${port}`); - const buf = new Buffer('foobar'); - ws.on('open', function () { - ws.send(buf); - }); - ws.onmessage = function (event) { + + ws.on('open', () => ws.send(buf)); + + ws.onmessage = (event) => { assert.ok(event.binary); - assert.ok(areArraysEqual(event.data, buf)); + assert.ok(event.data.equals(buf)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('before connect should fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); + try { ws.send('hi'); } catch (e) { + srv.close(done); ws.terminate(); - srv.close(); - done(); } }); }); it('before connect should pass error through callback, if present', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); - ws.send('hi', function (error) { + + ws.send('hi', (error) => { assert.ok(error instanceof Error); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('without data should be successful', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send(); - }); - srv.on('message', function (message, flags) { - assert.equal('', message); - srv.close(); + + ws.on('open', () => ws.send()); + + srv.on('message', (message, flags) => { + assert.strictEqual(message, ''); + srv.close(done); ws.terminate(); - done(); }); }); }); it('calls optional callback when flushed', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi', function () { - srv.close(); + + ws.on('open', () => { + ws.send('hi', () => { + srv.close(done); ws.terminate(); - done(); }); }); }); }); it('with unencoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi'); - }); - srv.on('message', function (message, flags) { - assert.equal('hi', message); - srv.close(); + + ws.on('open', () => ws.send('hi')); + + srv.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi', {mask: true}); - }); - srv.on('message', function (message, flags) { + + ws.on('open', () => ws.send('hi', { mask: true })); + + srv.on('message', (message, flags) => { assert.ok(flags.masked); - assert.equal('hi', message); - srv.close(); + assert.strictEqual(message, 'hi'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with unencoded binary message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(5); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function () { - ws.send(array, {binary: true}); - }); - srv.on('message', function (message, flags) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array, { binary: true })); + + srv.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - srv.close(); + assert.ok(message.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with encoded binary message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(5); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function () { - ws.send(array, {mask: true, binary: true}); - }); - srv.on('message', function (message, flags) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array, { mask: true, binary: true })); + + srv.on('message', (message, flags) => { assert.ok(flags.binary); assert.ok(flags.masked); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - srv.close(); + assert.ok(message.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with binary stream will send fragmented data', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let callbackFired = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true}, function (error) { - assert.equal(null, error); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100 + }); + + ws.send(fileStream, { binary: true }, (error) => { + assert.ifError(error); callbackFired = true; }); }); - srv.on('message', function (data, flags) { - assert.ok(flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); - ws.terminate(); - }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(callbackFired); - srv.close(); - done(); + srv.close(done); + }); + + srv.on('message', (data, flags) => { + assert.ok(flags.binary); + assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); + + ws.close(); }); }); }); it('with text stream will send fragmented data', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let callbackFired = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, {binary: false}, function (error) { - assert.equal(null, error); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + + ws.send(fileStream, { binary: false }, (error) => { + assert.ifError(error); callbackFired = true; }); }); - srv.on('message', function (data, flags) { - assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - ws.terminate(); - }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(callbackFired); - srv.close(); - done(); + srv.close(done); + }); + + srv.on('message', (data, flags) => { + assert.ok(!flags.binary); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); + + ws.close(); }); }); }); it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); ws.send('foobar'); ws.send('baz'); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); + assert.strictEqual(data, 'foobar'); } else { assert.ok(!flags.binary); - assert.equal('baz', data); - srv.close(); + assert.strictEqual(data, 'baz'); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); + let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + ws.stream((error, send) => { + assert.ifError(error); + if (++i === 1) send('foo'); else send('bar', true); }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); - srv.close(); + assert.strictEqual(data, 'foobar'); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); ws.ping('foobar'); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); - srv.on('ping', function (data) { - assert.equal('foobar', data); + + srv.on('ping', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); ws.pong('foobar'); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); if (++receivedIndex === 2) { - srv.close(); - ws.terminate(); - done(); + srv.close(done); + ws.close(); } }); - srv.on('pong', function (data) { - assert.equal('foobar', data); + + srv.on('pong', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); ws.send(fileStream); ws.close(1000, 'foobar'); }); - ws.on('close', function () { - srv.close(); - ws.terminate(); - done(); + + ws.on('close', () => srv.close(done)); + ws.on('error', () => { + // That's quite alright -- a send was attempted after close }); - ws.on('error', function () { /* That's quite alright -- a send was attempted after close */ }); - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); }); - srv.on('close', function (code, data) { - assert.equal(1000, code); - assert.equal('foobar', data); + + srv.on('close', (code, data) => { + assert.strictEqual(code, 1000); + assert.strictEqual(data, 'foobar'); }); }); }); @@ -1122,70 +1121,79 @@ describe('WebSocket', function () { describe('#stream', function () { it('very long binary data can be streamed', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const buffer = new Buffer(10 * 1024); - for (let i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; - ws.on('open', function () { - let i = 0; - const blockSize = 800; + + for (let i = 0; i < buffer.length; ++i) { + buffer[i] = i % 0xff; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { const bufLen = buffer.length; - ws.stream({binary: true}, function (error, send) { - assert.ok(!error); + const blockSize = 800; + let i = 0; + + ws.stream({ binary: true }, (error, send) => { + assert.ifError(error); + const start = i * blockSize; const toSend = Math.min(blockSize, bufLen - (i * blockSize)); const end = start + toSend; const isFinal = toSend < blockSize; + send(buffer.slice(start, end), isFinal); i += 1; }); }); - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(buffer, data)); + assert.ok(data.equals(buffer)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('before connect should pass error through callback', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); - ws.stream(function (error) { + + ws.stream((error) => { assert.ok(error instanceof Error); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('without callback should fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { + + ws.on('open', () => { try { ws.stream(); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); ws.send('foobar'); @@ -1195,64 +1203,69 @@ describe('WebSocket', function () { } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); + assert.strictEqual(data, 'foobar'); } else { assert.ok(!flags.binary); - assert.equal('baz', data); - srv.close(); + assert.strictEqual(data, 'baz'); + srv.close(done); ws.terminate(); - done(); } }); }); }); - it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + it('will cause intermittent stream to be delayed in order', function (done) { + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); + let i2 = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i2 === 1) send('foo'); else send('bar', true); }); + ws.send('baz'); } else { send(payload.substr(5, 5), true); } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); + assert.strictEqual(data, 'foobar'); } else if (receivedIndex === 3) { assert.ok(!flags.binary); - assert.equal('baz', data); - setTimeout(function () { - srv.close(); + assert.strictEqual(data, 'baz'); + setTimeout(() => { + srv.close(done); ws.terminate(); - done(); }, 1000); } else { throw new Error('more messages than we actually sent just arrived'); @@ -1262,13 +1275,15 @@ describe('WebSocket', function () { }); it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); ws.ping('foobar'); @@ -1277,35 +1292,38 @@ describe('WebSocket', function () { } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); - srv.on('ping', function (data) { - assert.equal('foobar', data); + + srv.on('ping', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); ws.pong('foobar'); @@ -1314,35 +1332,38 @@ describe('WebSocket', function () { } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); - srv.on('pong', function (data) { - assert.equal('foobar', data); + + srv.on('pong', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); let errorGiven = false; - ws.on('open', function () { + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { + + ws.stream((error, send) => { if (++i === 1) { send(payload.substr(0, 5)); ws.close(1000, 'foobar'); @@ -1354,19 +1375,21 @@ describe('WebSocket', function () { } }); }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(errorGiven); - srv.close(); + srv.close(done); ws.terminate(); - done(); }); - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); }); - srv.on('close', function (code, data) { - assert.equal(1000, code); - assert.equal('foobar', data); + + srv.on('close', (code, data) => { + assert.strictEqual(code, 1000); + assert.strictEqual(data.toString(), 'foobar'); }); }); }); @@ -1374,375 +1397,356 @@ describe('WebSocket', function () { describe('#close', function () { it('will raise error callback, if any, if called during send stream', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let errorGiven = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, function (error) { - errorGiven = error != null; + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + + ws.send(fileStream, (error) => { + errorGiven = !!error; }); ws.close(1000, 'foobar'); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { assert.ok(errorGiven); - srv.close(); - ws.terminate(); - done(); + srv.close(done); }, 1000); }); }); }); it('without invalid first argument throws exception', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { + + ws.on('open', () => { try { ws.close('error'); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('without reserved error code 1004 throws exception', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { + + ws.on('open', () => { try { ws.close(1004); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('without message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.close(1000); - }); - srv.on('close', function (code, message) { - assert.equal('', message); - srv.close(); + + ws.on('open', () => ws.close(1000)); + + srv.on('close', (code, message) => { + assert.strictEqual(message, ''); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.close(1000, 'some reason'); - }); - srv.on('close', function (code, message, flags) { + + ws.on('open', () => ws.close(1000, 'some reason')); + + srv.on('close', (code, message, flags) => { assert.ok(flags.masked); - assert.equal('some reason', message); - srv.close(); + assert.strictEqual(message, 'some reason'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.close(1000, 'some reason', {mask: true}); - }); - srv.on('close', function (code, message, flags) { + + ws.on('open', () => ws.close(1000, 'some reason', { mask: true })); + + srv.on('close', (code, message, flags) => { assert.ok(flags.masked); - assert.equal('some reason', message); - srv.close(); + assert.strictEqual(message, 'some reason'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('ends connection to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let connectedOnce = false; - ws.on('open', function () { + + ws.on('open', () => { connectedOnce = true; ws.close(1000, 'some reason', {mask: true}); }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(connectedOnce); - srv.close(); + srv.close(done); ws.terminate(); - done(); }); }); }); it('consumes all data when the server socket closed', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { - wss.on('connection', function (conn) { + const wss = new WebSocketServer({ port: ++port }, () => { + wss.on('connection', (conn) => { conn.send('foo'); conn.send('bar'); conn.send('baz'); conn.close(); }); + const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; - ws.on('message', function (message) { + + ws.on('message', (message) => { messages.push(message); if (messages.length === 3) { - assert.deepEqual(messages, ['foo', 'bar', 'baz']); - wss.close(); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); + + wss.close(done); ws.terminate(); - done(); } }); }); }); it('allows close code 1013', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('close', function (code) { + + ws.on('close', (code) => { assert.strictEqual(code, 1013); wss.close(done); }); }); - wss.on('connection', function (ws) { - ws.close(1013); - }); + wss.on('connection', (ws) => ws.close(1013)); }); }); describe('W3C API emulation', function () { it('should not throw errors when getting and setting', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - const listener = function () {}; + const listener = () => {}; ws.onmessage = listener; ws.onerror = listener; ws.onclose = listener; ws.onopen = listener; - assert.ok(ws.binaryType === 'nodebuffer'); + assert.strictEqual(ws.binaryType, 'nodebuffer'); ws.binaryType = 'arraybuffer'; - assert.ok(ws.binaryType === 'arraybuffer'); + assert.strictEqual(ws.binaryType, 'arraybuffer'); - assert.ok(ws.onopen === listener); - assert.ok(ws.onmessage === listener); - assert.ok(ws.onclose === listener); - assert.ok(ws.onerror === listener); + assert.strictEqual(ws.onopen, listener); + assert.strictEqual(ws.onmessage, listener); + assert.strictEqual(ws.onclose, listener); + assert.strictEqual(ws.onerror, listener); - srv.close(); + srv.close(done); ws.terminate(); - done(); }); }); it('should work the same as the EventEmitter api', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let message = 0; let close = 0; let open = 0; - ws.onmessage = function (messageEvent) { - assert.ok(!!messageEvent.data); + ws.onmessage = (messageEvent) => { + assert.strictEqual(messageEvent.data, 'foo'); ++message; ws.close(); }; - ws.onopen = function () { - ++open; - }; - - ws.onclose = function () { - ++close; - }; - - ws.on('open', function () { - ws.send('foo'); - }); + ws.onopen = () => ++open; + ws.onclose = () => ++close; - ws.on('close', function () { - process.nextTick(function () { - assert.ok(message === 1); - assert.ok(open === 1); - assert.ok(close === 1); + ws.on('open', () => ws.send('foo')); - srv.close(); - ws.terminate(); - done(); - }); + ws.on('close', () => { + assert.strictEqual(message, 1); + assert.strictEqual(open, 1); + assert.strictEqual(close, 1); + srv.close(done); + ws.terminate(); }); }); }); it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('open', function () { - ws.send('hi'); - }); - ws.addEventListener('message', function (messageEvent) { - assert.equal('hi', messageEvent.data); + + ws.addEventListener('open', () => ws.send('hi')); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.data, 'hi'); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('close', function (closeEvent) { - assert.equal(true, closeEvent.wasClean); - assert.equal(1000, closeEvent.code); - ws.terminate(); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(closeEvent.wasClean); + assert.strictEqual(closeEvent.code, 1000); + wss.close(); done(); }); }); - wss.on('connection', function (client) { - client.close(1000); - }); + + wss.on('connection', (client) => client.close(1000)); }); it('should receive valid CloseEvent when server closes with code 1001', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('close', function (closeEvent) { - assert.equal(false, closeEvent.wasClean); - assert.equal(1001, closeEvent.code); - assert.equal('some daft reason', closeEvent.reason); - ws.terminate(); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(!closeEvent.wasClean); + assert.strictEqual(closeEvent.code, 1001); + assert.strictEqual(closeEvent.reason, 'some daft reason'); + wss.close(); done(); }); }); - wss.on('connection', function (client) { - client.close(1001, 'some daft reason'); - }); + + wss.on('connection', (client) => client.close(1001, 'some daft reason')); }); it('should have target set on Events', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('open', function (openEvent) { - assert.equal(ws, openEvent.target); + + ws.addEventListener('open', (openEvent) => { + assert.strictEqual(openEvent.target, ws); }); - ws.addEventListener('message', function (messageEvent) { - assert.equal(ws, messageEvent.target); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.target, ws); wss.close(); }); - ws.addEventListener('close', function (closeEvent) { - assert.equal(ws, closeEvent.target); + ws.addEventListener('close', (closeEvent) => { + assert.strictEqual(closeEvent.target, ws); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function (errorEvent) { - assert.equal(errorEvent.message, 'forced'); - assert.equal(ws, errorEvent.target); - ws.terminate(); + ws.addEventListener('error', (errorEvent) => { + assert.strictEqual(errorEvent.message, 'forced'); + assert.strictEqual(errorEvent.target, ws); + done(); }); }); - wss.on('connection', function (client) { - client.send('hi'); - }); + + wss.on('connection', (client) => client.send('hi')); }); it('should have type set on Events', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('open', function (openEvent) { - assert.equal('open', openEvent.type); + + ws.addEventListener('open', (openEvent) => { + assert.strictEqual(openEvent.type, 'open'); }); - ws.addEventListener('message', function (messageEvent) { - assert.equal('message', messageEvent.type); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.type, 'message'); wss.close(); }); - ws.addEventListener('close', function (closeEvent) { - assert.equal('close', closeEvent.type); + ws.addEventListener('close', (closeEvent) => { + assert.strictEqual(closeEvent.type, 'close'); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function (errorEvent) { - assert.equal(errorEvent.message, 'forced'); - assert.equal('error', errorEvent.type); - ws.terminate(); + ws.addEventListener('error', (errorEvent) => { + assert.strictEqual(errorEvent.message, 'forced'); + assert.strictEqual(errorEvent.type, 'error'); + done(); }); }); - wss.on('connection', function (client) { - client.send('hi'); - }); + + wss.on('connection', (client) => client.send('hi')); }); it('should pass binary data as a node.js Buffer by default', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Uint8Array(4096); + const ws = new WebSocket(`ws://localhost:${port}`); - ws.onopen = function () { - ws.send(array, {binary: true}); - }; - ws.onmessage = function (messageEvent) { + ws.onopen = () => ws.send(array, { binary: true }); + ws.onmessage = (messageEvent) => { assert.ok(messageEvent.binary); - assert.ok(ws.binaryType === 'nodebuffer'); + assert.strictEqual(ws.binaryType, 'nodebuffer'); assert.ok(messageEvent.data instanceof Buffer); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { + const array = new Uint8Array(4096); const ws = new WebSocket(`ws://localhost:${port}`); + ws.binaryType = 'arraybuffer'; - const array = new Uint8Array(4096); - ws.onopen = function () { - ws.send(array, {binary: true}); - }; - ws.onmessage = function (messageEvent) { + ws.onopen = () => ws.send(array, { binary: true }); + ws.onmessage = (messageEvent) => { assert.ok(messageEvent.binary); assert.ok(messageEvent.data instanceof ArrayBuffer); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('should ignore binaryType for text messages', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; - ws.onopen = function () { - ws.send('foobar'); - }; - ws.onmessage = function (messageEvent) { + ws.onopen = () => ws.send('foobar'); + ws.onmessage = (messageEvent) => { assert.ok(!messageEvent.binary); - assert.ok(typeof messageEvent.data === 'string'); + assert.strictEqual(typeof messageEvent.data, 'string'); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); @@ -1855,6 +1859,7 @@ describe('WebSocket', function () { ws.on('message', (message, flags) => { assert.strictEqual(flags.binary, true); assert.ok(buf.equals(message)); + server.close(done); wss.close(); }); @@ -1869,6 +1874,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); + wss.on('connection', (client) => { assert.strictEqual(client.supports.binary, true); wss.close(); @@ -1911,10 +1917,10 @@ describe('WebSocket', function () { it('honors origin set in options', function (done) { const server = http.createServer(); - server.listen(++port, function () { + server.listen(++port, () => { const options = { origin: 'https://example.com:8000' }; - server.on('upgrade', function (req, socket, head) { + server.on('upgrade', (req, socket, head) => { assert.strictEqual(req.headers['origin'], options.origin); server.close(done); socket.destroy(); @@ -1929,52 +1935,71 @@ describe('WebSocket', function () { // so we need to expose the method that does this const buildHostHeader = WebSocket.buildHostHeader; let host = buildHostHeader(false, 'localhost', 80); - assert.equal('localhost', host); + assert.strictEqual(host, 'localhost'); host = buildHostHeader(false, 'localhost', 88); - assert.equal('localhost:88', host); + assert.strictEqual(host, 'localhost:88'); host = buildHostHeader(true, 'localhost', 443); - assert.equal('localhost', host); + assert.strictEqual(host, 'localhost'); host = buildHostHeader(true, 'localhost', 8443); - assert.equal('localhost:8443', host); + assert.strictEqual(host, 'localhost:8443'); }); }); describe('permessage-deflate', function () { - it('is enabled by default', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function () { + it('is enabled by default', (done) => { + const server = http.createServer(); + const wss = new WebSocketServer({ server, perMessageDeflate: true }); + + server.on('upgrade', (req, socket, head) => { + assert.ok(req.headers['sec-websocket-extensions'].includes('permessage-deflate')); + }); + + server.listen(++port, () => { const ws = new WebSocket(`ws://localhost:${port}`); - srv.on('upgrade', function (req, socket, head) { - assert.ok(~req.headers['sec-websocket-extensions'].indexOf('permessage-deflate')); - }); - ws.on('open', function () { + + ws.on('open', () => { assert.ok(ws.extensions['permessage-deflate']); - ws.terminate(); + server.close(done); wss.close(); - done(); }); }); }); it('can be disabled', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); - srv.on('upgrade', function (req, socket, head) { - assert.ok(!req.headers['sec-websocket-extensions']); - ws.terminate(); + const server = http.createServer(); + const wss = new WebSocketServer({ server, perMessageDeflate: true }); + + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['sec-websocket-extensions'], undefined); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: false + }); + + ws.on('open', () => { + server.close(done); wss.close(); - done(); }); }); }); it('can send extension parameters', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function () { + const server = http.createServer(); + const wss = new WebSocketServer({ server, perMessageDeflate: true }); + + server.on('upgrade', (req, socket, head) => { + const extensions = req.headers['sec-websocket-extensions']; + + assert.notStrictEqual(extensions.indexOf('permessage-deflate'), -1); + assert.notStrictEqual(extensions.indexOf('server_no_context_takeover'), -1); + assert.notStrictEqual(extensions.indexOf('client_no_context_takeover'), -1); + assert.notStrictEqual(extensions.indexOf('server_max_window_bits=10'), -1); + assert.notStrictEqual(extensions.indexOf('client_max_window_bits'), -1); + }); + + server.listen(++port, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { serverNoContextTakeover: true, @@ -1983,148 +2008,181 @@ describe('WebSocket', function () { clientMaxWindowBits: true } }); - srv.on('upgrade', function (req, socket, head) { - const extensions = req.headers['sec-websocket-extensions']; - assert.ok(~extensions.indexOf('permessage-deflate')); - assert.ok(~extensions.indexOf('server_no_context_takeover')); - assert.ok(~extensions.indexOf('client_no_context_takeover')); - assert.ok(~extensions.indexOf('server_max_window_bits=10')); - assert.ok(~extensions.indexOf('client_max_window_bits')); - ws.terminate(); + + ws.on('open', () => { + server.close(done); wss.close(); - done(); }); }); }); it('can send and receive text data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - ws.on('open', function () { - ws.send('hi', {compress: true}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true }); - ws.on('message', function (message, flags) { - assert.equal('hi', message); - ws.terminate(); + + ws.on('open', () => ws.send('hi', { compress: true })); + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); it('can send and receive a typed array', function (done) { const array = new Float32Array(5); - for (let i = 0; i < array.length; i++) array[i] = i / 2; - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - ws.on('open', function () { - ws.send(array, {compress: true}); - }); - ws.on('message', function (message, flags) { - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - ws.terminate(); + + for (let i = 0; i < array.length; i++) { + array[i] = i / 2; + } + + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + + ws.on('open', () => ws.send(array, { compress: true })); + ws.on('message', (message, flags) => { + assert.ok(message.equals(Buffer.from(array.buffer))); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); it('can send and receive ArrayBuffer', function (done) { const array = new Float32Array(5); - for (let i = 0; i < array.length; i++) array[i] = i / 2; - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - ws.on('open', function () { - ws.send(array.buffer, {compress: true}); - }); - ws.on('message', function (message, flags) { - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - ws.terminate(); + + for (let i = 0; i < array.length; i++) { + array[i] = i / 2; + } + + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + + ws.on('open', () => ws.send(array.buffer, { compress: true })); + ws.on('message', (message, flags) => { + assert.ok(message.equals(Buffer.from(array.buffer))); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); it('with binary stream will send fragmented data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + let callbackFired = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true, compress: true}, function (error) { - assert.equal(null, error); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100 + }); + + ws.send(fileStream, { binary: true, compress: true }, (error) => { + assert.ifError(error); callbackFired = true; }); }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(callbackFired); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (data, flags) { + + wss.on('connection', (ws) => { + ws.on('message', (data, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); - ws.terminate(); + assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); + ws.close(); }); }); }); describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); - ws.on('open', function () { - ws.send('hi', {compress: true}); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: false }); - ws.on('message', function (message, flags) { - assert.equal('hi', message); - ws.terminate(); + + ws.on('open', () => ws.send('hi', { compress: true })); + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); }); describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); let errorGiven = false; - ws.on('open', function () { - ws.send('hi', function (error) { - errorGiven = error != null; + + ws.on('open', () => { + ws.send('hi', (error) => { + errorGiven = !!error; }); ws.close(); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { assert.ok(!errorGiven); wss.close(); - ws.terminate(); done(); }, 1000); }); @@ -2134,20 +2192,26 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: { threshold: 0 }}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: { threshold: 0 } + }); let errorGiven = false; - ws.on('open', function () { - ws.send('hi', function (error) { - errorGiven = error != null; + + ws.on('open', () => { + ws.send('hi', (error) => { + errorGiven = !!error; }); ws.terminate(); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { assert.ok(errorGiven); wss.close(); - ws.terminate(); done(); }, 1000); }); @@ -2155,18 +2219,23 @@ describe('WebSocket', function () { }); it('can call during receiving data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - wss.on('connection', function (client) { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + + wss.on('connection', (client) => { for (let i = 0; i < 10; i++) { client.send('hi'); } - client.send('hi', function () { - ws.terminate(); - }); + client.send('hi', () => ws.terminate()); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { wss.close(); done(); }, 1000); diff --git a/test/testserver.js b/test/testserver.js index 4f03b79c8..bc94071ca 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,183 +1,165 @@ 'use strict'; +const EventEmitter = require('events'); const crypto = require('crypto'); -const events = require('events'); const http = require('http'); -const util = require('util'); const Receiver = require('../lib/Receiver'); const Sender = require('../lib/Sender'); module.exports = { handlers: { - valid: validServer, - invalidKey: invalidRequestHandler, closeAfterConnect: closeAfterConnectHandler, - return401: return401 + invalidKey: invalidRequestHandler, + return401: return401, + valid: validServer }, - createServer: function (port, handler, cb) { + createServer: (port, handler, cb) => { if (handler && !cb) { cb = handler; handler = null; } - const webServer = http.createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('okay'); - }); + + const webServer = http.createServer(); const srv = new Server(webServer); - webServer.on('upgrade', function (req, socket) { + + webServer.on('upgrade', (req, socket) => { webServer._socket = socket; (handler || validServer)(srv, req, socket); }); - webServer.listen(port, '127.0.0.1', function () { cb(srv); }); + + webServer.listen(port, '127.0.0.1', () => cb(srv)); } }; -/** - * Test strategies - */ - function validServer (server, req, socket) { - if (typeof req.headers.upgrade === 'undefined' || - req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { throw new Error('invalid headers'); } if (!req.headers['sec-websocket-key']) { - socket.end(); throw new Error('websocket key is missing'); } // calc key - let key = req.headers['sec-websocket-key']; - const shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - key = shasum.digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key - ]; - - socket.write(headers.concat('', '').join('\r\n')); + const key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .digest('base64'); + socket.setTimeout(0); socket.setNoDelay(true); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept:${key}\r\n` + + '\r\n' + ); + const sender = new Sender(socket); const receiver = new Receiver(); - receiver.ontext = function (message, flags) { + + receiver.ontext = (message, flags) => { server.emit('message', message, flags); sender.send(message); }; - receiver.onbinary = function (message, flags) { + receiver.onbinary = (message, flags) => { flags = flags || {}; flags.binary = true; server.emit('message', message, flags); - sender.send(message, {binary: true}); + sender.send(message, { binary: true }); }; - receiver.onping = function (message, flags) { + receiver.onping = (message, flags) => { flags = flags || {}; server.emit('ping', message, flags); }; - receiver.onpong = function (message, flags) { + receiver.onpong = (message, flags) => { flags = flags || {}; server.emit('pong', message, flags); }; - receiver.onclose = function (code, message, flags) { + receiver.onclose = (code, message, flags) => { flags = flags || {}; - sender.close(code, message, false, function () { + sender.close(code, message, false, () => { server.emit('close', code, message, flags); socket.end(); }); }; - socket.on('data', function (data) { - receiver.add(data); - }); - socket.on('end', function () { - socket.end(); - }); + socket.on('data', (data) => receiver.add(data)); + socket.on('end', () => socket.end()); } function invalidRequestHandler (server, req, socket) { - if (typeof req.headers.upgrade === 'undefined' || - req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { throw new Error('invalid headers'); } if (!req.headers['sec-websocket-key']) { - socket.end(); throw new Error('websocket key is missing'); } // calc key - let key = req.headers['sec-websocket-key']; - const shasum = crypto.createHash('sha1'); - shasum.update(key + 'bogus', 'binary'); - key = shasum.digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key - ]; - - socket.write(headers.concat('', '').join('\r\n')); + const key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}bogus`, 'latin1') + .digest('base64'); + + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept:${key}\r\n` + + '\r\n' + ); socket.end(); } function closeAfterConnectHandler (server, req, socket) { - if (typeof req.headers.upgrade === 'undefined' || - req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { throw new Error('invalid headers'); } if (!req.headers['sec-websocket-key']) { - socket.end(); throw new Error('websocket key is missing'); } // calc key - let key = req.headers['sec-websocket-key']; - const shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - key = shasum.digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key - ]; - - socket.write(headers.concat('', '').join('\r\n')); + const key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .digest('base64'); + + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept:${key}\r\n` + + '\r\n' + ); socket.end(); } function return401 (server, req, socket) { - const headers = [ - 'HTTP/1.1 401 Unauthorized', - 'Content-type: text/html' - ]; - - socket.write(headers.concat('', '').join('\r\n')); - socket.write('Not allowed!'); + socket.write( + `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + 'Content-Length: 12\r\n' + + '\r\n' + + 'Not allowed!' + ); socket.end(); } /** * Server object, which will do the actual emitting */ +class Server extends EventEmitter { + constructor (webServer) { + super(); + this.webServer = webServer; + } -function Server (webServer) { - this.webServer = webServer; + close (cb) { + this.webServer.close(cb); + if (this._socket) this._socket.end(); + } } - -util.inherits(Server, events.EventEmitter); - -Server.prototype.close = function () { - this.webServer.close(); - if (this._socket) this._socket.end(); -}; From 36e60fcc93b524369cdb0bc5d8d1a780e7e01f64 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 10:33:45 +0200 Subject: [PATCH 099/489] [test] Don't use the NODE_TLS_REJECT_UNAUTHORIZED env variable --- test/WebSocket.test.js | 15 +++++++++++---- test/WebSocketServer.test.js | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 888e35d60..52612c533 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1765,7 +1765,9 @@ describe('WebSocket', function () { server.close(done); }); - server.listen(++port, () => new WebSocket('wss://localhost:' + port)); + server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + })); }); it('can connect to secure websocket server with client side certificate', function (done) { @@ -1794,7 +1796,8 @@ describe('WebSocket', function () { server.listen(++port, () => { const ws = new WebSocket(`wss://localhost:${port}`, { cert: fs.readFileSync('test/fixtures/agent1-cert.pem'), - key: fs.readFileSync('test/fixtures/agent1-key.pem') + key: fs.readFileSync('test/fixtures/agent1-key.pem'), + rejectUnauthorized: false }); }); }); @@ -1834,7 +1837,9 @@ describe('WebSocket', function () { }); server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`); + const ws = new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + }); ws.on('open', () => ws.send('foobar')); }); @@ -1853,7 +1858,9 @@ describe('WebSocket', function () { }); server.listen(++port, () => { - const ws = new WebSocket('wss://localhost:' + port); + const ws = new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + }); ws.on('open', () => ws.send(buf)); ws.on('message', (message, flags) => { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 9d035c375..b74a28afd 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -657,9 +657,9 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => { - const ws = new WebSocket('wss://localhost:' + port); - }); + server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + })); }); it('verifyClient has secure:false for non-ssl connections', function (done) { From 53f033acea1ff6fe5b836f9a9f5ddc1b41b067de Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 10:38:32 +0200 Subject: [PATCH 100/489] [minor] Remove Makefile in favor of npm scripts --- .travis.yml | 2 ++ Makefile | 48 ------------------------------------------------ package.json | 6 +++++- 3 files changed, 7 insertions(+), 49 deletions(-) delete mode 100644 Makefile diff --git a/.travis.yml b/.travis.yml index 4b92d55ae..1891cb08d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,5 @@ node_js: - "7" - "6" - "4" +script: + - "npm run test-travis" diff --git a/Makefile b/Makefile deleted file mode 100644 index 2ae3d0fbe..000000000 --- a/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -ALL_TESTS = $(shell find test -name '*.test.js') -ALL_INTEGRATION = $(shell find test -name '*.integration.js') - -lint: - @./node_modules/.bin/eslint . - -run-tests: - @./node_modules/.bin/mocha \ - -t 5000 \ - -s 2400 \ - $(TESTFLAGS) \ - $(TESTS) - -run-integrationtests: - @./node_modules/.bin/mocha \ - -t 5000 \ - -s 6000 \ - $(TESTFLAGS) \ - $(TESTS) - -run-coverage: - @./node_modules/.bin/istanbul cover --report html \ - ./node_modules/.bin/_mocha -- \ - -t 5000 \ - -s 6000 \ - $(TESTFLAGS) \ - $(TESTS) - -test: lint - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-tests - -integrationtest: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_INTEGRATION)" run-integrationtests - -coverage: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-coverage - -benchmark: - @node bench/sender.benchmark.js - @node bench/parser.benchmark.js - -autobahn: - @NODE_PATH=lib node test/autobahn.js - -autobahn-server: - @NODE_PATH=lib node test/autobahn-server.js - -.PHONY: test coverage diff --git a/package.json b/package.json index 8d33af457..939c7de8c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,11 @@ "url": "git://github.com/websockets/ws.git" }, "scripts": { - "test": "make test" + "test-travis": "npm run lint && istanbul cover _mocha --report lcovonly -- test/*.test.js", + "coverage": "istanbul cover _mocha --report html -- test/*.test.js", + "integration": "npm run lint && mocha test/*.integration.js", + "test": "npm run lint && mocha test/*.test.js", + "lint": "eslint ." }, "dependencies": { "ultron": "1.0.x" From 31c05b01cc57d4e688b7b79a59ef22931c5b0b42 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 12:21:00 +0200 Subject: [PATCH 101/489] [test] Add secure WebSocket (wss) integration test --- test/WebSocket.integration.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index 237ac5cfc..90d5adcfb 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -5,16 +5,37 @@ const assert = require('assert'); const WebSocket = require('..'); describe('WebSocket', function () { - it('communicates successfully with echo service', function (done) { + it('communicates successfully with echo service (ws)', function (done) { const ws = new WebSocket('ws://echo.websocket.org/', { - origin: 'http://websocket.org', + origin: 'ws://echo.websocket.org', protocolVersion: 13 }); const str = Date.now().toString(); let dataReceived = false; - ws.on('open', () => ws.send(str, { mask: true })); + ws.on('open', () => ws.send(str)); + ws.on('close', () => { + assert.ok(dataReceived); + done(); + }); + ws.on('message', (data) => { + dataReceived = true; + assert.strictEqual(data, str); + ws.close(); + }); + }); + + it('communicates successfully with echo service (wss)', function (done) { + const ws = new WebSocket('wss://echo.websocket.org/', { + origin: 'wss://echo.websocket.org', + protocolVersion: 13 + }); + const str = Date.now().toString(); + + let dataReceived = false; + + ws.on('open', () => ws.send(str)); ws.on('close', () => { assert.ok(dataReceived); done(); From d4ac814f8613e2931f39bd4fbd4516d8d56118b5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 12:31:38 +0200 Subject: [PATCH 102/489] [ignore] Clean up .gitignore and .npmignore --- .gitignore | 9 ++------- .npmignore | 17 ++++++----------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 182c7b910..4b173c692 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,3 @@ +node_modules/ +coverage/ npm-debug.log -node_modules -.*.swp -.lock-* -build -coverage - -builderror.log diff --git a/.npmignore b/.npmignore index 1eba800f8..e0e4dee67 100644 --- a/.npmignore +++ b/.npmignore @@ -1,11 +1,6 @@ -npm-debug.log -node_modules -.*.swp -.lock-* -build - -bench -doc -examples -test - +coverage/ +examples/ +bench/ +test/ +doc/ +.* From 5a025dcb730427be1d696718003784b33babd696 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 15:14:24 +0200 Subject: [PATCH 103/489] [minor] Remove unnecessary `typeof` checks --- lib/Extensions.js | 2 +- lib/Receiver.js | 2 +- lib/WebSocket.js | 44 +++++++++++++++++++----------------------- lib/WebSocketServer.js | 10 ++++------ 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 574677129..036c71382 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -26,7 +26,7 @@ function parse (value) { var parts = param.trim().split('='); var key = parts[0]; var value = parts[1]; - if (typeof value === 'undefined') { + if (value === undefined) { value = true; } else { // unquote value diff --git a/lib/Receiver.js b/lib/Receiver.js index c5c527729..604ea8fc6 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -201,7 +201,7 @@ class Receiver { } } const handler = opcodes[this.state.opcode]; - if (typeof handler === 'undefined') { + if (handler === undefined) { this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); } else { handler.start(this, data); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index e0eead112..511a63f9d 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -162,7 +162,7 @@ WebSocket.prototype.ping = function ping (data, options, dontFailWhenClosed) { options = options || {}; - if (typeof options.mask === 'undefined') options.mask = !this._isServer; + if (options.mask === undefined) options.mask = !this._isServer; this._sender.ping(data, options); }; @@ -183,7 +183,7 @@ WebSocket.prototype.pong = function (data, options, dontFailWhenClosed) { options = options || {}; - if (typeof options.mask === 'undefined') options.mask = !this._isServer; + if (options.mask === undefined) options.mask = !this._isServer; this._sender.pong(data, options); }; @@ -215,7 +215,7 @@ WebSocket.prototype.send = function send (data, options, cb) { } if (this.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else throw new Error('not opened'); return; } @@ -230,27 +230,23 @@ WebSocket.prototype.send = function send (data, options, cb) { options = options || {}; if (options.fin !== false) options.fin = true; - if (typeof options.binary === 'undefined') { - options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || - ArrayBuffer.isView(data)); + if (options.binary === undefined) { + options.binary = data instanceof Buffer || data instanceof ArrayBuffer || + ArrayBuffer.isView(data); } - if (typeof options.mask === 'undefined') options.mask = !this._isServer; - if (typeof options.compress === 'undefined') options.compress = true; + if (options.mask === undefined) options.mask = !this._isServer; + if (options.compress === undefined) options.compress = true; if (!this.extensions[PerMessageDeflate.extensionName]) { options.compress = false; } - var readable = typeof stream.Readable === 'function' - ? stream.Readable - : stream.Stream; - - if (data instanceof readable) { + if (data instanceof stream.Readable) { startQueue(this); sendStream(this, data, options, (error) => { process.nextTick(() => executeQueueSends(this)); - if (typeof cb === 'function') cb(error); + if (cb) cb(error); }); } else { this._sender.send(data, options, cb); @@ -271,10 +267,10 @@ WebSocket.prototype.stream = function stream (options, cb) { options = {}; } - if (typeof cb !== 'function') throw new Error('callback must be provided'); + if (!cb) throw new Error('callback must be provided'); if (this.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else throw new Error('not opened'); return; } @@ -286,8 +282,8 @@ WebSocket.prototype.stream = function stream (options, cb) { options = options || {}; - if (typeof options.mask === 'undefined') options.mask = !this._isServer; - if (typeof options.compress === 'undefined') options.compress = true; + if (options.mask === undefined) options.mask = !this._isServer; + if (options.compress === undefined) options.compress = true; if (!this.extensions[PerMessageDeflate.extensionName]) { options.compress = false; } @@ -500,7 +496,7 @@ function MessageEvent (dataArg, isBinary, target) { */ function CloseEvent (code, reason, target) { this.type = 'close'; - this.wasClean = (typeof code === 'undefined' || code === 1000); + this.wasClean = code === undefined || code === 1000; this.code = code; this.reason = reason; this.target = target; @@ -723,7 +719,7 @@ function initAsClient (address, protocols, options) { } var serverKey = res.headers['sec-websocket-accept']; - if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { + if (serverKey !== expectedServerKey) { this.emit('error', new Error('invalid server key')); this.removeAllListeners(); socket.end(); @@ -855,7 +851,7 @@ function startQueue (instance) { function executeQueueSends (instance) { var queue = instance._queue; - if (typeof queue === 'undefined') return; + if (queue === undefined) return; delete instance._queue; for (var i = 0, l = queue.length; i < l; ++i) { @@ -866,7 +862,7 @@ function executeQueueSends (instance) { function sendStream (instance, stream, options, cb) { stream.on('data', function incoming (data) { if (instance.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else { delete instance._queue; instance.emit('error', new Error('not opened')); @@ -880,7 +876,7 @@ function sendStream (instance, stream, options, cb) { stream.on('end', function end () { if (instance.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else { delete instance._queue; instance.emit('error', new Error('not opened')); @@ -891,7 +887,7 @@ function sendStream (instance, stream, options, cb) { options.fin = true; instance._sender.send(null, options); - if (typeof cb === 'function') cb(null); + if (cb) cb(null); }); } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index e51c2e1c5..6bd06f908 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -73,9 +73,7 @@ function WebSocketServer (options, callback) { if (this._server._webSocketPaths && options.server._webSocketPaths[options.path]) { throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); } - if (typeof this._server._webSocketPaths !== 'object') { - this._server._webSocketPaths = {}; - } + if (!this._server._webSocketPaths) this._server._webSocketPaths = {}; this._server._webSocketPaths[options.path] = 1; } } @@ -139,7 +137,7 @@ WebSocketServer.prototype.close = function (callback) { // close the http server if it was internally created try { - if (typeof this._closeServer !== 'undefined') { + if (this._closeServer !== undefined) { this._closeServer(); } } finally { @@ -283,7 +281,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // calling completeHybiUpgrade2 var completeHybiUpgrade1 = () => { // choose from the sub-protocols - if (typeof this.options.handleProtocols === 'function') { + if (this.options.handleProtocols) { var protList = (protocols || '').split(/, */); var callbackCalled = false; this.options.handleProtocols(protList, (result, protocol) => { @@ -302,7 +300,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { }; // optionally call external client verification handler - if (typeof this.options.verifyClient === 'function') { + if (this.options.verifyClient) { var info = { secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, origin: origin, From 95001508cdec29dfe49b9e87a493f9d9f559bcb7 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 31 Oct 2016 19:27:16 +0100 Subject: [PATCH 104/489] Use `binary` instead of `latin1` encoding for node 4 compatibility --- lib/WebSocketServer.js | 2 +- test/testserver.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 6bd06f908..fe7978a9d 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -217,7 +217,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var completeHybiUpgrade2 = (protocol) => { // calc key var key = crypto.createHash('sha1') - .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') .digest('base64'); var headers = [ diff --git a/test/testserver.js b/test/testserver.js index bc94071ca..f68c4c32e 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -43,7 +43,7 @@ function validServer (server, req, socket) { // calc key const key = crypto.createHash('sha1') - .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') .digest('base64'); socket.setTimeout(0); From c8f3ed4f4d33610b33bf48ae0a6f58cbcd1d87e0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 2 Nov 2016 14:58:41 +0100 Subject: [PATCH 105/489] [fix] Ignore the case of the Upgrade header value as per spec --- lib/WebSocketServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index fe7978a9d..10eae03f2 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -170,7 +170,7 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb } } - if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { return abortConnection(socket, 400); } From 060b6cad09eeb1b01d6d073d829e976d7c3be1a9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 3 Nov 2016 11:25:06 +0100 Subject: [PATCH 106/489] [fix] Prevent data corruption when sending ping/pong messages (#881) --- lib/Sender.js | 50 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index f1acac557..ba4fa76c5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -64,6 +64,7 @@ class Sender { * @api public */ ping (data, options) { + if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.doPing, [data, options]]); } else { @@ -77,8 +78,7 @@ class Sender { * @api private */ doPing (data, options) { - var mask = options && options.mask; - this.frameAndSend(0x9, data ? Buffer.from(data.toString()) : null, true, mask); + this.frameAndSend(0x9, data, true, options.mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -90,6 +90,7 @@ class Sender { * @api public */ pong (data, options) { + if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.doPong, [data, options]]); } else { @@ -103,8 +104,7 @@ class Sender { * @api private */ doPong (data, options) { - var mask = options && options.mask; - this.frameAndSend(0xa, data ? Buffer.from(data.toString()) : null, true, mask); + this.frameAndSend(0xa, data, true, options.mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -120,6 +120,7 @@ class Sender { var mask = options && options.mask; var compress = options && options.compress; var opcode = options && options.binary ? 2 : 1; + if (this.firstFragment === false) { opcode = 0; compress = false; @@ -127,18 +128,9 @@ class Sender { this.firstFragment = false; this.compress = compress; } - if (finalFragment) this.firstFragment = true; - if (data && !Buffer.isBuffer(data)) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } else { - if (typeof data === 'number') { - data = data.toString(); - } - data = Buffer.from(data); - } - } + if (finalFragment) this.firstFragment = true; + if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); @@ -283,11 +275,29 @@ class Sender { module.exports = Sender; -function getBufferFromNative (data) { - // data is either an ArrayBuffer or ArrayBufferView. - return !data.buffer - ? new Buffer(data) - : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength); +/** + * Converts `data` into a buffer. + * + * @param {*} data Data to convert + * @return {Buffer} Converted data + * @private + */ +function toBuffer (data) { + if (Buffer.isBuffer(data)) return data; + + if (data instanceof ArrayBuffer) return Buffer.from(data); + + if (ArrayBuffer.isView(data)) { + const buf = Buffer.from(data.buffer); + + if (data.byteLength !== data.buffer.byteLength) { + return buf.slice(data.byteOffset, data.byteOffset + data.byteLength); + } + + return buf; + } + + return Buffer.from(typeof data === 'number' ? data.toString() : data); } function getRandomMask () { From 73d6880b3dea0e3627653077456241c5ba6b7d5b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 31 Oct 2016 14:54:02 +0100 Subject: [PATCH 107/489] [major] Rewrite the parser --- bench/parser.benchmark.js | 39 +- lib/Receiver.js | 723 +++++++++++++++++--------------------- test/Receiver.test.js | 26 +- 3 files changed, 363 insertions(+), 425 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index f0b9cae72..63883f8ea 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -7,48 +7,55 @@ 'use strict'; const benchmark = require('benchmark'); +const crypto = require('crypto'); const util = require('../test/hybi-util'); const Receiver = require('../').Receiver; -function createBinaryPacket (length) { - const message = Buffer.alloc(length); +// +// Override the `cleanup` method to make the "close message" test work as +// expected. +// +Receiver.prototype.cleanup = function () { + this.state = 0; + this.start(); +}; - for (var i = 0; i < length; ++i) message[i] = i % 10; +function createBinaryPacket (length) { + const message = crypto.randomBytes(length); - return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + '3483a868' + - util.mask(message, '3483a868').toString('hex'), 'hex'); + return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + + '3483a868' + util.mask(message, '3483a868').toString('hex'), 'hex'); } const pingMessage = 'Hello'; const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); + +const textMessage = 'a'.repeat(20); +const maskedTextPacket = Buffer.from('81' + util.pack(2, 0x80 | textMessage.length) + + '61616161' + util.mask(textMessage, '61616161').toString('hex'), 'hex'); + const pingPacket2 = Buffer.from('8900', 'hex'); const closePacket = Buffer.from('8800', 'hex'); -const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex'); const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024); const binaryDataPacket4 = createBinaryPacket(1024 * 1024); -var receiver = new Receiver({}, 1024 * 1024); +const receiver = new Receiver(); const suite = new benchmark.Suite(); suite.add('ping message', () => receiver.add(pingPacket1)); suite.add('ping with no data', () => receiver.add(pingPacket2)); -suite.add('close message', () => { - receiver.add(closePacket); - receiver.endPacket(); -}); -suite.add('masked text message', () => receiver.add(maskedTextPacket)); +suite.add('close message', () => receiver.add(closePacket)); +suite.add('masked text message (20 bytes)', () => receiver.add(maskedTextPacket)); suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); suite.add('binary data (200 KiB)', () => receiver.add(binaryDataPacket3)); suite.add('binary data (1 MiB)', () => receiver.add(binaryDataPacket4)); -suite.on('cycle', (e) => { - console.log(e.target.toString()); - receiver = new Receiver(); -}); + +suite.on('cycle', (e) => console.log(e.target.toString())); if (require.main === module) { suite.run({ async: true }); diff --git a/lib/Receiver.js b/lib/Receiver.js index 604ea8fc6..cc7b2be30 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -6,10 +6,19 @@ 'use strict'; +const PerMessageDeflate = require('./PerMessageDeflate'); +const bufferUtil = require('./BufferUtil').BufferUtil; const Validation = require('./Validation').Validation; const ErrorCodes = require('./ErrorCodes'); -const bufferUtil = require('./BufferUtil').BufferUtil; -const PerMessageDeflate = require('./PerMessageDeflate'); + +const EMPTY_BUFFER = Buffer.alloc(0); + +const START = 0; +const GET_PAYLOAD_LENGTH_16 = 1; +const GET_PAYLOAD_LENGTH_64 = 2; +const GET_MASK = 3; +const GET_DATA = 4; +const HANDLE_DATA = 5; const noop = () => {}; @@ -17,70 +26,53 @@ const noop = () => {}; * HyBi Receiver implementation. */ class Receiver { + /** + * Creates a Receiver instance. + * + * @param {Object} extensions An object containing the negotiated extensions + * @param {Number} maxPayload The maximum allowed message length + */ constructor (extensions, maxPayload) { this.extensions = extensions || {}; this.maxPayload = maxPayload | 0; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0 - }; - this.expectBytes = 0; - this.expectHandler = null; - this.currentMessage = []; - this.currentMessageLength = 0; - this.currentPayloadLength = 0; - this.expectData(2, this.processPacket); + + this.bufferedBytes = 0; + this.buffers = []; + + this.compressed = false; + this.payloadLength = 0; + this.fragmented = 0; + this.masked = false; + this.fin = false; + this.mask = null; + this.opcode = 0; + + this.totalPayloadLength = 0; + this.messageLength = 0; + this.fragments = []; + this.dead = false; - this.onerror = noop; - this.ontext = noop; this.onbinary = noop; this.onclose = noop; + this.onerror = noop; + this.ontext = noop; this.onping = noop; this.onpong = noop; - this.buffers = []; - this.bufferedBytes = 0; - } - - /** - * Add new data to the parser. - * - * @api public - */ - add (data) { - if (this.dead) return; - - this.buffers.push(data); - this.bufferedBytes += data.length; - - this.process(); - } - - /** - * Check buffer for data. - * - * @api private - */ - process () { - if (this.expectBytes && this.expectBytes <= this.bufferedBytes) { - var bufferForHandler = this.readBuffer(this.expectBytes); - this.expectBytes = 0; - this.expectHandler(bufferForHandler); - } + this.state = START; } /** - * Consume bytes from the available buffered data. + * Consumes bytes from the available buffered data. * - * @api private + * @param {Number} bytes The number of bytes to consume + * @private */ readBuffer (bytes) { + var bufoff = 0; var dst; var l; - var bufoff = 0; if (bytes === this.buffers[0].length) { this.bufferedBytes -= bytes; @@ -117,441 +109,380 @@ class Receiver { } /** - * Releases all resources used by the receiver. + * Adds new data to the parser. * - * @api public + * @public */ - cleanup () { - this.dead = true; - this.expectBytes = 0; - this.expectHandler = null; - this.buffers = []; - this.bufferedBytes = 0; - this.state = null; - this.currentMessage = null; - this.onerror = null; - this.ontext = null; - this.onbinary = null; - this.onclose = null; - this.onping = null; - this.onpong = null; + add (data) { + if (this.dead) return; + + this.bufferedBytes += data.length; + this.buffers.push(data); + + switch (this.state) { + case START: + this.start(); + break; + case GET_PAYLOAD_LENGTH_16: + this.getPayloadLength16(); + break; + case GET_PAYLOAD_LENGTH_64: + this.getPayloadLength64(); + break; + case GET_MASK: + this.getMask(); + break; + case GET_DATA: + this.getData(); + } } /** - * Waits for a certain amount of data bytes to be available, then fires a callback. + * Reads the first two bytes of a frame. * - * @api private + * @private */ - expectData (length, handler) { - if (length === 0) { - handler(null); + start () { + if (this.bufferedBytes < 2) return; + + const buf = this.readBuffer(2); + + if ((buf[0] & 0x30) !== 0x00) { + this.error(new Error('RSV2 and RSV3 must be clear'), 1002); return; } - this.expectBytes = length; - this.expectHandler = handler; - this.process(); - } + const compressed = (buf[0] & 0x40) === 0x40; - /** - * Start processing a new packet. - * - * @api private - */ - processPacket (data) { - if (this.extensions[PerMessageDeflate.extensionName]) { - if ((data[0] & 0x30) !== 0) { - this.error(new Error('reserved fields (2, 3) must be empty'), 1002); + if (compressed && !this.extensions[PerMessageDeflate.extensionName]) { + this.error(new Error('RSV1 must be clear'), 1002); + return; + } + + this.fin = (buf[0] & 0x80) === 0x80; + this.opcode = buf[0] & 0x0f; + this.payloadLength = buf[1] & 0x7f; + + if (this.opcode === 0x00) { + if (compressed) { + this.error(new Error('RSV1 must be clear'), 1002); return; } - } else { - if ((data[0] & 0x70) !== 0) { - this.error(new Error('reserved fields must be empty'), 1002); + + if (!this.fragmented) { + this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); return; + } else { + this.opcode = this.fragmented; } - } - this.state.lastFragment = (data[0] & 0x80) === 0x80; - this.state.masked = (data[1] & 0x80) === 0x80; - const compressed = (data[0] & 0x40) === 0x40; - const opcode = data[0] & 0xf; - if (opcode === 0) { - if (compressed) { - this.error(new Error('continuation frame cannot have the Per-message Compressed bits'), 1002); + } else if (this.opcode === 0x01 || this.opcode === 0x02) { + if (this.fragmented) { + this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); return; } - // continuation frame - this.state.opcode = this.state.activeFragmentedOperation; - if (!(this.state.opcode === 1 || this.state.opcode === 2)) { - this.error(new Error('continuation frame cannot follow current opcode'), 1002); + + this.compressed = compressed; + } else if (this.opcode > 0x07 && this.opcode < 0x0b) { + if (!this.fin) { + this.error(new Error('FIN must be set'), 1002); return; } - } else { - if (opcode < 3 && this.state.activeFragmentedOperation != null) { - this.error(new Error('data frames after the initial data frame must have opcode 0'), 1002); + + if (compressed) { + this.error(new Error('RSV1 must be clear'), 1002); return; } - if (opcode >= 8 && compressed) { - this.error(new Error('control frames cannot have the Per-message Compressed bits'), 1002); + + if (this.payloadLength > 0x7d) { + this.error(new Error('invalid payload length'), 1002); return; } - this.state.compressed = compressed; - this.state.opcode = opcode; - if (this.state.lastFragment === false) { - this.state.activeFragmentedOperation = opcode; - } + } else { + this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + return; } - const handler = opcodes[this.state.opcode]; - if (handler === undefined) { - this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); + + if (!this.fin && !this.fragmented) this.fragmented = this.opcode; + + this.masked = (buf[1] & 0x80) === 0x80; + + if (this.payloadLength === 126) { + this.state = GET_PAYLOAD_LENGTH_16; + this.getPayloadLength16(); + } else if (this.payloadLength === 127) { + this.state = GET_PAYLOAD_LENGTH_64; + this.getPayloadLength64(); } else { - handler.start(this, data); + this.haveLength(); } } /** - * Endprocessing a packet. + * Gets extended payload length (7+16). * - * @api private + * @private */ - endPacket () { - if (this.dead) return; - if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { - // end current fragmented operation - this.state.activeFragmentedOperation = null; - } - if (this.state.activeFragmentedOperation !== null) { - this.state.opcode = this.state.activeFragmentedOperation; - } else { - this.currentPayloadLength = this.state.opcode = 0; - } - this.state.lastFragment = false; - this.state.masked = false; - this.expectData(2, this.processPacket); + getPayloadLength16 () { + if (this.bufferedBytes < 2) return; + + this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); + this.haveLength(); } /** - * Reset the parser state. + * Gets extended payload length (7+64). * - * @api private + * @private */ - reset () { - if (this.dead) return; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0 - }; - this.expectBytes = 0; - this.expectHandler = null; - this.buffers = []; - this.bufferedBytes = 0; - this.currentMessage = []; - this.currentMessageLength = 0; - this.currentPayloadLength = 0; + getPayloadLength64 () { + if (this.bufferedBytes < 8) return; + + const buf = this.readBuffer(8); + const num = buf.readUInt32BE(0, true); + + // + // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned + // if payload length is greater than this number. + // + if (num > Math.pow(2, 53 - 32) - 1) { + this.error(new Error('max payload size exceeded'), 1009); + return; + } + + this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); + this.haveLength(); } /** - * Unmask received data. + * Payload length has been read. * - * @api private + * @private */ - unmask (mask, buf) { - if (mask != null && buf != null) bufferUtil.unmask(buf, mask); - return buf; + haveLength () { + if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) { + return; + } + + if (this.masked) { + this.state = GET_MASK; + this.getMask(); + } else { + this.state = GET_DATA; + this.getData(); + } } /** - * Handles an error. + * Reads mask bytes. * - * @api private + * @private */ - error (err, protocolErrorCode) { - this.reset(); - this.onerror(err, protocolErrorCode); - return this; + getMask () { + if (this.bufferedBytes < 4) return; + + this.mask = this.readBuffer(4); + this.state = GET_DATA; + this.getData(); } /** - * Checks payload size, disconnects socket when it exceeds `maxPayload`. + * Reads data bytes. * - * @api private + * @private */ - maxPayloadExceeded (length) { - if (this.maxPayload < 1) return false; - - const fullLength = this.currentPayloadLength + length; - if (fullLength <= this.maxPayload) { - this.currentPayloadLength = fullLength; - return false; + getData () { + if (this.payloadLength === 0) { + if (this.opcode > 0x07) this.controlMessage(EMPTY_BUFFER); + else this.dataMessage(); + return; } - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); - this.cleanup(); - return true; + if (this.bufferedBytes < this.payloadLength) return; + + const data = this.readBuffer(this.payloadLength); + + if (this.masked) bufferUtil.unmask(data, this.mask); + + if (this.opcode > 0x07) { + this.controlMessage(data); + } else if (this.compressed) { + this.state = HANDLE_DATA; + this.decompress(data); + } else if (this.pushFragment(data)) { + this.dataMessage(); + } } /** - * Handles compressed data. + * Decompresses data. * - * @api private + * @param {Buffer} data Compressed data + * @private */ - handleDataCompressed (packet) { + decompress (data) { const extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(packet, this.state.lastFragment, (err, buffer) => { + + extension.decompress(data, this.fin, (err, buf) => { if (this.dead) return; + if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } - this.handleData(buffer); - this.endPacket(); + if (this.pushFragment(buf)) this.dataMessage(); }); } /** - * Handles uncompressed data. + * Handles a data message. * - * @api private + * @private */ - handleData (buffer) { - if (buffer != null) { - if (this.maxPayload < 1 || this.currentMessageLength + buffer.length <= this.maxPayload) { - this.currentMessageLength += buffer.length; - this.currentMessage.push(buffer); + dataMessage () { + if (this.fin) { + const buf = this.fragments.length > 1 + ? Buffer.concat(this.fragments, this.messageLength) + : this.fragments.length === 1 + ? this.fragments[0] + : EMPTY_BUFFER; + + this.totalPayloadLength = 0; + this.fragments.length = 0; + this.messageLength = 0; + this.fragmented = 0; + + if (this.opcode === 2) { + this.onbinary(buf, { masked: this.masked }); } else { - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); - return; - } - } - if (this.state.lastFragment) { - const messageBuffer = this.currentMessage.length === 1 - ? this.currentMessage[0] - : Buffer.concat(this.currentMessage, this.currentMessageLength); - this.currentMessage = []; - this.currentMessageLength = 0; - - if (this.state.opcode === 2) { - this.onbinary(messageBuffer, { masked: this.state.masked }); - } else { - if (!Validation.isValidUTF8(messageBuffer)) { + if (!Validation.isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(messageBuffer.toString(), { masked: this.state.masked }); + + this.ontext(buf.toString(), { masked: this.masked }); } } - } -} -module.exports = Receiver; + this.state = START; + this.start(); + } -// -// Opcode handlers. -// -const opcodes = { - // text - '1': { - start: (receiver, data) => { - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - if (receiver.maxPayloadExceeded(firstLength)) return; - opcodes['1'].getData(receiver, firstLength); - } else if (firstLength === 126) { - receiver.expectData(2, (data) => { - const length = data.readUInt16BE(0, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['1'].getData(receiver, length); - }); - } else if (firstLength === 127) { - receiver.expectData(8, (data) => { - if (data.readUInt32BE(0, true) !== 0) { - receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); - return; - } - const length = data.readUInt32BE(4, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['1'].getData(receiver, length); - }); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['1'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['1'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data) || new Buffer(0); - if (receiver.state.compressed) { - receiver.handleDataCompressed(packet); - } else { - receiver.handleData(packet); - receiver.endPacket(); - } - } - }, - // binary - '2': { - start: (receiver, data) => { - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - if (receiver.maxPayloadExceeded(firstLength)) return; - opcodes['2'].getData(receiver, firstLength); - } else if (firstLength === 126) { - receiver.expectData(2, (data) => { - const length = data.readUInt16BE(0, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['2'].getData(receiver, length); - }); - } else if (firstLength === 127) { - receiver.expectData(8, (data) => { - if (data.readUInt32BE(0, true) !== 0) { - receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); - return; - } - const length = data.readUInt32BE(4, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['2'].getData(receiver, length); - }); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['2'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['2'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data) || new Buffer(0); - if (receiver.state.compressed) { - receiver.handleDataCompressed(packet); + /** + * Handles a control message. + * + * @param {Buffer} data Data to handle + * @private + */ + controlMessage (data) { + if (this.opcode === 0x08) { + if (data.length === 0) { + this.onclose(1000, '', { masked: this.masked }); + this.cleanup(); + } else if (data.length === 1) { + this.error(new Error('invalid payload length'), 1002); } else { - receiver.handleData(packet); - receiver.endPacket(); - } - } - }, - // close - '8': { - start: (receiver, data) => { - if (receiver.state.lastFragment === false) { - receiver.error('fragmented close is not supported', 1002); - return; - } + const code = data.readUInt16BE(0, true); - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - opcodes['8'].getData(receiver, firstLength); - } else { - receiver.error('control frames cannot have more than 125 bytes of data', 1002); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['8'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['8'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data); - if (packet && packet.length === 1) { - receiver.error('close packets with data must be at least two bytes long', 1002); - return; - } - const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; - if (!ErrorCodes.isValidErrorCode(code)) { - receiver.error('invalid error code', 1002); - return; - } - var message = ''; - if (packet && packet.length > 2) { - const messageBuffer = packet.slice(2); - if (!Validation.isValidUTF8(messageBuffer)) { - receiver.error('invalid utf8 sequence', 1007); + if (!ErrorCodes.isValidErrorCode(code)) { + this.error(new Error(`invalid status code: ${code}`), 1002); return; } - message = messageBuffer.toString(); + + const buf = data.slice(2); + + if (!Validation.isValidUTF8(buf)) { + this.error(new Error('invalid utf8 sequence'), 1007); + return; + } + + this.onclose(code, buf.toString(), { masked: this.masked }); + this.cleanup(); } - receiver.onclose(code, message, { masked: receiver.state.masked }); - receiver.reset(); + + return; } - }, - // ping - '9': { - start: (receiver, data) => { - if (receiver.state.lastFragment === false) { - receiver.error('fragmented ping is not supported', 1002); - return; - } - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - opcodes['9'].getData(receiver, firstLength); - } else { - receiver.error('control frames cannot have more than 125 bytes of data', 1002); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['9'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['9'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data); - const flags = { masked: receiver.state.masked, binary: true }; - receiver.onping(packet, flags); - receiver.endPacket(); + const flags = { masked: this.masked, binary: true }; + + if (this.opcode === 0x09) this.onping(data, flags); + else this.onpong(data, flags); + + this.state = START; + this.start(); + } + + /** + * Handles an error. + * + * @param {Error} err The error + * @param {Number} code Close code + * @private + */ + error (err, code) { + this.onerror(err, code); + this.cleanup(); + } + + /** + * Checks payload size, disconnects socket when it exceeds `maxPayload`. + * + * @param {Number} length Payload length + * @private + */ + maxPayloadExceeded (length) { + if (length === 0 || this.maxPayload < 1) return false; + + const fullLength = this.totalPayloadLength + length; + + if (fullLength <= this.maxPayload) { + this.totalPayloadLength = fullLength; + return false; } - }, - // pong - '10': { - start: (receiver, data) => { - if (receiver.state.lastFragment === false) { - receiver.error('fragmented pong is not supported', 1002); - return; - } - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - opcodes['10'].getData(receiver, firstLength); - } else { - receiver.error('control frames cannot have more than 125 bytes of data', 1002); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['10'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['10'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data); - const flags = { masked: receiver.state.masked, binary: true }; - receiver.onpong(packet, flags); - receiver.endPacket(); + this.error(new Error('max payload size exceeded'), 1009); + return true; + } + + /** + * Appends a fragment in the fragments array after checking that the sum of + * fragment lengths does not exceed `maxPayload`. + * + * @param {Buffer} fragment The fragment to add + * @return {Boolean} `true` if `maxPayload` is not exceeded, else `false` + * @private + */ + pushFragment (fragment) { + if (this.maxPayload < 1 || this.messageLength + fragment.length <= this.maxPayload) { + this.messageLength += fragment.length; + this.fragments.push(fragment); + return true; } + + this.error(new Error('max payload size exceeded'), 1009); + return false; + } + + /** + * Releases resources used by the receiver. + * + * @public + */ + cleanup () { + this.dead = true; + + this.extensions = null; + this.fragments = null; + this.buffers = null; + this.mask = null; + + this.onbinary = null; + this.onclose = null; + this.onerror = null; + this.ontext = null; + this.onping = null; + this.onpong = null; } -}; +} + +module.exports = Receiver; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 0f7b1b077..5717c7b49 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -122,7 +122,7 @@ describe('Receiver', function () { const p = new Receiver(); p.onping = function (data) { - assert.strictEqual(data, null); + assert.ok(data.equals(Buffer.alloc(0))); done(); }; @@ -312,41 +312,41 @@ describe('Receiver', function () { }); }); - it('resets `currentPayloadLength` only on final frame (unfragmented)', function () { + it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { const p = new Receiver({}, 10); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from('810548656c6c6f', 'hex')); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); }); - it('resets `currentPayloadLength` only on final frame (fragmented)', function () { + it('resets `totalPayloadLength` only on final frame (fragmented)', function () { const p = new Receiver({}, 10); const frame1 = '01024865'; const frame2 = '80036c6c6f'; - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from(frame1, 'hex')); - assert.strictEqual(p.currentPayloadLength, 2); + assert.strictEqual(p.totalPayloadLength, 2); p.add(Buffer.from(frame2, 'hex')); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); }); - it('resets `currentPayloadLength` only on final frame (fragmented + ping)', function () { + it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function () { const p = new Receiver({}, 10); const frame1 = '01024865'; const frame2 = '8900'; const frame3 = '80036c6c6f'; - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from(frame1, 'hex')); - assert.strictEqual(p.currentPayloadLength, 2); + assert.strictEqual(p.totalPayloadLength, 2); p.add(Buffer.from(frame2, 'hex')); - assert.strictEqual(p.currentPayloadLength, 2); + assert.strictEqual(p.totalPayloadLength, 2); p.add(Buffer.from(frame3, 'hex')); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); }); it('will raise an error on a 200 KiB long masked binary message when maxpayload is 20 KiB', function (done) { From 1b46f5ed97db6fb99a88e1bd4128e5c9d61e6a81 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 3 Nov 2016 17:03:45 +0100 Subject: [PATCH 108/489] [fix] Handle empty frames correctly in compressed & fragmented messages --- lib/Receiver.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index cc7b2be30..62d68c361 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -291,17 +291,14 @@ class Receiver { * @private */ getData () { - if (this.payloadLength === 0) { - if (this.opcode > 0x07) this.controlMessage(EMPTY_BUFFER); - else this.dataMessage(); - return; - } - - if (this.bufferedBytes < this.payloadLength) return; + var data = EMPTY_BUFFER; - const data = this.readBuffer(this.payloadLength); + if (this.payloadLength) { + if (this.bufferedBytes < this.payloadLength) return; - if (this.masked) bufferUtil.unmask(data, this.mask); + data = this.readBuffer(this.payloadLength); + if (this.masked) bufferUtil.unmask(data, this.mask); + } if (this.opcode > 0x07) { this.controlMessage(data); @@ -453,8 +450,12 @@ class Receiver { * @private */ pushFragment (fragment) { - if (this.maxPayload < 1 || this.messageLength + fragment.length <= this.maxPayload) { - this.messageLength += fragment.length; + if (fragment.length === 0) return true; + + const totalLength = this.messageLength + fragment.length; + + if (this.maxPayload < 1 || totalLength <= this.maxPayload) { + this.messageLength = totalLength; this.fragments.push(fragment); return true; } From d3fef3b407239171a7bcc516ff8bdb19606d36bf Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 19:06:12 +0100 Subject: [PATCH 109/489] Fix compression threshold use with fragmented messages --- doc/ws.md | 1 + lib/Sender.js | 23 +++++++---------------- test/Sender.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 897f875c2..b5189594d 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -65,6 +65,7 @@ If `handleProtocols` is not set then the handshake is accepted regardless the va * `threshold` Number: Payloads smaller than this will not be compressed. Default 1024 bytes. If a property is empty then either an offered configuration or a default value is used. +When sending a fragmented message the length of the first fragment is compared to the threshold. This determines if compression is used for the entire message. ### server.close([callback]) diff --git a/lib/Sender.js b/lib/Sender.js index ba4fa76c5..ae62b9f64 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -121,16 +121,20 @@ class Sender { var compress = options && options.compress; var opcode = options && options.binary ? 2 : 1; + if (data) data = toBuffer(data); + if (this.firstFragment === false) { opcode = 0; compress = false; } else { this.firstFragment = false; + if (compress && this.extensions[PerMessageDeflate.extensionName]) { + compress = data && data.length > this.extensions[PerMessageDeflate.extensionName].threshold; + } this.compress = compress; } if (finalFragment) this.firstFragment = true; - if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); @@ -145,12 +149,12 @@ class Sender { * @api private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { - if (data && data.length < this.extensions[PerMessageDeflate.extensionName].threshold) { + if (!this.compress) { this.frameAndSend(opcode, data, finalFragment, mask, false, cb); this.messageHandlerCallback(); return; } - this.applyExtensions(data, finalFragment, this.compress, (err, data) => { + this.extensions[PerMessageDeflate.extensionName].compress(data, finalFragment, (err, data) => { if (err) { if (cb) cb(err); else this.onerror(err); @@ -258,19 +262,6 @@ class Sender { this.messageHandlers.push(params); this.flush(); } - - /** - * Apply extensions to message - * - * @api private - */ - applyExtensions (data, fin, compress, callback) { - if (compress && data) { - this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); - } else { - callback(null, data); - } - } } module.exports = Sender; diff --git a/test/Sender.test.js b/test/Sender.test.js index acb16918a..651824487 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -76,6 +76,48 @@ describe('Sender', function () { sender.send('hi', { compress: true }); }); + it('compresses all frames in a fragmented message', function (done) { + let messageCount = 0; + let messageLength = 16; + const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 11; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('123', { compress: true, fin: false }); + sender.send('12', { compress: true, fin: true }); + }); + + it('compresses no frames in a fragmented message', function (done) { + let messageCount = 0; + let messageLength = 4; + const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 5; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('12', { compress: true, fin: false }); + sender.send('123', { compress: true, fin: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 16bd0c1ceb71ab7507905d6ea17dbb3ba7d84eeb Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 19:23:18 +0100 Subject: [PATCH 110/489] Fix sending empty fragment --- lib/Sender.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index ae62b9f64..f39fa97a0 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -149,8 +149,8 @@ class Sender { * @api private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { - if (!this.compress) { - this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + if (!this.compress || !data) { + this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); this.messageHandlerCallback(); return; } From e4514483989c5e1706a6618dfb30f3f66a690be2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 4 Nov 2016 17:43:24 +0100 Subject: [PATCH 111/489] [codestyle] Update JSDoc comments in lib/Sender.js --- lib/Sender.js | 106 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 24 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index f39fa97a0..5f33e3f5e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -11,9 +11,15 @@ const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); /** - * HyBi Sender implementation, Inherits from EventEmitter. + * HyBi Sender implementation. */ class Sender { + /** + * Creates a Sender instance. + * + * @param {net.Socket} socket The connection socket + * @param {Object} extensions An object containing the negotiated extensions + */ constructor (socket, extensions) { this._socket = socket; this.extensions = extensions || {}; @@ -25,9 +31,13 @@ class Sender { } /** - * Sends a close instruction to the remote party. + * Sends a close message to the other peer. * - * @api public + * @param {(Number|undefined)} code The status code component of the body + * @param {String} data The message component of the body + * @param {Boolean} mask Specifies whether or not to mask the message + * @param {Function} cb Callback + * @public */ close (code, data, mask, cb) { if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { @@ -46,9 +56,12 @@ class Sender { } /** - * Sends a close frame. + * Frames and sends a close message. * - * @api private + * @param {Buffer} data The message to send + * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Function} cb Callback + * @private */ doClose (data, mask, cb) { this.frameAndSend(0x8, data, true, mask); @@ -59,9 +72,12 @@ class Sender { } /** - * Sends a ping message to the remote party. + * Sends a ping message to the other peer. * - * @api public + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @public */ ping (data, options) { if (data) data = toBuffer(data); @@ -73,9 +89,12 @@ class Sender { } /** - * Sends a ping frame. + * Frames and sends a ping message. * - * @api private + * @param {Buffer} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @private */ doPing (data, options) { this.frameAndSend(0x9, data, true, options.mask); @@ -85,9 +104,12 @@ class Sender { } /** - * Sends a pong message to the remote party. + * Sends a pong message to the other peer. * - * @api public + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @public */ pong (data, options) { if (data) data = toBuffer(data); @@ -99,9 +121,12 @@ class Sender { } /** - * Sends a pong frame. + * Frames and sends a pong message. * - * @api private + * @param {Buffer} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @private */ doPong (data, options) { this.frameAndSend(0xa, data, true, options.mask); @@ -111,9 +136,15 @@ class Sender { } /** - * Sends text or binary data to the remote party. + * Sends a data message to the other peer. * - * @api public + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.compress Specifies whether or not to compress `data` + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.fin Specifies whether the fragment is the last one + * @param {Function} cb Callback + * @public */ send (data, options, cb) { var finalFragment = !options || options.fin !== false; @@ -144,9 +175,15 @@ class Sender { } /** - * Sends compressed data. + * Compresses, frames and sends a data message. * - * @api private + * @param {Number} opcode The opcode + * @param {Buffer} data The message to send + * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Boolean} compress Specifies whether or not to set the RSV1 bit + * @param {Function} cb Callback + * @private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { if (!this.compress || !data) { @@ -168,7 +205,13 @@ class Sender { /** * Frames and sends a piece of data according to the HyBi WebSocket protocol. * - * @api private + * @param {Number} opcode The opcode + * @param {Buffer} data The data to send + * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {Boolean} maskData Specifies whether or not to mask `data` + * @param {Boolean} compressed Specifies whether or not to set the RSV1 bit + * @param {Function} cb Callback + * @private */ frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { if (!data) { @@ -228,9 +271,9 @@ class Sender { } /** - * Execute message handler buffers + * Executes a queued send operation. * - * @api private + * @private */ flush () { if (this.processing) return; @@ -244,9 +287,9 @@ class Sender { } /** - * Callback to indicate message handler completion. + * Signals the completion of a send operation. * - * @api private + * @private */ messageHandlerCallback () { this.processing = false; @@ -254,9 +297,9 @@ class Sender { } /** - * Enqueues a send frame operation. + * Enqueues a send operation. * - * @api private + * @private */ enqueue (params) { this.messageHandlers.push(params); @@ -291,6 +334,12 @@ function toBuffer (data) { return Buffer.from(typeof data === 'number' ? data.toString() : data); } +/** + * Generates a random mask. + * + * @return {Buffer} The mask + * @private + */ function getRandomMask () { return new Buffer([ ~~(Math.random() * 255), @@ -300,6 +349,15 @@ function getRandomMask () { ]); } +/** + * Sends a frame. + * + * @param {Sender} sender Sender instance + * @param {Buffer} outputBuffer The data to send + * @param {Buffer} data Additional data to send if frame is split into two buffers + * @param {Function} cb Callback + * @private + */ function sendFramedData (sender, outputBuffer, data, cb) { try { if (data) { From 9823f710f95eea0a19ea674f74a98361b040f161 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 19:50:07 +0100 Subject: [PATCH 112/489] Remove unneeded `encoding` argument to `socket.write` --- lib/Sender.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 5f33e3f5e..0c70ee156 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -361,10 +361,10 @@ function getRandomMask () { function sendFramedData (sender, outputBuffer, data, cb) { try { if (data) { - sender._socket.write(outputBuffer, 'binary'); - sender._socket.write(data, 'binary', cb); + sender._socket.write(outputBuffer); + sender._socket.write(data, cb); } else { - sender._socket.write(outputBuffer, 'binary', cb); + sender._socket.write(outputBuffer, cb); } } catch (e) { if (cb) cb(e); From 2dd55269bea2047ecc33829fc3a28a7ef189506e Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 20:54:34 +0100 Subject: [PATCH 113/489] Allow compressed empty first fragment --- lib/Sender.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 0c70ee156..2f7113030 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -159,8 +159,8 @@ class Sender { compress = false; } else { this.firstFragment = false; - if (compress && this.extensions[PerMessageDeflate.extensionName]) { - compress = data && data.length > this.extensions[PerMessageDeflate.extensionName].threshold; + if (compress && data && this.extensions[PerMessageDeflate.extensionName]) { + compress = data.length > this.extensions[PerMessageDeflate.extensionName].threshold; } this.compress = compress; } From 87904d7b0362d3574b8ef21336ad3cc22ff73611 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 21:19:46 +0100 Subject: [PATCH 114/489] Fix sending empty compressed fragments --- lib/PerMessageDeflate.js | 5 +++++ lib/Sender.js | 4 ++-- test/Sender.test.js | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 397377b91..03dded810 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -6,6 +6,7 @@ const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); +const EMPTY_BLOCK = Buffer.from([0x00]); /** * Per-message Compression Extensions implementation @@ -282,6 +283,10 @@ class PerMessageDeflate { */ compress (data, fin, callback) { + if (!data) { + return callback(null, EMPTY_BLOCK); + } + var endpoint = this._isServer ? 'server' : 'client'; if (!this._deflate) { diff --git a/lib/Sender.js b/lib/Sender.js index 2f7113030..b4637d10a 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -186,8 +186,8 @@ class Sender { * @private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { - if (!this.compress || !data) { - this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + if (!this.compress) { + this.frameAndSend(opcode, data, finalFragment, mask, false, cb); this.messageHandlerCallback(); return; } diff --git a/test/Sender.test.js b/test/Sender.test.js index 651824487..1e2fb4196 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -118,6 +118,48 @@ describe('Sender', function () { sender.send('123', { compress: true, fin: true }); }); + it('compresses empty first fragment', function (done) { + let messageCount = 0; + let messageLength = 3; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 13; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send(null, { compress: true, fin: false }); + sender.send('data', { compress: true, fin: true }); + }); + + it('compresses empty last fragment', function (done) { + let messageCount = 0; + let messageLength = 17; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 3; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('data', { compress: true, fin: false }); + sender.send(null, { compress: true, fin: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 8d0cdb6fb269b40b40b12c5ef19f6a28e3feba4b Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 21:44:47 +0100 Subject: [PATCH 115/489] Support sending an empty buffer as a compressed fragment --- lib/PerMessageDeflate.js | 2 +- lib/Sender.js | 2 +- test/Sender.test.js | 51 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 03dded810..83bb85065 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -283,7 +283,7 @@ class PerMessageDeflate { */ compress (data, fin, callback) { - if (!data) { + if (!data || data.length === 0) { return callback(null, EMPTY_BLOCK); } diff --git a/lib/Sender.js b/lib/Sender.js index b4637d10a..58b4a4a1b 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -160,7 +160,7 @@ class Sender { } else { this.firstFragment = false; if (compress && data && this.extensions[PerMessageDeflate.extensionName]) { - compress = data.length > this.extensions[PerMessageDeflate.extensionName].threshold; + compress = data.length >= this.extensions[PerMessageDeflate.extensionName].threshold; } this.compress = compress; } diff --git a/test/Sender.test.js b/test/Sender.test.js index 1e2fb4196..cb49a86d7 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -79,7 +79,7 @@ describe('Sender', function () { it('compresses all frames in a fragmented message', function (done) { let messageCount = 0; let messageLength = 16; - const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { assert.strictEqual(data.length, messageLength); @@ -100,7 +100,7 @@ describe('Sender', function () { it('compresses no frames in a fragmented message', function (done) { let messageCount = 0; let messageLength = 4; - const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { assert.strictEqual(data.length, messageLength); @@ -118,7 +118,7 @@ describe('Sender', function () { sender.send('123', { compress: true, fin: true }); }); - it('compresses empty first fragment', function (done) { + it('compresses null as first fragment', function (done) { let messageCount = 0; let messageLength = 3; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); @@ -139,7 +139,29 @@ describe('Sender', function () { sender.send('data', { compress: true, fin: true }); }); - it('compresses empty last fragment', function (done) { + it('compresses empty buffer as first fragment', function (done) { + let messageCount = 0; + // let messageLength = 3; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + console.log(data); + // assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + // messageLength = 13; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send(Buffer.alloc(0), { compress: true, fin: false }); + sender.send('data', { compress: true, fin: true }); + }); + + it('compresses null last fragment', function (done) { let messageCount = 0; let messageLength = 17; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); @@ -160,6 +182,27 @@ describe('Sender', function () { sender.send(null, { compress: true, fin: true }); }); + it('compresses empty buffer as last fragment', function (done) { + let messageCount = 0; + let messageLength = 17; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 3; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('data', { compress: true, fin: false }); + sender.send(Buffer.alloc(0), { compress: true, fin: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 158c6725f92f35cef527874273d8f3e92697def3 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 21:46:37 +0100 Subject: [PATCH 116/489] Uncomment test --- test/Sender.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/Sender.test.js b/test/Sender.test.js index cb49a86d7..f2384e38c 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -141,15 +141,14 @@ describe('Sender', function () { it('compresses empty buffer as first fragment', function (done) { let messageCount = 0; - // let messageLength = 3; + let messageLength = 3; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - console.log(data); - // assert.strictEqual(data.length, messageLength); + assert.strictEqual(data.length, messageLength); messageCount++; if (messageCount > 1) return done(); - // messageLength = 13; + messageLength = 13; } }, { 'permessage-deflate': perMessageDeflate From dd9c973e06592183fb38726f34afd5f9f3be68be Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 5 Nov 2016 03:02:01 +0100 Subject: [PATCH 117/489] Use noAssert when writing to buffers --- lib/Sender.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 58b4a4a1b..f1beff458 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -45,7 +45,7 @@ class Sender { } code = code || 1000; var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - dataBuffer.writeUInt16BE(code, 0); + dataBuffer.writeUInt16BE(code, 0, true); if (dataBuffer.length > 2) dataBuffer.write(data, 2); if (this.extensions[PerMessageDeflate.extensionName]) { @@ -242,11 +242,11 @@ class Sender { switch (secondByte) { case 126: - outputBuffer.writeUInt16BE(dataLength, 2); + outputBuffer.writeUInt16BE(dataLength, 2, true); break; case 127: - outputBuffer.writeUInt32BE(0, 2); - outputBuffer.writeUInt32BE(dataLength, 6); + outputBuffer.writeUInt32BE(0, 2, true); + outputBuffer.writeUInt32BE(dataLength, 6, true); } if (maskData) { From 6696543dde0cf364e6ce3d0a7ff9f36afb29b04b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 Nov 2016 07:27:51 +0100 Subject: [PATCH 118/489] [deps] Bump eslint to version 3.9.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 939c7de8c..a2e271bee 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "3.8.x", + "eslint": "3.9.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.3.x", From 8cbb5f5f70dc0a77bf0c71cefe40812a98644f3d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 Nov 2016 08:25:17 +0100 Subject: [PATCH 119/489] [test] Ensure that the RSV1 bit is set as intended --- test/Sender.test.js | 94 +++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/test/Sender.test.js b/test/Sender.test.js index f2384e38c..30eddd42a 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -31,7 +31,7 @@ describe('Sender', function () { assert.ok(text.equals(Buffer.from('hi there'))); }); - it('sets rsv1 flag if compressed', function (done) { + it('sets RSV1 bit if compressed', function (done) { const sender = new Sender({ write: (data) => { assert.strictEqual(data[0] & 0x40, 0x40); @@ -77,15 +77,18 @@ describe('Sender', function () { }); it('compresses all frames in a fragmented message', function (done) { - let messageCount = 0; - let messageLength = 16; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 11; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 16); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 11); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -98,15 +101,18 @@ describe('Sender', function () { }); it('compresses no frames in a fragmented message', function (done) { - let messageCount = 0; - let messageLength = 4; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 5; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x00); + assert.strictEqual(fragments[0].length, 4); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 5); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -119,15 +125,18 @@ describe('Sender', function () { }); it('compresses null as first fragment', function (done) { - let messageCount = 0; - let messageLength = 3; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 13; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 3); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 13); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -140,15 +149,18 @@ describe('Sender', function () { }); it('compresses empty buffer as first fragment', function (done) { - let messageCount = 0; - let messageLength = 3; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 13; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 3); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 13); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -161,15 +173,18 @@ describe('Sender', function () { }); it('compresses null last fragment', function (done) { - let messageCount = 0; - let messageLength = 17; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 3; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 3); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -182,15 +197,18 @@ describe('Sender', function () { }); it('compresses empty buffer as last fragment', function (done) { - let messageCount = 0; - let messageLength = 17; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 3; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 3); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -202,7 +220,7 @@ describe('Sender', function () { sender.send(Buffer.alloc(0), { compress: true, fin: true }); }); - it('Should be able to handle many send calls while processing without crashing on flush', function (done) { + it('handles many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 72a4bda5bc144b037702dfcea84dd58223f60cb0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 Nov 2016 09:42:14 +0100 Subject: [PATCH 120/489] [fix] Reset the `processing` flag inside the `nextTick` callback --- lib/Sender.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index f1beff458..4b212365e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -292,8 +292,10 @@ class Sender { * @private */ messageHandlerCallback () { - this.processing = false; - process.nextTick(() => this.flush()); + process.nextTick(() => { + this.processing = false; + this.flush(); + }); } /** From ebf86b58f1fb1e96ab96ebf95782bad7bc5ab8ca Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 6 Nov 2016 12:00:52 +0100 Subject: [PATCH 121/489] [minor] Remove redundant checks --- lib/Sender.js | 29 ++++++++++++++--------------- test/testserver.js | 17 +++++------------ 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 4b212365e..c5849f02b 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -140,6 +140,7 @@ class Sender { * * @param {*} data The message to send * @param {Object} options Options object + * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.compress Specifies whether or not to compress `data` * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Boolean} options.fin Specifies whether the fragment is the last one @@ -147,30 +148,28 @@ class Sender { * @public */ send (data, options, cb) { - var finalFragment = !options || options.fin !== false; - var mask = options && options.mask; - var compress = options && options.compress; - var opcode = options && options.binary ? 2 : 1; + const pmd = this.extensions[PerMessageDeflate.extensionName]; + var opcode = options.binary ? 2 : 1; + var compress = options.compress; if (data) data = toBuffer(data); - if (this.firstFragment === false) { - opcode = 0; - compress = false; - } else { + if (this.firstFragment) { this.firstFragment = false; - if (compress && data && this.extensions[PerMessageDeflate.extensionName]) { - compress = data.length >= this.extensions[PerMessageDeflate.extensionName].threshold; - } + if (compress && data && pmd) compress = data.length >= pmd.threshold; this.compress = compress; + } else { + compress = false; + opcode = 0; } - if (finalFragment) this.firstFragment = true; + if (options.fin) this.firstFragment = true; - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); + if (pmd) { + const args = [opcode, data, options.fin, options.mask, compress, cb]; + this.enqueue([this.sendCompressed, args]); } else { - this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + this.frameAndSend(opcode, data, options.fin, options.mask, false, cb); } } diff --git a/test/testserver.js b/test/testserver.js index f68c4c32e..48e1e287d 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -60,31 +60,24 @@ function validServer (server, req, socket) { const sender = new Sender(socket); const receiver = new Receiver(); + receiver.onping = (message, flags) => server.emit('ping', message, flags); + receiver.onpong = (message, flags) => server.emit('pong', message, flags); receiver.ontext = (message, flags) => { server.emit('message', message, flags); - sender.send(message); + sender.send(message, { fin: true }); }; receiver.onbinary = (message, flags) => { - flags = flags || {}; flags.binary = true; server.emit('message', message, flags); - sender.send(message, { binary: true }); - }; - receiver.onping = (message, flags) => { - flags = flags || {}; - server.emit('ping', message, flags); - }; - receiver.onpong = (message, flags) => { - flags = flags || {}; - server.emit('pong', message, flags); + sender.send(message, { binary: true, fin: true }); }; receiver.onclose = (code, message, flags) => { - flags = flags || {}; sender.close(code, message, false, () => { server.emit('close', code, message, flags); socket.end(); }); }; + socket.on('data', (data) => receiver.add(data)); socket.on('end', () => socket.end()); } From 2fc1965e63008f0d65301ba1ee923ddc4c3d1415 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 6 Nov 2016 20:10:53 +0100 Subject: [PATCH 122/489] [major] Remove path registry --- lib/WebSocketServer.js | 17 ---------- test/WebSocketServer.test.js | 63 ------------------------------------ 2 files changed, 80 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 10eae03f2..8c9a2eeb5 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -67,15 +67,6 @@ function WebSocketServer (options, callback) { this._closeServer = () => this._server && this._server.close(); } else if (options.server) { this._server = options.server; - if (options.path) { - // take note of the path, to avoid collisions when multiple websocket servers are - // listening on the same http server - if (this._server._webSocketPaths && options.server._webSocketPaths[options.path]) { - throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); - } - if (!this._server._webSocketPaths) this._server._webSocketPaths = {}; - this._server._webSocketPaths[options.path] = 1; - } } if (this._server) { @@ -127,14 +118,6 @@ WebSocketServer.prototype.close = function (callback) { } } - // remove path descriptor, if any - if (this.path && this._server._webSocketPaths) { - delete this._server._webSocketPaths[this.path]; - if (Object.keys(this._server._webSocketPaths).length === 0) { - delete this._server._webSocketPaths; - } - } - // close the http server if it was internally created try { if (this._closeServer !== undefined) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index b74a28afd..1da15738c 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -109,48 +109,6 @@ describe('WebSocketServer', function () { }); }); - it('can have two different instances listening on the same http server with two different paths', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const wss1 = new WebSocketServer({ server, path: '/wss1' }); - const wss2 = new WebSocketServer({ server, path: '/wss2' }); - let doneCount = 0; - - wss1.on('connection', (client) => { - wss1.close(); - if (++doneCount === 2) { - server.close(done); - } - }); - - wss2.on('connection', (client) => { - wss2.close(); - if (++doneCount === 2) { - server.close(done); - } - }); - - /* eslint-disable no-unused-vars */ - const ws1 = new WebSocket(`ws://localhost:${port}/wss1`); - const ws2 = new WebSocket(`ws://localhost:${port}/wss2?foo=1`); - /* eslint-enable no-unused-vars */ - }); - }); - - it('cannot have two different instances listening on the same http server with the same path', function (done) { - const server = http.createServer(); - const wss1 = new WebSocketServer({ server: server, path: '/wss1' }); - - try { - // eslint-disable-next-line no-unused-vars - const wss2 = new WebSocketServer({ server: server, path: '/wss1' }); - } catch (e) { - wss1.close(); - done(); - } - }); - it('will not crash when it receives an unhandled opcode', function (done) { const wss = new WebSocketServer({ port: 8080 }); @@ -229,27 +187,6 @@ describe('WebSocketServer', function () { server.close(done); }); }); - - it('cleans up websocket data on a precreated server', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const wss1 = new WebSocketServer({ server, path: '/wss1' }); - const wss2 = new WebSocketServer({ server, path: '/wss2' }); - - assert.strictEqual(typeof server._webSocketPaths, 'object'); - assert.strictEqual(Object.keys(server._webSocketPaths).length, 2); - - wss1.close(); - - assert.strictEqual(Object.keys(server._webSocketPaths).length, 1); - - wss2.close(); - - assert.strictEqual(typeof server._webSocketPaths, 'undefined'); - server.close(done); - }); - }); }); describe('#clients', function () { From 5cfe70ea1dc656f77bd43bfccd2c48b5db839d8a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 7 Nov 2016 17:09:08 +0100 Subject: [PATCH 123/489] [minor] Avoid copying buffers when possible --- bench/sender.benchmark.js | 29 +++--- lib/Sender.js | 186 +++++++++++++++++++++----------------- test/Sender.test.js | 36 ++++---- 3 files changed, 141 insertions(+), 110 deletions(-) diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 11e856a33..8473e561d 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -7,25 +7,32 @@ 'use strict'; const benchmark = require('benchmark'); +const crypto = require('crypto'); const Sender = require('../').Sender; -const data1 = Buffer.alloc(200 * 1024, 99); -const data2 = Buffer.alloc(1024 * 1024, 99); +const data1 = crypto.randomBytes(64); +const data2 = crypto.randomBytes(16 * 1024); +const data3 = crypto.randomBytes(64 * 1024); +const data4 = crypto.randomBytes(200 * 1024); +const data5 = crypto.randomBytes(1024 * 1024); const suite = new benchmark.Suite(); var sender = new Sender(); sender._socket = { write () {} }; -suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, false)); -suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, true)); -suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, false)); -suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, true)); -suite.on('cycle', (e) => { - console.log(e.target.toString()); - sender = new Sender(); - sender._socket = { write () {} }; -}); +suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(0x2, data1, false, true, false)); +suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(0x2, data1, true, true, true)); +suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(0x2, data2, false, true, false)); +suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(0x2, data2, true, true, true)); +suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(0x2, data3, false, true, false)); +suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(0x2, data3, true, true, true)); +suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data4, false, true, false)); +suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data4, true, true, true)); +suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data5, false, true, false)); +suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data5, true, true, true)); + +suite.on('cycle', (e) => console.log(e.target.toString())); if (require.main === module) { suite.run({ async: true }); diff --git a/lib/Sender.js b/lib/Sender.js index c5849f02b..de6fda611 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -43,15 +43,16 @@ class Sender { if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { throw new Error('first argument must be a valid error code number'); } - code = code || 1000; - var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - dataBuffer.writeUInt16BE(code, 0, true); - if (dataBuffer.length > 2) dataBuffer.write(data, 2); + + const buf = Buffer.allocUnsafe(2 + (data ? Buffer.byteLength(data) : 0)); + + buf.writeUInt16BE(code || 1000, 0, true); + if (buf.length > 2) buf.write(data, 2); if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doClose, [dataBuffer, mask, cb]]); + this.enqueue([this.doClose, [buf, mask, cb]]); } else { - this.doClose(dataBuffer, mask, cb); + this.doClose(buf, mask, cb); } } @@ -64,7 +65,7 @@ class Sender { * @private */ doClose (data, mask, cb) { - this.frameAndSend(0x8, data, true, mask); + this.frameAndSend(0x08, data, false, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -80,24 +81,22 @@ class Sender { * @public */ ping (data, options) { - if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPing, [data, options]]); + this.enqueue([this.doPing, [data, options.mask]]); } else { - this.doPing(data, options); + this.doPing(data, options.mask); } } /** * Frames and sends a ping message. * - * @param {Buffer} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {*} data The message to send + * @param {Boolean} mask Specifies whether or not to mask `data` * @private */ - doPing (data, options) { - this.frameAndSend(0x9, data, true, options.mask); + doPing (data, mask) { + this.frameAndSend(0x09, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -112,24 +111,22 @@ class Sender { * @public */ pong (data, options) { - if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPong, [data, options]]); + this.enqueue([this.doPong, [data, options.mask]]); } else { - this.doPong(data, options); + this.doPong(data, options.mask); } } /** * Frames and sends a pong message. * - * @param {Buffer} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {*} data The message to send + * @param {Boolean} mask Specifies whether or not to mask `data` * @private */ - doPong (data, options) { - this.frameAndSend(0xa, data, true, options.mask); + doPong (data, mask) { + this.frameAndSend(0x0a, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -140,10 +137,10 @@ class Sender { * * @param {*} data The message to send * @param {Object} options Options object - * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.compress Specifies whether or not to compress `data` - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.fin Specifies whether the fragment is the last one + * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Function} cb Callback * @public */ @@ -152,8 +149,6 @@ class Sender { var opcode = options.binary ? 2 : 1; var compress = options.compress; - if (data) data = toBuffer(data); - if (this.firstFragment) { this.firstFragment = false; if (compress && data && pmd) compress = data.length >= pmd.threshold; @@ -169,7 +164,7 @@ class Sender { const args = [opcode, data, options.fin, options.mask, compress, cb]; this.enqueue([this.sendCompressed, args]); } else { - this.frameAndSend(opcode, data, options.fin, options.mask, false, cb); + this.frameAndSend(opcode, data, true, options.fin, options.mask, false, cb); } } @@ -177,26 +172,29 @@ class Sender { * Compresses, frames and sends a data message. * * @param {Number} opcode The opcode - * @param {Buffer} data The message to send - * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {*} data The message to send + * @param {Boolean} fin Specifies whether or not to set the FIN bit * @param {Boolean} mask Specifies whether or not to mask `data` - * @param {Boolean} compress Specifies whether or not to set the RSV1 bit + * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (opcode, data, finalFragment, mask, compress, cb) { + sendCompressed (opcode, data, fin, mask, rsv1, cb) { if (!this.compress) { - this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + this.frameAndSend(opcode, data, true, fin, mask, false, cb); this.messageHandlerCallback(); return; } - this.extensions[PerMessageDeflate.extensionName].compress(data, finalFragment, (err, data) => { + + if (data && !Buffer.isBuffer(data)) data = toBuffer(data); + this.extensions[PerMessageDeflate.extensionName].compress(data, fin, (err, buf) => { if (err) { if (cb) cb(err); else this.onerror(err); return; } - this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + + this.frameAndSend(opcode, buf, false, fin, mask, rsv1, cb); this.messageHandlerCallback(); }); } @@ -205,67 +203,84 @@ class Sender { * Frames and sends a piece of data according to the HyBi WebSocket protocol. * * @param {Number} opcode The opcode - * @param {Buffer} data The data to send - * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {*} data The data to send + * @param {Boolean} readOnly Specifies whether `data` can be modified + * @param {Boolean} fin Specifies whether or not to set the FIN bit * @param {Boolean} maskData Specifies whether or not to mask `data` - * @param {Boolean} compressed Specifies whether or not to set the RSV1 bit + * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { + frameAndSend (opcode, data, readOnly, fin, maskData, rsv1, cb) { if (!data) { - var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] - .concat(maskData ? [0, 0, 0, 0] : []); - sendFramedData(this, new Buffer(buff), null, cb); + const bytes = [opcode, 0]; + + if (fin) bytes[0] |= 0x80; + if (maskData) { + bytes[1] |= 0x80; + bytes.push(0, 0, 0, 0); + } + + sendFramedData(this, Buffer.from(bytes), null, cb); return; } - var dataLength = data.length; + if (!Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(typeof data === 'number' ? data.toString() : data); + readOnly = false; + } + } + + const mergeBuffers = data.length < 1024 || maskData && readOnly; var dataOffset = maskData ? 6 : 2; - var secondByte = dataLength; + var payloadLength = data.length; - if (dataLength >= 65536) { + if (data.length >= 65536) { dataOffset += 8; - secondByte = 127; - } else if (dataLength > 125) { + payloadLength = 127; + } else if (data.length > 125) { dataOffset += 2; - secondByte = 126; + payloadLength = 126; } - var canModifyData = compressed; - var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); - var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; - var outputBuffer = new Buffer(totalLength); - outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; - if (compressed) outputBuffer[0] |= 0x40; - - switch (secondByte) { - case 126: - outputBuffer.writeUInt16BE(dataLength, 2, true); - break; - case 127: - outputBuffer.writeUInt32BE(0, 2, true); - outputBuffer.writeUInt32BE(dataLength, 6, true); + const outputBuffer = Buffer.allocUnsafe( + mergeBuffers ? data.length + dataOffset : dataOffset + ); + + outputBuffer[0] = fin ? opcode | 0x80 : opcode; + if (rsv1) outputBuffer[0] |= 0x40; + + if (payloadLength === 126) { + outputBuffer.writeUInt16BE(data.length, 2, true); + } else if (payloadLength === 127) { + outputBuffer.writeUInt32BE(0, 2, true); + outputBuffer.writeUInt32BE(data.length, 6, true); } if (maskData) { - outputBuffer[1] = secondByte | 0x80; - var mask = getRandomMask(); + const mask = getRandomMask(); + + outputBuffer[1] = payloadLength | 0x80; outputBuffer[dataOffset - 4] = mask[0]; outputBuffer[dataOffset - 3] = mask[1]; outputBuffer[dataOffset - 2] = mask[2]; outputBuffer[dataOffset - 1] = mask[3]; + if (mergeBuffers) { - bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); + bufferUtil.mask(data, mask, outputBuffer, dataOffset, data.length); } else { - bufferUtil.mask(data, mask, data, 0, dataLength); + bufferUtil.mask(data, mask, data, 0, data.length); } } else { - outputBuffer[1] = secondByte; - if (mergeBuffers) { - data.copy(outputBuffer, dataOffset); - } + outputBuffer[1] = payloadLength; + if (mergeBuffers) data.copy(outputBuffer, dataOffset); } + sendFramedData(this, outputBuffer, mergeBuffers ? null : data, cb); } @@ -300,6 +315,7 @@ class Sender { /** * Enqueues a send operation. * + * @param {Array} params Send operation parameters. * @private */ enqueue (params) { @@ -310,6 +326,23 @@ class Sender { module.exports = Sender; +/** + * Converts an `ArrayBuffer` view into a buffer. + * + * @param {(DataView|TypedArray)} view The view to convert + * @return {Buffer} Converted view + * @private + */ +function viewToBuffer (view) { + const buf = Buffer.from(view.buffer); + + if (view.byteLength !== view.buffer.byteLength) { + return buf.slice(view.byteOffset, view.byteOffset + view.byteLength); + } + + return buf; +} + /** * Converts `data` into a buffer. * @@ -318,19 +351,8 @@ module.exports = Sender; * @private */ function toBuffer (data) { - if (Buffer.isBuffer(data)) return data; - if (data instanceof ArrayBuffer) return Buffer.from(data); - - if (ArrayBuffer.isView(data)) { - const buf = Buffer.from(data.buffer); - - if (data.byteLength !== data.buffer.byteLength) { - return buf.slice(data.byteOffset, data.byteOffset + data.byteLength); - } - - return buf; - } + if (ArrayBuffer.isView(data)) return viewToBuffer(data); return Buffer.from(typeof data === 'number' ? data.toString() : data); } diff --git a/test/Sender.test.js b/test/Sender.test.js index 30eddd42a..d88f32d4f 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -17,7 +17,7 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const buf = Buffer.from([1, 2, 3, 4, 5]); - sender.frameAndSend(2, buf, true, true); + sender.frameAndSend(2, buf, true, true, true); assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); @@ -26,7 +26,7 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const text = Buffer.from('hi there'); - sender.frameAndSend(1, text, true, true); + sender.frameAndSend(1, text, true, true, true); assert.ok(text.equals(Buffer.from('hi there'))); }); @@ -39,7 +39,7 @@ describe('Sender', function () { } }); - sender.frameAndSend(1, Buffer.from('hi'), true, false, true); + sender.frameAndSend(1, Buffer.from('hi'), false, true, false, true); }); }); @@ -57,7 +57,7 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('hi', { compress: true }); + sender.send('hi', { compress: true, fin: true }); }); it('does not compress data for small payloads', function (done) { @@ -73,7 +73,7 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('hi', { compress: true }); + sender.send('hi', { compress: true, fin: true }); }); it('compresses all frames in a fragmented message', function (done) { @@ -221,23 +221,25 @@ describe('Sender', function () { }); it('handles many send calls while processing without crashing on flush', function (done) { - const maxMessages = 5000; - let messageCount = 0; - + let cnt = 0; + const perMessageDeflate = new PerMessageDeflate(); const sender = new Sender({ - write: (data) => { - messageCount++; - if (messageCount > maxMessages) return done(); + write: () => { + if (++cnt > 1e4) done(); } + }, { + 'permessage-deflate': perMessageDeflate }); - for (let i = 0; i < maxMessages; i++) { + perMessageDeflate.accept([{}]); + + for (let i = 0; i < 1e4; i++) { sender.processing = true; - sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + sender.send('hi', { compress: false, fin: true }); } sender.processing = false; - sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + sender.send('hi', { compress: false, fin: true }); }); }); @@ -254,9 +256,9 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('foo', { compress: true }); - sender.send('bar', { compress: true }); - sender.send('baz', { compress: true }); + sender.send('foo', { compress: true, fin: true }); + sender.send('bar', { compress: true, fin: true }); + sender.send('baz', { compress: true, fin: true }); sender.close(1000, null, false, (err) => { assert.strictEqual(count, 4); From 647242518f8ea2ccafbd2bc008327d8b7fedbebd Mon Sep 17 00:00:00 2001 From: William Yaworsky Date: Tue, 14 Apr 2015 02:00:17 -0400 Subject: [PATCH 124/489] [minor] Add `shouldHandle()` method to `WebSocketServer` Moved the check to see if a `WebSocketServer` instance should handle a given request into a public method. This will allow people to override this logic with a patch if they desire. --- lib/WebSocketServer.js | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 10eae03f2..f513100fe 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -156,21 +156,35 @@ WebSocketServer.prototype.close = function (callback) { }; /** - * Handle a HTTP Upgrade request. + * See if a given request should be handled by this server instance. * - * @api public + * @param {http.IncomingMessage} req Request object to inspect + * @return {Boolean} `true` if the request is valid, else `false` + * @public */ - -WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb) { - // check for wrong path - if (this.options.path) { - var u = url.parse(req.url); - if (u && u.pathname !== this.options.path) { - return abortConnection(socket, 400); - } +WebSocketServer.prototype.shouldHandle = function (req) { + if (this.options.path && url.parse(req.url).pathname !== this.options.path) { + return false; } - if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { + return true; +}; + +/** + * Handle a HTTP Upgrade request. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @public + */ +WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { + if ( + !this.shouldHandle(req) || + !req.headers.upgrade || + req.headers.upgrade.toLowerCase() !== 'websocket' + ) { return abortConnection(socket, 400); } From d83117d5fbaf2ece7e4ffe575efd486469abc29d Mon Sep 17 00:00:00 2001 From: William Yaworsky Date: Tue, 14 Apr 2015 02:17:45 -0400 Subject: [PATCH 125/489] [test] Add tests for `WebSocketServer.prototype.shouldHandle()` --- test/WebSocketServer.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index b74a28afd..419563d42 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -367,6 +367,20 @@ describe('WebSocketServer', function () { }); }); + describe('#shouldHandle', function () { + it('returns true when the path matches', function () { + const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + + assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); + }); + + it('returns false when the path does not match', function () { + const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + + assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); + }); + }); + describe('#handleUpgrade', function () { it('can be used for a pre-existing server', function (done) { const server = http.createServer(); From 442ca0abb5726d667c9ffd9296c0f92c78097721 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 16:25:17 +0200 Subject: [PATCH 126/489] [major] Remove `stream` method and ability to send a stream --- lib/WebSocket.js | 120 +------- test/WebSocket.test.js | 602 +---------------------------------------- 2 files changed, 7 insertions(+), 715 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 511a63f9d..a89c1daac 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -11,7 +11,6 @@ const util = require('util'); const http = require('http'); const https = require('https'); const crypto = require('crypto'); -const stream = require('stream'); const Ultron = require('ultron'); const Sender = require('./Sender'); const Receiver = require('./Receiver'); @@ -222,11 +221,6 @@ WebSocket.prototype.send = function send (data, options, cb) { if (!data) data = ''; - if (this._queue) { - this._queue.push(() => this.send(data, options, cb)); - return; - } - options = options || {}; if (options.fin !== false) options.fin = true; @@ -241,72 +235,7 @@ WebSocket.prototype.send = function send (data, options, cb) { options.compress = false; } - if (data instanceof stream.Readable) { - startQueue(this); - - sendStream(this, data, options, (error) => { - process.nextTick(() => executeQueueSends(this)); - if (cb) cb(error); - }); - } else { - this._sender.send(data, options, cb); - } -}; - -/** - * Streams data through calls to a user supplied function - * - * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean - * @param {function} 'function (error, send)' which is executed on successive - * ticks of which send is 'function (data, final)'. - * @api public - */ -WebSocket.prototype.stream = function stream (options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; - } - - if (!cb) throw new Error('callback must be provided'); - - if (this.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else throw new Error('not opened'); - return; - } - - if (this._queue) { - this._queue.push(() => this.stream(options, cb)); - return; - } - - options = options || {}; - - if (options.mask === undefined) options.mask = !this._isServer; - if (options.compress === undefined) options.compress = true; - if (!this.extensions[PerMessageDeflate.extensionName]) { - options.compress = false; - } - - startQueue(this); - - const send = (data, final) => { - try { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); - options.fin = final === true; - this._sender.send(data, options); - if (!final) process.nextTick(cb, null, send); - else executeQueueSends(this); - } catch (e) { - if (typeof cb === 'function') cb(e); - else { - delete this._queue; - this.emit('error', e); - } - } - }; - - process.nextTick(cb, null, send); + this._sender.send(data, options, cb); }; /** @@ -845,52 +774,6 @@ function establishConnection (socket, upgradeHead) { this.emit('open'); } -function startQueue (instance) { - instance._queue = instance._queue || []; -} - -function executeQueueSends (instance) { - var queue = instance._queue; - if (queue === undefined) return; - - delete instance._queue; - for (var i = 0, l = queue.length; i < l; ++i) { - queue[i](); - } -} - -function sendStream (instance, stream, options, cb) { - stream.on('data', function incoming (data) { - if (instance.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else { - delete instance._queue; - instance.emit('error', new Error('not opened')); - } - return; - } - - options.fin = false; - instance._sender.send(data, options); - }); - - stream.on('end', function end () { - if (instance.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else { - delete instance._queue; - instance.emit('error', new Error('not opened')); - } - return; - } - - options.fin = true; - instance._sender.send(null, options); - - if (cb) cb(null); - }); -} - function cleanupWebsocketResources (error) { if (this.readyState === WebSocket.CLOSED) return; @@ -941,5 +824,4 @@ function cleanupWebsocketResources (error) { this.removeAllListeners(); this.on('error', function onerror () {}); // catch all errors after this - delete this._queue; } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 52612c533..cafab9647 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -796,11 +796,11 @@ describe('WebSocket', function () { }); }); - it('with unencoded message is successfully transmitted to the server', function (done) { + it('with unmasked message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send('hi')); + ws.on('open', () => ws.send('hi', { mask: false })); srv.on('message', (message, flags) => { assert.strictEqual(message, 'hi'); @@ -810,7 +810,7 @@ describe('WebSocket', function () { }); }); - it('with encoded message is successfully transmitted to the server', function (done) { + it('with masked message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -825,7 +825,7 @@ describe('WebSocket', function () { }); }); - it('with unencoded binary message is successfully transmitted to the server', function (done) { + it('with unmasked binary message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const array = new Float32Array(5); @@ -835,7 +835,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send(array, { binary: true })); + ws.on('open', () => ws.send(array, { mask: false, binary: true })); srv.on('message', (message, flags) => { assert.ok(flags.binary); @@ -846,7 +846,7 @@ describe('WebSocket', function () { }); }); - it('with encoded binary message is successfully transmitted to the server', function (done) { + it('with masked binary message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const array = new Float32Array(5); @@ -867,561 +867,9 @@ describe('WebSocket', function () { }); }); }); - - it('with binary stream will send fragmented data', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - let callbackFired = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100 - }); - - ws.send(fileStream, { binary: true }, (error) => { - assert.ifError(error); - callbackFired = true; - }); - }); - - ws.on('close', () => { - assert.ok(callbackFired); - srv.close(done); - }); - - srv.on('message', (data, flags) => { - assert.ok(flags.binary); - assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); - - ws.close(); - }); - }); - }); - - it('with text stream will send fragmented data', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - let callbackFired = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream, { binary: false }, (error) => { - assert.ifError(error); - callbackFired = true; - }); - }); - - ws.on('close', () => { - assert.ok(callbackFired); - srv.close(done); - }); - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - - ws.close(); - }); - }); - }); - - it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - ws.send('foobar'); - ws.send('baz'); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - } else { - assert.ok(!flags.binary); - assert.strictEqual(data, 'baz'); - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - - let i = 0; - ws.stream((error, send) => { - assert.ifError(error); - - if (++i === 1) send('foo'); - else send('bar', true); - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - ws.ping('foobar'); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - - srv.on('ping', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - ws.pong('foobar'); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - if (++receivedIndex === 2) { - srv.close(done); - ws.close(); - } - }); - - srv.on('pong', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - ws.send(fileStream); - ws.close(1000, 'foobar'); - }); - - ws.on('close', () => srv.close(done)); - ws.on('error', () => { - // That's quite alright -- a send was attempted after close - }); - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - }); - - srv.on('close', (code, data) => { - assert.strictEqual(code, 1000); - assert.strictEqual(data, 'foobar'); - }); - }); - }); - }); - - describe('#stream', function () { - it('very long binary data can be streamed', function (done) { - server.createServer(++port, (srv) => { - const buffer = new Buffer(10 * 1024); - - for (let i = 0; i < buffer.length; ++i) { - buffer[i] = i % 0xff; - } - - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const bufLen = buffer.length; - const blockSize = 800; - let i = 0; - - ws.stream({ binary: true }, (error, send) => { - assert.ifError(error); - - const start = i * blockSize; - const toSend = Math.min(blockSize, bufLen - (i * blockSize)); - const end = start + toSend; - const isFinal = toSend < blockSize; - - send(buffer.slice(start, end), isFinal); - i += 1; - }); - }); - - srv.on('message', (data, flags) => { - assert.ok(flags.binary); - assert.ok(data.equals(buffer)); - srv.close(done); - ws.terminate(); - }); - }); - }); - - it('before connect should pass error through callback', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.stream((error) => { - assert.ok(error instanceof Error); - srv.close(done); - ws.terminate(); - }); - }); - }); - - it('without callback should fail', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - try { - ws.stream(); - } catch (e) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - ws.send('foobar'); - ws.send('baz'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - } else { - assert.ok(!flags.binary); - assert.strictEqual(data, 'baz'); - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - - let i2 = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i2 === 1) send('foo'); - else send('bar', true); - }); - - ws.send('baz'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - } else if (receivedIndex === 3) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'baz'); - setTimeout(() => { - srv.close(done); - ws.terminate(); - }, 1000); - } else { - throw new Error('more messages than we actually sent just arrived'); - } - }); - }); - }); - - it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - ws.ping('foobar'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - - srv.on('ping', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - ws.pong('foobar'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - - srv.on('pong', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - let errorGiven = false; - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - if (++i === 1) { - send(payload.substr(0, 5)); - ws.close(1000, 'foobar'); - } else if (i === 2) { - send(payload.substr(5, 5), true); - } else if (i === 3) { - assert.ok(error); - errorGiven = true; - } - }); - }); - - ws.on('close', () => { - assert.ok(errorGiven); - srv.close(done); - ws.terminate(); - }); - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - }); - - srv.on('close', (code, data) => { - assert.strictEqual(code, 1000); - assert.strictEqual(data.toString(), 'foobar'); - }); - }); - }); }); describe('#close', function () { - it('will raise error callback, if any, if called during send stream', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - let errorGiven = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream, (error) => { - errorGiven = !!error; - }); - ws.close(1000, 'foobar'); - }); - - ws.on('close', () => { - setTimeout(() => { - assert.ok(errorGiven); - srv.close(done); - }, 1000); - }); - }); - }); - it('without invalid first argument throws exception', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -2107,44 +1555,6 @@ describe('WebSocket', function () { }); }); - it('with binary stream will send fragmented data', function (done) { - const wss = new WebSocketServer({ - perMessageDeflate: true, - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true - }); - - let callbackFired = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100 - }); - - ws.send(fileStream, { binary: true, compress: true }, (error) => { - assert.ifError(error); - callbackFired = true; - }); - }); - - ws.on('close', () => { - assert.ok(callbackFired); - wss.close(); - done(); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (data, flags) => { - assert.ok(flags.binary); - assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); - ws.close(); - }); - }); - }); - describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { From bdcd18b97e7324dbe8effa7d06866b29172709e7 Mon Sep 17 00:00:00 2001 From: Antti Risteli Date: Sat, 12 Nov 2016 09:09:50 +0200 Subject: [PATCH 127/489] [fix] Call zlib flush with the correct flush level (#733) --- lib/PerMessageDeflate.js | 5 +++-- test/PerMessageDeflate.test.js | 27 +++++++++++++++++++++++++++ test/Sender.test.js | 12 ++++++------ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 83bb85065..ae58689a8 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -7,6 +7,7 @@ const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +const FLUSH_LEVEL = zlib.Z_SYNC_FLUSH; /** * Per-message Compression Extensions implementation @@ -292,7 +293,7 @@ class PerMessageDeflate { if (!this._deflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._deflate = zlib.createDeflateRaw({ - flush: zlib.Z_SYNC_FLUSH, + flush: FLUSH_LEVEL, windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL }); @@ -304,7 +305,7 @@ class PerMessageDeflate { this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(function () { + this._deflate.flush(FLUSH_LEVEL, function () { cleanup(); var data = Buffer.concat(buffers); if (fin) { diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index fe570f4b2..59d202b69 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -311,5 +311,32 @@ describe('PerMessageDeflate', function () { }); }); }); + + it('should compress data between contexts when allowed', function (done) { + var perMessageDeflate = new PerMessageDeflate(); + var extensions = Extensions.parse('permessage-deflate'); + perMessageDeflate.accept(extensions['permessage-deflate']); + + var buf = new Buffer('foofoo'); + perMessageDeflate.compress(buf, true, function (err, compressed1) { + if (err) return done(err); + + perMessageDeflate.decompress(compressed1, true, function (err, data) { + if (err) return done(err); + + perMessageDeflate.compress(data, true, function (err, compressed2) { + if (err) return done(err); + + perMessageDeflate.decompress(compressed2, true, function (err, data) { + if (err) return done(err); + + assert.ok(compressed2.length < compressed1.length); + assert.deepEqual(data, buf); + done(); + }); + }); + }); + }); + }); }); }); diff --git a/test/Sender.test.js b/test/Sender.test.js index d88f32d4f..01342b54d 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -85,9 +85,9 @@ describe('Sender', function () { if (fragments.length !== 2) return; assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 16); + assert.strictEqual(fragments[0].length, 11); assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 11); + assert.strictEqual(fragments[1].length, 6); done(); } }, { @@ -135,7 +135,7 @@ describe('Sender', function () { assert.strictEqual(fragments[0][0] & 0x40, 0x40); assert.strictEqual(fragments[0].length, 3); assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 13); + assert.strictEqual(fragments[1].length, 8); done(); } }, { @@ -159,7 +159,7 @@ describe('Sender', function () { assert.strictEqual(fragments[0][0] & 0x40, 0x40); assert.strictEqual(fragments[0].length, 3); assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 13); + assert.strictEqual(fragments[1].length, 8); done(); } }, { @@ -181,7 +181,7 @@ describe('Sender', function () { if (fragments.length !== 2) return; assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[0].length, 12); assert.strictEqual(fragments[1][0] & 0x40, 0x00); assert.strictEqual(fragments[1].length, 3); done(); @@ -205,7 +205,7 @@ describe('Sender', function () { if (fragments.length !== 2) return; assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[0].length, 12); assert.strictEqual(fragments[1][0] & 0x40, 0x00); assert.strictEqual(fragments[1].length, 3); done(); From e909cf177b3566d638a1aa683640ab1dff32ef6e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 12 Nov 2016 08:31:36 +0100 Subject: [PATCH 128/489] [minor] Fix nits --- lib/PerMessageDeflate.js | 5 ++--- test/PerMessageDeflate.test.js | 17 +++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index ae58689a8..37162c0d1 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -7,7 +7,6 @@ const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); -const FLUSH_LEVEL = zlib.Z_SYNC_FLUSH; /** * Per-message Compression Extensions implementation @@ -293,7 +292,7 @@ class PerMessageDeflate { if (!this._deflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._deflate = zlib.createDeflateRaw({ - flush: FLUSH_LEVEL, + flush: zlib.Z_SYNC_FLUSH, windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL }); @@ -305,7 +304,7 @@ class PerMessageDeflate { this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(FLUSH_LEVEL, function () { + this._deflate.flush(zlib.Z_SYNC_FLUSH, function () { cleanup(); var data = Buffer.concat(buffers); if (fin) { diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 59d202b69..fb1706813 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -313,25 +313,26 @@ describe('PerMessageDeflate', function () { }); it('should compress data between contexts when allowed', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate'); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const extensions = Extensions.parse('permessage-deflate'); + const buf = Buffer.from('foofoo'); + perMessageDeflate.accept(extensions['permessage-deflate']); - var buf = new Buffer('foofoo'); - perMessageDeflate.compress(buf, true, function (err, compressed1) { + perMessageDeflate.compress(buf, true, (err, compressed1) => { if (err) return done(err); - perMessageDeflate.decompress(compressed1, true, function (err, data) { + perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); - perMessageDeflate.compress(data, true, function (err, compressed2) { + perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function (err, data) { + perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); assert.ok(compressed2.length < compressed1.length); - assert.deepEqual(data, buf); + assert.ok(data.equals(buf)); done(); }); }); From 6bd0a57f17a5ed75127ebe67b27661c25f4ba3ec Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 13 Nov 2016 11:31:52 +0100 Subject: [PATCH 129/489] [major] Remove callback from `handleProtocols` --- doc/ws.md | 11 ++- lib/WebSocketServer.js | 139 +++++++++++++++-------------------- test/WebSocketServer.test.js | 54 ++------------ 3 files changed, 69 insertions(+), 135 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index b5189594d..6ba7e2b2b 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -45,13 +45,12 @@ If `verifyClient` is not set then the handshake is automatically accepted. ### options.handleProtocols -`handleProtocols` receives two arguments: -* `protocols` Array: The list of WebSocket sub-protocols indicated by the client in the Sec-WebSocket-Protocol header. -* `cb` Function: A callback that must be called by the user upon inspection of the protocols. Arguments in this callback are: - * `result` Boolean: Whether the user accepts or not the handshake. - * `protocol` String: If `result` is `true` then this field sets the value of the Sec-WebSocket-Protocol header in the HTTP 101 response. +`handleProtocols` takes a single argument: +* `protocols` Array: The list of WebSocket sub-protocols indicated by the client in the `Sec-WebSocket-Protocol` header. + +If returned value is `false` then the handshake is rejected with the HTTP 401 status code, otherwise the returned value sets the value of the `Sec-WebSocket-Protocol` header in the HTTP 101 response. -If `handleProtocols` is not set then the handshake is accepted regardless the value of Sec-WebSocket-Protocol header. If it is set but the user does not invoke the `cb` callback then the handshake is rejected with error HTTP 501. +If `handleProtocols` is not set then the handshake is automatically accepted. ### options.perMessageDeflate diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index b2eda494a..dab4cfea1 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -166,58 +166,51 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { if ( !this.shouldHandle(req) || !req.headers.upgrade || - req.headers.upgrade.toLowerCase() !== 'websocket' + req.headers.upgrade.toLowerCase() !== 'websocket' || + !req.headers['sec-websocket-key'] ) { return abortConnection(socket, 400); } - handleHybiUpgrade.apply(this, arguments); + socket.on('error', socketError); + upgrade.apply(this, arguments); }; module.exports = WebSocketServer; /** - * Entirely private apis, - * which may or may not be bound to a specific WebSocket instance. + * Handle premature socket errors. + * + * @private */ +function socketError () { + this.destroy(); +} -function handleHybiUpgrade (req, socket, upgradeHead, cb) { - // handle premature socket errors - var errorHandler = () => { - try { socket.destroy(); } catch (e) {} - }; - socket.on('error', errorHandler); - - // verify key presence - if (!req.headers['sec-websocket-key']) { - return abortConnection(socket, 400); - } - - // verify version - var version = +req.headers['sec-websocket-version']; - if (version !== 8 && version !== 13) { - return abortConnection(socket, 400); - } - - // verify protocol - var protocols = req.headers['sec-websocket-protocol']; +/** + * Upgrade the connection to WebSocket. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @private + */ +function upgrade (req, socket, head, cb) { + const version = +req.headers['sec-websocket-version']; - // verify client - var origin = version !== 13 - ? req.headers['sec-websocket-origin'] - : req.headers['origin']; + if (version !== 8 && version !== 13) return abortConnection(socket, 400); - // handle extensions offer - var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']); + var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); // handler to call when the connection sequence completes - var completeHybiUpgrade2 = (protocol) => { + const completeUpgrade = () => { // calc key - var key = crypto.createHash('sha1') + const key = crypto.createHash('sha1') .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') .digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -228,40 +221,39 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { headers.push(`Sec-WebSocket-Protocol: ${protocol}`); } - var extensions = {}; + const offer = Extensions.parse(req.headers['sec-websocket-extensions']); + var extensions; + try { - extensions = acceptExtensions.call(this, extensionsOffer); + extensions = acceptExtensions.call(this, offer); } catch (err) { return abortConnection(socket, 400); } if (Object.keys(extensions).length) { - var serverExtensions = {}; - Object.keys(extensions).forEach((token) => { - serverExtensions[token] = [extensions[token].params]; - }); + const serverExtensions = Object.keys(extensions).reduce((obj, key) => { + obj[key] = [extensions[key].params]; + return obj; + }, {}); + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); } // allows external modification/inspection of handshake headers this.emit('headers', headers); - socket.setTimeout(0); - socket.setNoDelay(true); - - try { + if (socket.writable) { socket.write(headers.concat('', '').join('\r\n')); - } catch (e) { - // if the upgrade write fails, shut the connection down hard - try { socket.destroy(); } catch (e) {} + } else { + socket.destroy(); return; } - var client = new WebSocket([req, socket, upgradeHead], { + const client = new WebSocket([req, socket, head], { + maxPayload: this.options.maxPayload, protocolVersion: version, - protocol: protocol, - extensions: extensions, - maxPayload: this.options.maxPayload + extensions, + protocol }); if (this.clients) { @@ -270,44 +262,31 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } // signal upgrade complete - socket.removeListener('error', errorHandler); + socket.removeListener('error', socketError); cb(client); }; - // optionally call external protocol selection handler before - // calling completeHybiUpgrade2 - var completeHybiUpgrade1 = () => { - // choose from the sub-protocols - if (this.options.handleProtocols) { - var protList = (protocols || '').split(/, */); - var callbackCalled = false; - this.options.handleProtocols(protList, (result, protocol) => { - callbackCalled = true; - if (!result) return abortConnection(socket, 401); - - completeHybiUpgrade2(protocol); - }); - if (!callbackCalled) { - // the handleProtocols handler never called our callback - abortConnection(socket, 501, 'Could not process protocols'); - } - } else { - completeHybiUpgrade2(protocols && protocols.split(/, */)[0]); - } - }; + // optionally call external protocol selection handler + if (this.options.handleProtocols) { + protocol = this.options.handleProtocols(protocol); + if (protocol === false) return abortConnection(socket, 401); + } else { + protocol = protocol[0]; + } // optionally call external client verification handler if (this.options.verifyClient) { - var info = { - secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, - origin: origin, - req: req + const info = { + origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], + secure: !!(req.connection.authorized || req.connection.encrypted), + req }; + if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, message) => { - if (!result) return abortConnection(socket, code || 401, message); + this.options.verifyClient(info, (verified, code, message) => { + if (!verified) return abortConnection(socket, code || 401, message); - completeHybiUpgrade1(); + completeUpgrade(); }); return; } else if (!this.options.verifyClient(info)) { @@ -315,7 +294,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } } - completeHybiUpgrade1(); + completeUpgrade(); } function acceptExtensions (offer) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 86e52174c..2bfa6aafa 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -765,7 +765,7 @@ describe('WebSocketServer', function () { it('selects the last protocol via protocol handler', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), + handleProtocols: (ps) => ps[ps.length - 1], port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); @@ -780,7 +780,7 @@ describe('WebSocketServer', function () { it('client detects invalid server protocol', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, 'prot3'), + handleProtocols: (ps) => 'prot3', port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); @@ -795,22 +795,7 @@ describe('WebSocketServer', function () { it('client detects no server protocol', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); - }); - }); - - it('client refuses server protocols', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), + handleProtocols: (ps) => {}, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); @@ -825,7 +810,7 @@ describe('WebSocketServer', function () { it('server detects unauthorized protocol handler', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), + handleProtocols: (ps) => false, port: ++port }, () => { const req = http.request({ @@ -834,7 +819,7 @@ describe('WebSocketServer', function () { 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' + 'Origin': 'http://foobar.com' }, host: '127.0.0.1', port @@ -850,35 +835,6 @@ describe('WebSocketServer', function () { }); }); - it('server detects invalid protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => { - // not calling callback is an error and shouldn't timeout - }, - port: ++port - }, () => { - const req = http.request({ - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 501); - wss.close(); - done(); - }); - - req.end(); - }); - }); - it('accept connections with sec-websocket-extensions', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const req = http.request({ From 6a8d707483f2a4ba4c754894a2c0e0c258fe3407 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 12 Nov 2016 20:26:56 +0100 Subject: [PATCH 130/489] [minor] Use ultron to handle the events listeners on the server (#889) --- lib/WebSocketServer.js | 102 ++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index b2eda494a..ea818f288 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -6,23 +6,35 @@ 'use strict'; -const util = require('util'); const EventEmitter = require('events'); -const http = require('http'); const crypto = require('crypto'); -const WebSocket = require('./WebSocket'); -const Extensions = require('./Extensions'); -const PerMessageDeflate = require('./PerMessageDeflate'); +const Ultron = require('ultron'); +const http = require('http'); +const util = require('util'); const url = require('url'); -var isDefinedAndNonNull = function (options, key) { - return options[key] !== undefined && options[key] !== null; -}; +const PerMessageDeflate = require('./PerMessageDeflate'); +const Extensions = require('./Extensions'); +const WebSocket = require('./WebSocket'); /** - * WebSocket Server implementation + * Create a `WebSocketServer` instance. + * + * @param {Object} options Configuration options + * @param {String} options.host The hostname where to bind the server + * @param {Number} options.port The port where to bind the server + * @param {http.Server} options.server A pre-created HTTP/S server to use + * @param {Function} options.verifyClient An hook to reject connections + * @param {Function} options.handleProtocols An hook to handle protocols + * @param {String} options.path Accept only connections matching this path + * @param {Boolean} options.noServer Enable no server mode + * @param {Boolean} options.clientTracking Specifies whether or not to track clients + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate + * @param {Number} options.maxPayload The maximum allowed message size + * @param {Function} callback A listener for the `listening` event + * @constructor + * @public */ - function WebSocketServer (options, callback) { if (this instanceof WebSocketServer === false) { return new WebSocketServer(options, callback); @@ -44,13 +56,14 @@ function WebSocketServer (options, callback) { backlog: null // use default (511 as implemented in net.js) }, options); - if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { - throw new TypeError('`port` or a `server` must be provided'); + if (options.port == null && !options.server && !options.noServer) { + throw new TypeError('missing or invalid options'); } - if (isDefinedAndNonNull(options, 'port')) { + if (options.port != null) { this._server = http.createServer((req, res) => { - var body = http.STATUS_CODES[426]; + const body = http.STATUS_CODES[426]; + res.writeHead(426, { 'Content-Length': body.length, 'Content-Type': 'text/plain' @@ -58,33 +71,21 @@ function WebSocketServer (options, callback) { res.end(body); }); this._server.allowHalfOpen = false; - // maybe use a generic server.listen(options[, callback]) variant here, instead of two overloaded variants? - if (isDefinedAndNonNull(options, 'backlog')) { - this._server.listen(options.port, options.host, options.backlog, callback); - } else { - this._server.listen(options.port, options.host, callback); - } - this._closeServer = () => this._server && this._server.close(); + this._server.listen(options.port, options.host, options.backlog, callback); } else if (options.server) { this._server = options.server; } if (this._server) { - this._onceServerListening = () => this.emit('listening'); - this._server.once('listening', this._onceServerListening); - this._onServerError = (error) => this.emit('error', error); - this._server.on('error', this._onServerError); - this._onServerUpgrade = (req, socket, upgradeHead) => { - // copy upgradeHead to avoid retention of large slab buffers used in node core - var head = new Buffer(upgradeHead.length); - upgradeHead.copy(head); - + this._ultron = new Ultron(this._server); + this._ultron.on('listening', () => this.emit('listening')); + this._ultron.on('error', (err) => this.emit('error', err)); + this._ultron.on('upgrade', (req, socket, head) => { this.handleUpgrade(req, socket, head, (client) => { this.emit(`connection${req.url}`, client); this.emit('connection', client); }); - }; - this._server.on('upgrade', this._onServerUpgrade); + }); } if (options.clientTracking) this.clients = new Set(); @@ -92,19 +93,15 @@ function WebSocketServer (options, callback) { this.path = options.path; } -/** - * Inherits from EventEmitter. - */ - util.inherits(WebSocketServer, EventEmitter); /** - * Immediately shuts down the connection. + * Close the server. * - * @api public + * @param {Function} cb Callback + * @public */ - -WebSocketServer.prototype.close = function (callback) { +WebSocketServer.prototype.close = function (cb) { // terminate all associated clients var error = null; @@ -118,24 +115,15 @@ WebSocketServer.prototype.close = function (callback) { } } - // close the http server if it was internally created - try { - if (this._closeServer !== undefined) { - this._closeServer(); - } - } finally { - if (this._server) { - this._server.removeListener('listening', this._onceServerListening); - this._server.removeListener('error', this._onServerError); - this._server.removeListener('upgrade', this._onServerUpgrade); - } - delete this._server; - } - if (callback) { - callback(error); - } else if (error) { - throw error; + if (this._server) { + // close the http server if it was internally created + if (this.options.port != null) this._server.close(); + this._ultron.destroy(); + this._ultron = this._server = null; } + + if (cb) cb(error); + else if (error) throw error; }; /** From 084369dde8f99af7a7e2bfc63b286648d10181b1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 15 Nov 2016 10:52:47 +0100 Subject: [PATCH 131/489] [fix] Invoke the close callback when the close event is emitted --- lib/WebSocketServer.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index ea818f288..e8b25f2b7 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -103,27 +103,18 @@ util.inherits(WebSocketServer, EventEmitter); */ WebSocketServer.prototype.close = function (cb) { // terminate all associated clients - var error = null; - if (this.clients) { - for (const client of this.clients) { - try { - client.terminate(); - } catch (e) { - error = e; - } - } + for (const client of this.clients) client.terminate(); } if (this._server) { // close the http server if it was internally created - if (this.options.port != null) this._server.close(); + if (this.options.port != null) this._server.close(cb); this._ultron.destroy(); this._ultron = this._server = null; + } else if (cb) { + cb(); } - - if (cb) cb(error); - else if (error) throw error; }; /** From 4056bde03b1e123a7ce8145a1d8a8c097fc451bb Mon Sep 17 00:00:00 2001 From: Antti Risteli Date: Tue, 15 Nov 2016 23:58:23 +0200 Subject: [PATCH 132/489] [test] Fix Autobahn tests (#894) --- test/autobahn-server.js | 32 ++++++-------------------- test/autobahn.js | 51 ++++++++++++----------------------------- 2 files changed, 22 insertions(+), 61 deletions(-) diff --git a/test/autobahn-server.js b/test/autobahn-server.js index 73655d8b3..7d3e92915 100644 --- a/test/autobahn-server.js +++ b/test/autobahn-server.js @@ -1,31 +1,13 @@ 'use strict'; -var WebSocket = require('../'); -var WebSocketServer = WebSocket.Server; +const WebSocket = require('../'); -process.on('uncaughtException', function (err) { - console.log('Caught exception: ', err, err.stack); +const port = process.argv.length > 2 ? parseInt(process.argv[2]) : 9001; +const wss = new WebSocket.Server({ port }, () => { + console.log(`Listening to port ${port}. Use extra argument to define the port`); }); -process.on('SIGINT', function () { - try { - console.log('Updating reports and shutting down'); - var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function () { - process.exit(); - }); - } catch (e) { - process.exit(); - } -}); - -var wss = new WebSocketServer({port: 8181}); -wss.on('connection', function (ws) { - console.log('new connection'); - ws.on('message', function (data, flags) { - ws.send(flags.buffer, {binary: flags.binary === true}); - }); - ws.on('error', function () { - console.log('error', arguments); - }); +wss.on('connection', (ws) => { + ws.on('message', (data) => ws.send(data)); + ws.on('error', (e) => console.error(e)); }); diff --git a/test/autobahn.js b/test/autobahn.js index dbe3fefd5..cf2492494 100644 --- a/test/autobahn.js +++ b/test/autobahn.js @@ -1,55 +1,34 @@ 'use strict'; -var WebSocket = require('../'); -var currentTest = 1; -var lastTest = -1; -var testCount = null; +const WebSocket = require('../'); -process.on('uncaughtException', function (err) { - console.log('Caught exception: ', err, err.stack); -}); - -process.on('SIGINT', function () { - try { - console.log('Updating reports and shutting down'); - var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function () { - process.exit(); - }); - } catch (e) { - process.exit(); - } -}); +let currentTest = 1; +let testCount; function nextTest () { - var ws; + let ws; - if (currentTest > testCount || (lastTest !== -1 && currentTest > lastTest)) { - console.log('Updating reports and shutting down'); + if (currentTest > testCount) { ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function () { - process.exit(); - }); return; } - console.log('Running test case ' + currentTest + '/' + testCount); - ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); - ws.on('message', function (data, flags) { - ws.send(flags.buffer, {binary: flags.binary === true, mask: true}); - }); - ws.on('close', function (data) { - currentTest += 1; + console.log(`Running test case ${currentTest}/${testCount}`); + + ws = new WebSocket(`ws://localhost:9001/runCase?case=${currentTest}&agent=ws`); + ws.on('message', (data) => ws.send(data)); + ws.on('close', () => { + currentTest++; process.nextTick(nextTest); }); - ws.on('error', function (e) {}); + ws.on('error', (e) => console.error(e)); } -var ws = new WebSocket('ws://localhost:9001/getCaseCount'); -ws.on('message', function (data, flags) { +const ws = new WebSocket('ws://localhost:9001/getCaseCount'); +ws.on('message', (data) => { testCount = parseInt(data); }); -ws.on('close', function () { +ws.on('close', () => { if (testCount > 0) { nextTest(); } From dcdc652df3532389c9973c5ebe2911509ce685ed Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 16 Nov 2016 15:16:24 +0100 Subject: [PATCH 133/489] [fix] Use `null` as default value for the `host` option Fixes #588 --- lib/WebSocketServer.js | 18 +++++++++--------- test/WebSocketServer.test.js | 36 +++++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4110d7921..f5007000c 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -43,17 +43,17 @@ function WebSocketServer (options, callback) { EventEmitter.call(this); options = Object.assign({ - host: '0.0.0.0', - port: null, - server: null, - verifyClient: null, + maxPayload: 100 * 1024 * 1024, + perMessageDeflate: true, handleProtocols: null, - path: null, - noServer: false, clientTracking: true, - perMessageDeflate: true, - maxPayload: 100 * 1024 * 1024, - backlog: null // use default (511 as implemented in net.js) + verifyClient: null, + noServer: false, + backlog: null, // use default (511 as implemented in net.js) + server: null, + host: null, + path: null, + port: null }, options); if (options.port == null && !options.server && !options.noServer) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 2bfa6aafa..282676fc0 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -31,19 +31,30 @@ describe('WebSocketServer', function () { it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocketServer({ port: 50003 }); const wss2 = new WebSocketServer({ port: 50003 }); - wss2.on('error', () => { - wss1.close(); - done(); - }); + + wss2.on('error', () => wss1.close(done)); }); it('starts a server on a given port', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', (client) => { - wss.close(); - done(); + + wss.on('connection', (client) => wss.close(done)); + }); + + it('binds the server on any IPv6 address when available', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const address = wss._server.address().address; + + // The Trusty build environment does not have IPv6 connectivity. + if (process.env.TRAVIS) { + assert.strictEqual(address, '0.0.0.0'); + } else { + assert.strictEqual(address, '::'); + } + + wss.close(done); }); }); @@ -70,15 +81,14 @@ describe('WebSocketServer', function () { res.on('data', (chunk) => { body += chunk; }); res.on('end', () => { assert.strictEqual(body, http.STATUS_CODES[426]); - wss.close(); - done(); + wss.close(done); }); }); }); }); // Don't test this on Windows. It throws errors for obvious reasons. - if (!/^win/i.test(process.platform)) { + if (process.platform !== 'win32') { it('uses a precreated http server listening on unix socket', function (done) { const server = http.createServer(); const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; @@ -110,13 +120,13 @@ describe('WebSocketServer', function () { }); it('will not crash when it receives an unhandled opcode', function (done) { - const wss = new WebSocketServer({ port: 8080 }); + const wss = new WebSocketServer({ port: ++port }); wss.on('connection', (ws) => { - ws.onerror = () => done(); + ws.onerror = () => wss.close(done); }); - const ws = new WebSocket('ws://localhost:8080/'); + const ws = new WebSocket(`ws://localhost:${port}/`); ws.onopen = () => { ws._socket.write(new Buffer([5])); From c917a9dc5f3babd787daa2692dd38279b336ed18 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 16 Nov 2016 15:30:08 +0100 Subject: [PATCH 134/489] [test] Fix failing test --- test/WebSocketServer.test.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 282676fc0..b1f1d192d 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -45,15 +45,7 @@ describe('WebSocketServer', function () { it('binds the server on any IPv6 address when available', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const address = wss._server.address().address; - - // The Trusty build environment does not have IPv6 connectivity. - if (process.env.TRAVIS) { - assert.strictEqual(address, '0.0.0.0'); - } else { - assert.strictEqual(address, '::'); - } - + assert.strictEqual(wss._server.address().address, '::'); wss.close(done); }); }); From 04530ad939c1fed63e2e5ef26bca250e8a4b1e7f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 16 Nov 2016 20:15:42 +0100 Subject: [PATCH 135/489] [fix] Don't emit the connection event if socket is closed prematurely Fixes #380 --- lib/WebSocketServer.js | 2 +- test/WebSocketServer.test.js | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index f5007000c..249306585 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -221,7 +221,7 @@ function upgrade (req, socket, head, cb) { // allows external modification/inspection of handshake headers this.emit('headers', headers); - if (socket.writable) { + if (socket.readable && socket.writable) { socket.write(headers.concat('', '').join('\r\n')); } else { socket.destroy(); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index b1f1d192d..03a5ca16c 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -5,6 +5,7 @@ const assert = require('assert'); const https = require('https'); const http = require('http'); +const net = require('net'); const fs = require('fs'); const WebSocket = require('..'); @@ -726,6 +727,43 @@ describe('WebSocketServer', function () { }); }); + it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) { + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => setTimeout(cb, 100, true), + server + }); + + wss.on('connection', () => { + throw new Error('connection event emitted'); + }); + + const socket = net.connect({ host: 'localhost', port }, () => { + socket.write([ + 'GET / HTTP/1.1', + 'Host: localhost', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version: 13', + '', + '' + ].join('\r\n')); + }); + + socket.on('end', () => { + wss.close(); + server.close(done); + }); + + socket.setTimeout(50, () => { + socket.end(); + }); + }); + }); + it('handles messages passed along with the upgrade request (upgrade head)', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const req = http.request({ From 5583051f85f206a154631ce8deeb968437dac680 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 17 Nov 2016 07:37:49 +0100 Subject: [PATCH 136/489] chore(package): update dependencies (#896) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a2e271bee..57f171bc4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "3.9.x", + "eslint": "3.10.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.3.x", From d93e864938c3fd84ae1fbb4b816f81ede0b825f3 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 09:07:04 +0100 Subject: [PATCH 137/489] [doc] Add more badges --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d7863d60..9b78bb0e5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # ws: a node.js websocket library -[![Build Status](https://travis-ci.org/websockets/ws.svg?branch=master)](https://travis-ci.org/websockets/ws) +[![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) +[![Build Status](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) +[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) `ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, and [probably the fastest WebSocket library for node.js][archive]. From 8cd6dd2704f23edfbcfe768ec4c44f41389101f1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 09:18:49 +0100 Subject: [PATCH 138/489] [travis] Add after-script to send coverage data to coveralls --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1891cb08d..c9c131ecc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,3 +7,5 @@ node_js: - "4" script: - "npm run test-travis" +after_script: + - "npm install coveralls@2 && cat coverage/lcov.info | coveralls" From f2ac5f151393b9b664f5be59b0a8e06344d790be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 15:26:44 +0100 Subject: [PATCH 139/489] [codestyle] Fix nit --- test/WebSocketServer.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 03a5ca16c..cc28896b7 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -758,9 +758,7 @@ describe('WebSocketServer', function () { server.close(done); }); - socket.setTimeout(50, () => { - socket.end(); - }); + socket.setTimeout(50, () => socket.end()); }); }); From 3e1aa854dee6c1c4c9c1d820ff907356841949ce Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 16:20:36 +0100 Subject: [PATCH 140/489] [minor] Use arrow functions for lexical this in lib/PerMessageDeflate.js --- lib/PerMessageDeflate.js | 112 ++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 37162c0d1..6c2278d7c 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -98,7 +98,7 @@ class PerMessageDeflate { acceptAsServer (paramsList) { var accepted = {}; - var result = paramsList.some(function (params) { + var result = paramsList.some((params) => { accepted = {}; if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { return; @@ -135,7 +135,7 @@ class PerMessageDeflate { accepted.client_max_window_bits = params.client_max_window_bits; } return true; - }, this); + }); if (!result) { throw new Error(`Doesn't support the offered configuration`); @@ -176,8 +176,8 @@ class PerMessageDeflate { */ normalizeParams (paramsList) { - return paramsList.map(function (params) { - Object.keys(params).forEach(function (key) { + return paramsList.map((params) => { + Object.keys(params).forEach((key) => { var value = params[key]; if (value.length > 1) { throw new Error('Multiple extension parameters for ' + key); @@ -209,9 +209,9 @@ class PerMessageDeflate { default: throw new Error(`Not defined extension parameter (${key})`); } - }, this); + }); return params; - }, this); + }); } /** @@ -230,30 +230,14 @@ class PerMessageDeflate { } this._inflate.writeInProgress = true; - var self = this; - var buffers = []; + const buffers = []; var cumulativeBufferLength = 0; - this._inflate.on('error', onError).on('data', onData); - this._inflate.write(data); - if (fin) { - this._inflate.write(TRAILER); - } - this._inflate.flush(function () { - cleanup(); - callback(null, Buffer.concat(buffers)); - }); - - function onError (err) { - cleanup(); - callback(err); - } - - function onData (data) { - if (self._maxPayload > 0) { + const onData = (data) => { + if (this._maxPayload > 0) { cumulativeBufferLength += data.length; - if (cumulativeBufferLength > self._maxPayload) { - const err = new Error(`payload cannot exceed ${self._maxPayload} bytes`); + if (cumulativeBufferLength > this._maxPayload) { + const err = new Error(`payload cannot exceed ${this._maxPayload} bytes`); err.closeCode = 1009; buffers.length = 0; cleanup(); @@ -262,18 +246,33 @@ class PerMessageDeflate { } } buffers.push(data); - } + }; - function cleanup () { - if (!self._inflate) return; - self._inflate.removeListener('error', onError); - self._inflate.removeListener('data', onData); - self._inflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { - self._inflate.close(); - self._inflate = null; + const onError = (err) => { + cleanup(); + callback(err); + }; + + const cleanup = () => { + if (!this._inflate) return; + this._inflate.removeListener('error', onError); + this._inflate.removeListener('data', onData); + this._inflate.writeInProgress = false; + if ((fin && this.params[endpoint + '_no_context_takeover']) || this._inflate.pendingClose) { + this._inflate.close(); + this._inflate = null; } + }; + + this._inflate.on('error', onError).on('data', onData); + this._inflate.write(data); + if (fin) { + this._inflate.write(TRAILER); } + this._inflate.flush(() => { + cleanup(); + callback(null, Buffer.concat(buffers)); + }); } /** @@ -299,12 +298,27 @@ class PerMessageDeflate { } this._deflate.writeInProgress = true; - var self = this; - var buffers = []; + const buffers = []; + + const onData = (data) => buffers.push(data); + const onError = (err) => { + cleanup(); + callback(err); + }; + const cleanup = () => { + if (!this._deflate) return; + this._deflate.removeListener('error', onError); + this._deflate.removeListener('data', onData); + this._deflate.writeInProgress = false; + if ((fin && this.params[endpoint + '_no_context_takeover']) || this._deflate.pendingClose) { + this._deflate.close(); + this._deflate = null; + } + }; this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(zlib.Z_SYNC_FLUSH, function () { + this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { cleanup(); var data = Buffer.concat(buffers); if (fin) { @@ -312,26 +326,6 @@ class PerMessageDeflate { } callback(null, data); }); - - function onError (err) { - cleanup(); - callback(err); - } - - function onData (data) { - buffers.push(data); - } - - function cleanup () { - if (!self._deflate) return; - self._deflate.removeListener('error', onError); - self._deflate.removeListener('data', onData); - self._deflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { - self._deflate.close(); - self._deflate = null; - } - } } } From 44950b668fab76a1baa17f43075992c7b8abfb90 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 17:38:46 +0100 Subject: [PATCH 141/489] [test] Fix flaky test --- test/WebSocketServer.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index cc28896b7..0f832f55f 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -30,10 +30,11 @@ describe('WebSocketServer', function () { }); it('emits an error if http server bind fails', function (done) { - const wss1 = new WebSocketServer({ port: 50003 }); - const wss2 = new WebSocketServer({ port: 50003 }); + const wss1 = new WebSocketServer({ port: 50003 }, () => { + const wss2 = new WebSocketServer({ port: 50003 }); - wss2.on('error', () => wss1.close(done)); + wss2.on('error', () => wss1.close(done)); + }); }); it('starts a server on a given port', function (done) { From 40291cb8de609ab58ee5d4e9003b8e2fc993f745 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 15:00:04 +0100 Subject: [PATCH 142/489] [minor] Move `completeUpgrade` to `WebSocketServer.prototype` --- lib/WebSocketServer.js | 227 ++++++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 104 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 249306585..08ae3feb9 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -17,6 +17,8 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const Extensions = require('./Extensions'); const WebSocket = require('./WebSocket'); +const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + /** * Create a `WebSocketServer` instance. * @@ -142,110 +144,25 @@ WebSocketServer.prototype.shouldHandle = function (req) { * @public */ WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { + socket.on('error', socketError); + + const version = +req.headers['sec-websocket-version']; + if ( !this.shouldHandle(req) || !req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] + !req.headers['sec-websocket-key'] || + version !== 8 && version !== 13 ) { return abortConnection(socket, 400); } - socket.on('error', socketError); - upgrade.apply(this, arguments); -}; - -module.exports = WebSocketServer; - -/** - * Handle premature socket errors. - * - * @private - */ -function socketError () { - this.destroy(); -} - -/** - * Upgrade the connection to WebSocket. - * - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Function} cb Callback - * @private - */ -function upgrade (req, socket, head, cb) { - const version = +req.headers['sec-websocket-version']; - - if (version !== 8 && version !== 13) return abortConnection(socket, 400); - var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); - // handler to call when the connection sequence completes - const completeUpgrade = () => { - // calc key - const key = crypto.createHash('sha1') - .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') - .digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - `Sec-WebSocket-Accept: ${key}` - ]; - - if (protocol) { - headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - } - - const offer = Extensions.parse(req.headers['sec-websocket-extensions']); - var extensions; - - try { - extensions = acceptExtensions.call(this, offer); - } catch (err) { - return abortConnection(socket, 400); - } - - if (Object.keys(extensions).length) { - const serverExtensions = Object.keys(extensions).reduce((obj, key) => { - obj[key] = [extensions[key].params]; - return obj; - }, {}); - - headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); - } - - // allows external modification/inspection of handshake headers - this.emit('headers', headers); - - if (socket.readable && socket.writable) { - socket.write(headers.concat('', '').join('\r\n')); - } else { - socket.destroy(); - return; - } - - const client = new WebSocket([req, socket, head], { - maxPayload: this.options.maxPayload, - protocolVersion: version, - extensions, - protocol - }); - - if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); - } - - // signal upgrade complete - socket.removeListener('error', socketError); - cb(client); - }; - - // optionally call external protocol selection handler + // + // Optionally call external protocol selection handler. + // if (this.options.handleProtocols) { protocol = this.options.handleProtocols(protocol); if (protocol === false) return abortConnection(socket, 401); @@ -253,7 +170,9 @@ function upgrade (req, socket, head, cb) { protocol = protocol[0]; } - // optionally call external client verification handler + // + // Optionally call external client verification handler. + // if (this.options.verifyClient) { const info = { origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], @@ -265,7 +184,7 @@ function upgrade (req, socket, head, cb) { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - completeUpgrade(); + this.completeUpgrade(protocol, version, req, socket, head, cb); }); return; } else if (!this.options.verifyClient(info)) { @@ -273,18 +192,116 @@ function upgrade (req, socket, head, cb) { } } - completeUpgrade(); + this.completeUpgrade(protocol, version, req, socket, head, cb); +}; + +/** + * Upgrade the connection to WebSocket. + * + * @param {String} protocol The chosen subprotocol + * @param {Number} version The WebSocket protocol version + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @private + */ +WebSocketServer.prototype.completeUpgrade = function (protocol, version, req, socket, head, cb) { + // + // Destroy the socket if the client has already sent a FIN packet. + // + if (!socket.readable || !socket.writable) return socket.destroy(); + + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + GUID, 'binary') + .digest('base64'); + + const headers = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Accept: ${key}` + ]; + + if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + + const offer = Extensions.parse(req.headers['sec-websocket-extensions']); + var extensions; + + try { + extensions = acceptExtensions(this.options, offer); + } catch (err) { + return abortConnection(socket, 400); + } + + const props = Object.keys(extensions); + + if (props.length) { + const serverExtensions = props.reduce((obj, key) => { + obj[key] = [extensions[key].params]; + return obj; + }, {}); + + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); + } + + // + // Allow external modification/inspection of handshake headers. + // + this.emit('headers', headers); + + socket.write(headers.concat('', '').join('\r\n')); + + const client = new WebSocket([req, socket, head], { + maxPayload: this.options.maxPayload, + protocolVersion: version, + extensions, + protocol + }); + + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); + } + + socket.removeListener('error', socketError); + cb(client); +}; + +module.exports = WebSocketServer; + +/** + * Handle premature socket errors. + * + * @private + */ +function socketError () { + this.destroy(); } -function acceptExtensions (offer) { - var extensions = {}; - var options = this.options.perMessageDeflate; - var maxPayload = this.options.maxPayload; - if (options && offer[PerMessageDeflate.extensionName]) { - var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true, maxPayload); +/** + * Accept WebSocket extensions. + * + * @param {Object} options The `WebSocketServer` configuration options + * @param {Object} offer The parsed value of the `sec-websocket-extensions` header + * @return {Object} Accepted extensions + * @private + */ +function acceptExtensions (options, offer) { + const pmd = options.perMessageDeflate; + const extensions = {}; + + if (pmd && offer[PerMessageDeflate.extensionName]) { + const perMessageDeflate = new PerMessageDeflate( + pmd !== true ? pmd : {}, + true, + options.maxPayload + ); + perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]); extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } + return extensions; } @@ -294,7 +311,7 @@ function acceptExtensions (offer) { * @param {net.Socket} socket The socket of the upgrade request * @param {Number} code The HTTP response status code * @param {String} [message] The HTTP response body - * @api private + * @private */ function abortConnection (socket, code, message) { if (socket.writable) { @@ -308,5 +325,7 @@ function abortConnection (socket, code, message) { message ); } + + socket.removeListener('error', socketError); socket.destroy(); } From ef0a379d88dbf6d951c6880f9b8f506437a4ca84 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 17 Nov 2016 19:17:04 +0000 Subject: [PATCH 143/489] chore(package): update eslint-plugin-promise to version 3.4.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57f171bc4..93092d2ff 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "eslint": "3.10.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", - "eslint-plugin-promise": "3.3.x", + "eslint-plugin-promise": "~3.4.0", "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", "mocha": "3.1.x", From 199c248a4a65d175fdcb351ff1d29c87debaf855 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 18 Nov 2016 10:43:40 +0100 Subject: [PATCH 144/489] [minor] Remove redundant checks --- lib/WebSocket.js | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 511a63f9d..092f776cb 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -773,18 +773,18 @@ function initAsClient (address, protocols, options) { } function establishConnection (socket, upgradeHead) { - var ultron = this._ultron = new Ultron(socket); - socket.setTimeout(0); - socket.setNoDelay(true); + socket.setNoDelay(); this._receiver = new Receiver(this.extensions, this.maxPayload); + this._sender = new Sender(socket, this.extensions); + this._ultron = new Ultron(socket); this._socket = socket; // socket cleanup handlers - ultron.on('end', cleanupWebsocketResources.bind(this)); - ultron.on('close', cleanupWebsocketResources.bind(this)); - ultron.on('error', cleanupWebsocketResources.bind(this)); + this._ultron.on('end', cleanupWebsocketResources.bind(this)); + this._ultron.on('close', cleanupWebsocketResources.bind(this)); + this._ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver if (upgradeHead && upgradeHead.length > 0) { @@ -793,49 +793,33 @@ function establishConnection (socket, upgradeHead) { } // subsequent packets are pushed to the receiver - ultron.on('data', (data) => { + this._ultron.on('data', (data) => { this.bytesReceived += data.length; this._receiver.add(data); }); // receiver event handlers - this._receiver.ontext = (data, flags) => this.emit('message', data, flags || {}); - + this._receiver.ontext = (data, flags) => this.emit('message', data, flags); this._receiver.onbinary = (data, flags) => { - flags = flags || {}; flags.binary = true; - this.emit('message', data, flags); }; - this._receiver.onping = (data, flags) => { - flags = flags || {}; - - this.pong(data, { - mask: !this._isServer, - binary: flags.binary === true - }, true); - + this.pong(data, { mask: !this._isServer }, true); this.emit('ping', data, flags); }; - - this._receiver.onpong = (data, flags) => this.emit('pong', data, flags || {}); - + this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); this._receiver.onclose = (code, data, flags) => { - flags = flags || {}; - this._closeReceived = true; this.close(code, data); }; - this._receiver.onerror = (error, errorCode) => { // close the connection when the receiver reports a HyBi error code this.close(errorCode, ''); this.emit('error', error); }; - // finalize the client - this._sender = new Sender(socket, this.extensions); + // sender event handlers this._sender.onerror = (error) => { this.close(1002, ''); this.emit('error', error); From e4e6163425896e9e8a4561f01e7566de33334574 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 23 Nov 2016 09:15:57 +0100 Subject: [PATCH 145/489] [major] Refactor `WebSocketServer` to use class syntax (#903) --- lib/WebSocketServer.js | 434 +++++++++++++++++------------------ test/WebSocketServer.test.js | 6 - 2 files changed, 215 insertions(+), 225 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 08ae3feb9..a0024760e 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -10,7 +10,6 @@ const EventEmitter = require('events'); const crypto = require('crypto'); const Ultron = require('ultron'); const http = require('http'); -const util = require('util'); const url = require('url'); const PerMessageDeflate = require('./PerMessageDeflate'); @@ -20,253 +19,250 @@ const WebSocket = require('./WebSocket'); const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; /** - * Create a `WebSocketServer` instance. - * - * @param {Object} options Configuration options - * @param {String} options.host The hostname where to bind the server - * @param {Number} options.port The port where to bind the server - * @param {http.Server} options.server A pre-created HTTP/S server to use - * @param {Function} options.verifyClient An hook to reject connections - * @param {Function} options.handleProtocols An hook to handle protocols - * @param {String} options.path Accept only connections matching this path - * @param {Boolean} options.noServer Enable no server mode - * @param {Boolean} options.clientTracking Specifies whether or not to track clients - * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate - * @param {Number} options.maxPayload The maximum allowed message size - * @param {Function} callback A listener for the `listening` event - * @constructor - * @public + * Class representing a WebSocket server. */ -function WebSocketServer (options, callback) { - if (this instanceof WebSocketServer === false) { - return new WebSocketServer(options, callback); - } - - EventEmitter.call(this); - - options = Object.assign({ - maxPayload: 100 * 1024 * 1024, - perMessageDeflate: true, - handleProtocols: null, - clientTracking: true, - verifyClient: null, - noServer: false, - backlog: null, // use default (511 as implemented in net.js) - server: null, - host: null, - path: null, - port: null - }, options); - - if (options.port == null && !options.server && !options.noServer) { - throw new TypeError('missing or invalid options'); - } +class WebSocketServer extends EventEmitter { + /** + * Create a `WebSocketServer` instance. + * + * @param {Object} options Configuration options + * @param {String} options.host The hostname where to bind the server + * @param {Number} options.port The port where to bind the server + * @param {http.Server} options.server A pre-created HTTP/S server to use + * @param {Function} options.verifyClient An hook to reject connections + * @param {Function} options.handleProtocols An hook to handle protocols + * @param {String} options.path Accept only connections matching this path + * @param {Boolean} options.noServer Enable no server mode + * @param {Boolean} options.clientTracking Specifies whether or not to track clients + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate + * @param {Number} options.maxPayload The maximum allowed message size + * @param {Function} callback A listener for the `listening` event + */ + constructor (options, callback) { + super(); + + options = Object.assign({ + maxPayload: 100 * 1024 * 1024, + perMessageDeflate: true, + handleProtocols: null, + clientTracking: true, + verifyClient: null, + noServer: false, + backlog: null, // use default (511 as implemented in net.js) + server: null, + host: null, + path: null, + port: null + }, options); + + if (options.port == null && !options.server && !options.noServer) { + throw new TypeError('missing or invalid options'); + } - if (options.port != null) { - this._server = http.createServer((req, res) => { - const body = http.STATUS_CODES[426]; + if (options.port != null) { + this._server = http.createServer((req, res) => { + const body = http.STATUS_CODES[426]; - res.writeHead(426, { - 'Content-Length': body.length, - 'Content-Type': 'text/plain' + res.writeHead(426, { + 'Content-Length': body.length, + 'Content-Type': 'text/plain' + }); + res.end(body); }); - res.end(body); - }); - this._server.allowHalfOpen = false; - this._server.listen(options.port, options.host, options.backlog, callback); - } else if (options.server) { - this._server = options.server; - } + this._server.allowHalfOpen = false; + this._server.listen(options.port, options.host, options.backlog, callback); + } else if (options.server) { + this._server = options.server; + } - if (this._server) { - this._ultron = new Ultron(this._server); - this._ultron.on('listening', () => this.emit('listening')); - this._ultron.on('error', (err) => this.emit('error', err)); - this._ultron.on('upgrade', (req, socket, head) => { - this.handleUpgrade(req, socket, head, (client) => { - this.emit(`connection${req.url}`, client); - this.emit('connection', client); + if (this._server) { + this._ultron = new Ultron(this._server); + this._ultron.on('listening', () => this.emit('listening')); + this._ultron.on('error', (err) => this.emit('error', err)); + this._ultron.on('upgrade', (req, socket, head) => { + this.handleUpgrade(req, socket, head, (client) => { + this.emit(`connection${req.url}`, client); + this.emit('connection', client); + }); }); - }); - } - - if (options.clientTracking) this.clients = new Set(); - this.options = options; - this.path = options.path; -} - -util.inherits(WebSocketServer, EventEmitter); + } -/** - * Close the server. - * - * @param {Function} cb Callback - * @public - */ -WebSocketServer.prototype.close = function (cb) { - // terminate all associated clients - if (this.clients) { - for (const client of this.clients) client.terminate(); + if (options.clientTracking) this.clients = new Set(); + this.options = options; + this.path = options.path; } - if (this._server) { - // close the http server if it was internally created - if (this.options.port != null) this._server.close(cb); - this._ultron.destroy(); - this._ultron = this._server = null; - } else if (cb) { - cb(); - } -}; + /** + * Close the server. + * + * @param {Function} cb Callback + * @public + */ + close (cb) { + // terminate all associated clients + if (this.clients) { + for (const client of this.clients) client.terminate(); + } -/** - * See if a given request should be handled by this server instance. - * - * @param {http.IncomingMessage} req Request object to inspect - * @return {Boolean} `true` if the request is valid, else `false` - * @public - */ -WebSocketServer.prototype.shouldHandle = function (req) { - if (this.options.path && url.parse(req.url).pathname !== this.options.path) { - return false; + if (this._server) { + // close the http server if it was internally created + if (this.options.port != null) this._server.close(cb); + this._ultron.destroy(); + this._ultron = this._server = null; + } else if (cb) { + cb(); + } } - return true; -}; + /** + * See if a given request should be handled by this server instance. + * + * @param {http.IncomingMessage} req Request object to inspect + * @return {Boolean} `true` if the request is valid, else `false` + * @public + */ + shouldHandle (req) { + if (this.options.path && url.parse(req.url).pathname !== this.options.path) { + return false; + } -/** - * Handle a HTTP Upgrade request. - * - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Function} cb Callback - * @public - */ -WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { - socket.on('error', socketError); - - const version = +req.headers['sec-websocket-version']; - - if ( - !this.shouldHandle(req) || - !req.headers.upgrade || - req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] || - version !== 8 && version !== 13 - ) { - return abortConnection(socket, 400); + return true; } - var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); + /** + * Handle a HTTP Upgrade request. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @public + */ + handleUpgrade (req, socket, head, cb) { + socket.on('error', socketError); + + const version = +req.headers['sec-websocket-version']; + + if ( + !this.shouldHandle(req) || + !req.headers.upgrade || + req.headers.upgrade.toLowerCase() !== 'websocket' || + !req.headers['sec-websocket-key'] || + version !== 8 && version !== 13 + ) { + return abortConnection(socket, 400); + } - // - // Optionally call external protocol selection handler. - // - if (this.options.handleProtocols) { - protocol = this.options.handleProtocols(protocol); - if (protocol === false) return abortConnection(socket, 401); - } else { - protocol = protocol[0]; - } + var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); - // - // Optionally call external client verification handler. - // - if (this.options.verifyClient) { - const info = { - origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], - secure: !!(req.connection.authorized || req.connection.encrypted), - req - }; - - if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (verified, code, message) => { - if (!verified) return abortConnection(socket, code || 401, message); - - this.completeUpgrade(protocol, version, req, socket, head, cb); - }); - return; - } else if (!this.options.verifyClient(info)) { - return abortConnection(socket, 401); + // + // Optionally call external protocol selection handler. + // + if (this.options.handleProtocols) { + protocol = this.options.handleProtocols(protocol); + if (protocol === false) return abortConnection(socket, 401); + } else { + protocol = protocol[0]; } - } - this.completeUpgrade(protocol, version, req, socket, head, cb); -}; + // + // Optionally call external client verification handler. + // + if (this.options.verifyClient) { + const info = { + origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], + secure: !!(req.connection.authorized || req.connection.encrypted), + req + }; + + if (this.options.verifyClient.length === 2) { + this.options.verifyClient(info, (verified, code, message) => { + if (!verified) return abortConnection(socket, code || 401, message); + + this.completeUpgrade(protocol, version, req, socket, head, cb); + }); + return; + } else if (!this.options.verifyClient(info)) { + return abortConnection(socket, 401); + } + } -/** - * Upgrade the connection to WebSocket. - * - * @param {String} protocol The chosen subprotocol - * @param {Number} version The WebSocket protocol version - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Function} cb Callback - * @private - */ -WebSocketServer.prototype.completeUpgrade = function (protocol, version, req, socket, head, cb) { - // - // Destroy the socket if the client has already sent a FIN packet. - // - if (!socket.readable || !socket.writable) return socket.destroy(); - - const key = crypto.createHash('sha1') - .update(req.headers['sec-websocket-key'] + GUID, 'binary') - .digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - `Sec-WebSocket-Accept: ${key}` - ]; - - if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - - const offer = Extensions.parse(req.headers['sec-websocket-extensions']); - var extensions; - - try { - extensions = acceptExtensions(this.options, offer); - } catch (err) { - return abortConnection(socket, 400); + this.completeUpgrade(protocol, version, req, socket, head, cb); } - const props = Object.keys(extensions); + /** + * Upgrade the connection to WebSocket. + * + * @param {String} protocol The chosen subprotocol + * @param {Number} version The WebSocket protocol version + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @private + */ + completeUpgrade (protocol, version, req, socket, head, cb) { + // + // Destroy the socket if the client has already sent a FIN packet. + // + if (!socket.readable || !socket.writable) return socket.destroy(); + + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + GUID, 'binary') + .digest('base64'); + + const headers = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Accept: ${key}` + ]; + + if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + + const offer = Extensions.parse(req.headers['sec-websocket-extensions']); + var extensions; + + try { + extensions = acceptExtensions(this.options, offer); + } catch (err) { + return abortConnection(socket, 400); + } - if (props.length) { - const serverExtensions = props.reduce((obj, key) => { - obj[key] = [extensions[key].params]; - return obj; - }, {}); + const props = Object.keys(extensions); - headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); - } + if (props.length) { + const serverExtensions = props.reduce((obj, key) => { + obj[key] = [extensions[key].params]; + return obj; + }, {}); - // - // Allow external modification/inspection of handshake headers. - // - this.emit('headers', headers); + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); + } - socket.write(headers.concat('', '').join('\r\n')); + // + // Allow external modification/inspection of handshake headers. + // + this.emit('headers', headers); - const client = new WebSocket([req, socket, head], { - maxPayload: this.options.maxPayload, - protocolVersion: version, - extensions, - protocol - }); + socket.write(headers.concat('', '').join('\r\n')); - if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); - } + const client = new WebSocket([req, socket, head], { + maxPayload: this.options.maxPayload, + protocolVersion: version, + extensions, + protocol + }); - socket.removeListener('error', socketError); - cb(client); -}; + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); + } + + socket.removeListener('error', socketError); + cb(client); + } +} module.exports = WebSocketServer; diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 0f832f55f..e79aa5257 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -23,12 +23,6 @@ describe('WebSocketServer', function () { assert.throws(() => new WebSocketServer({})); }); - it('should return a new instance if called without new', function () { - const wss = WebSocketServer({ noServer: true }); - - assert.ok(wss instanceof WebSocketServer); - }); - it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocketServer({ port: 50003 }, () => { const wss2 = new WebSocketServer({ port: 50003 }); From d2997e733fc7f0ce362b86d1ce49f429cf215dd1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 23 Nov 2016 09:16:43 +0100 Subject: [PATCH 146/489] [minor] Rename some methods of the `Sender` class (#905) --- lib/Sender.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index de6fda611..792af4852 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,9 +6,9 @@ 'use strict'; -const ErrorCodes = require('./ErrorCodes'); -const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); +const bufferUtil = require('./BufferUtil').BufferUtil; +const ErrorCodes = require('./ErrorCodes'); /** * HyBi Sender implementation. @@ -21,13 +21,13 @@ class Sender { * @param {Object} extensions An object containing the negotiated extensions */ constructor (socket, extensions) { - this._socket = socket; this.extensions = extensions || {}; this.firstFragment = true; - this.compress = false; - this.messageHandlers = []; this.processing = false; + this.compress = false; + this._socket = socket; this.onerror = null; + this.queue = []; } /** @@ -67,7 +67,7 @@ class Sender { doClose (data, mask, cb) { this.frameAndSend(0x08, data, false, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { - this.messageHandlerCallback(); + this.continue(); } if (cb) cb(); } @@ -98,7 +98,7 @@ class Sender { doPing (data, mask) { this.frameAndSend(0x09, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { - this.messageHandlerCallback(); + this.continue(); } } @@ -128,7 +128,7 @@ class Sender { doPong (data, mask) { this.frameAndSend(0x0a, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { - this.messageHandlerCallback(); + this.continue(); } } @@ -182,7 +182,7 @@ class Sender { sendCompressed (opcode, data, fin, mask, rsv1, cb) { if (!this.compress) { this.frameAndSend(opcode, data, true, fin, mask, false, cb); - this.messageHandlerCallback(); + this.continue(); return; } @@ -195,7 +195,7 @@ class Sender { } this.frameAndSend(opcode, buf, false, fin, mask, rsv1, cb); - this.messageHandlerCallback(); + this.continue(); }); } @@ -289,10 +289,10 @@ class Sender { * * @private */ - flush () { + dequeue () { if (this.processing) return; - var handler = this.messageHandlers.shift(); + const handler = this.queue.shift(); if (!handler) return; this.processing = true; @@ -305,10 +305,10 @@ class Sender { * * @private */ - messageHandlerCallback () { + continue () { process.nextTick(() => { this.processing = false; - this.flush(); + this.dequeue(); }); } @@ -319,8 +319,8 @@ class Sender { * @private */ enqueue (params) { - this.messageHandlers.push(params); - this.flush(); + this.queue.push(params); + this.dequeue(); } } From abc5c965b531f4ddce5c02478f941b13ec5f1996 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 25 Nov 2016 08:48:32 +0100 Subject: [PATCH 147/489] chore(package): update mocha to version 3.2.0 (#912) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93092d2ff..a9eae337f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint-plugin-promise": "~3.4.0", "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", - "mocha": "3.1.x", + "mocha": "~3.2.0", "utf-8-validate": "1.2.x" } } From 269dff8bfe99437c77f5ef558278acb120f929ec Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 25 Nov 2016 10:48:33 +0100 Subject: [PATCH 148/489] [deps] Use tilde ranges --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index a9eae337f..972d00e1b 100644 --- a/package.json +++ b/package.json @@ -25,18 +25,18 @@ "lint": "eslint ." }, "dependencies": { - "ultron": "1.0.x" + "ultron": "~1.0.2" }, "devDependencies": { - "benchmark": "2.1.x", - "bufferutil": "1.2.x", - "eslint": "3.10.x", - "eslint-config-semistandard": "7.0.x", - "eslint-config-standard": "6.2.x", + "benchmark": "~2.1.2", + "bufferutil": "~1.2.1", + "eslint": "~3.10.2", + "eslint-config-semistandard": "~7.0.0", + "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", - "eslint-plugin-standard": "2.0.x", - "istanbul": "0.4.x", + "eslint-plugin-standard": "~2.0.1", + "istanbul": "~0.4.5", "mocha": "~3.2.0", - "utf-8-validate": "1.2.x" + "utf-8-validate": "~1.2.1" } } From 7253f06f5432c76f3e82e2c055fcea08b612d8b2 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 20 Sep 2016 08:59:58 -0600 Subject: [PATCH 149/489] [fix] Use `crypto.randomBytes()` to generate the masking key --- lib/Sender.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 792af4852..e0393bc65 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,6 +6,8 @@ 'use strict'; +const crypto = require('crypto'); + const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil').BufferUtil; const ErrorCodes = require('./ErrorCodes'); @@ -364,12 +366,7 @@ function toBuffer (data) { * @private */ function getRandomMask () { - return new Buffer([ - ~~(Math.random() * 255), - ~~(Math.random() * 255), - ~~(Math.random() * 255), - ~~(Math.random() * 255) - ]); + return crypto.randomBytes(4); } /** From 078e96a31dd7c1c9fee9f51db806e91db7b5aeac Mon Sep 17 00:00:00 2001 From: Hans-Peter Herzog Date: Sat, 6 Aug 2016 17:32:09 +0200 Subject: [PATCH 150/489] [feature] Add removeEventListener method to WebSocket interface --- lib/WebSocket.js | 18 ++++++++++++++++++ test/WebSocket.test.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 092f776cb..bb00ff3a1 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -470,6 +470,24 @@ WebSocket.prototype.addEventListener = function (method, listener) { } }; +/** + * Removes an event listener previously registered with `addEventListener`. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @param {String} method A string representing the event type to remove + * @param {Function} listener The listener to remove + * @public + */ +WebSocket.prototype.removeEventListener = function (method, listener) { + const listeners = this.listeners(method); + + for (var i = 0; i < listeners.length; i++) { + if (listeners[i]._listener === listener) { + this.removeListener(method, listeners[i]); + } + } +}; + module.exports = WebSocket; module.exports.buildHostHeader = buildHostHeader; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 52612c533..a9cefbf9c 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1618,6 +1618,41 @@ describe('WebSocket', function () { }); }); + it('should remove event listeners added with addEventListener', function (done) { + server.createServer(++port, (srv) => { + const message = () => {}; + const open = () => {}; + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('message', message); + ws.addEventListener('open', open); + + assert.notStrictEqual( + ws.listeners('message').find((listener) => listener._listener === message), + undefined + ); + assert.notStrictEqual( + ws.listeners('open').find((listener) => listener._listener === open), + undefined + ); + + ws.removeEventListener('message', message); + ws.removeEventListener('open', open); + + assert.strictEqual( + ws.listeners('message').find((listener) => listener._listener === message), + undefined + ); + assert.strictEqual( + ws.listeners('open').find((listener) => listener._listener === open), + undefined + ); + + srv.close(done); + ws.close(); + }); + }); + it('should receive valid CloseEvent when server closes with code 1000', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); From 2dc201d4587a2f24b37f16df2aa166879ce30367 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 25 Nov 2016 17:13:40 +0100 Subject: [PATCH 151/489] [test] Simplify `removeEventListener` test --- test/WebSocket.test.js | 53 +++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index a9cefbf9c..14bd00abb 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -325,8 +325,8 @@ describe('WebSocket', function () { }); it('can handle error before request is upgraded', function (done) { - // Here, we don't create a server, to guarantee that the connection will - // fail before the request is upgraded + // Here, we don't create a server, to guarantee that the connection will + // fail before the request is upgraded const ws = new WebSocket(`ws://localhost:${++port}`); ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); @@ -1618,39 +1618,24 @@ describe('WebSocket', function () { }); }); - it('should remove event listeners added with addEventListener', function (done) { - server.createServer(++port, (srv) => { - const message = () => {}; - const open = () => {}; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('message', message); - ws.addEventListener('open', open); - - assert.notStrictEqual( - ws.listeners('message').find((listener) => listener._listener === message), - undefined - ); - assert.notStrictEqual( - ws.listeners('open').find((listener) => listener._listener === open), - undefined - ); - - ws.removeEventListener('message', message); - ws.removeEventListener('open', open); - - assert.strictEqual( - ws.listeners('message').find((listener) => listener._listener === message), - undefined - ); - assert.strictEqual( - ws.listeners('open').find((listener) => listener._listener === open), - undefined - ); + it('should remove event listeners added with addEventListener', function () { + const message = () => {}; + const open = () => {}; + const ws = new WebSocket(`ws://localhost:${++port}`); - srv.close(done); - ws.close(); - }); + ws.on('error', () => {}); + + ws.addEventListener('message', message); + ws.addEventListener('open', open); + + assert.strictEqual(ws.listeners('message')[0]._listener, message); + assert.strictEqual(ws.listeners('open')[0]._listener, open); + + ws.removeEventListener('message', message); + ws.removeEventListener('open', open); + + assert.strictEqual(ws.listeners('message').length, 0); + assert.strictEqual(ws.listeners('open').length, 0); }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { From 2a4e78f84946a86bcb1c2a059981470ebd420ab0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 25 Nov 2016 22:23:48 +0000 Subject: [PATCH 152/489] chore(package): update eslint to version 3.11.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 972d00e1b..a28987fe9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.2.1", - "eslint": "~3.10.2", + "eslint": "~3.11.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From d3b98d4428a64401fe9f1e57a6bab95b8ab43f51 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 27 Nov 2016 08:32:28 +0100 Subject: [PATCH 153/489] chore(package): update bufferutil to version 1.3.0 (#915) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a28987fe9..162b61c5b 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "benchmark": "~2.1.2", - "bufferutil": "~1.2.1", + "bufferutil": "~1.3.0", "eslint": "~3.11.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", From 984d35b661b76b304257b078a13f2c5aecd4e44f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 29 Nov 2016 17:31:20 +0100 Subject: [PATCH 154/489] [major] Make error message consistent --- lib/PerMessageDeflate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 6c2278d7c..f8400712b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -237,7 +237,7 @@ class PerMessageDeflate { if (this._maxPayload > 0) { cumulativeBufferLength += data.length; if (cumulativeBufferLength > this._maxPayload) { - const err = new Error(`payload cannot exceed ${this._maxPayload} bytes`); + const err = new Error('max payload size exceeded'); err.closeCode = 1009; buffers.length = 0; cleanup(); From fff499b704319b39c199d6b3d92fd581ff44171d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 29 Nov 2016 17:46:00 +0100 Subject: [PATCH 155/489] [test] Increase code coverage --- test/Receiver.test.js | 282 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 255 insertions(+), 27 deletions(-) diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 5717c7b49..ef5c249e0 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -53,15 +53,16 @@ describe('Receiver', function () { const msg = 'A'.repeat(200); const mask = '3483a868'; - const frame = '81FE' + util.pack(4, msg.length) + mask + - util.mask(msg, mask).toString('hex'); + const frame = Buffer.from('81FE' + util.pack(4, msg.length) + mask + + util.mask(msg, mask).toString('hex'), 'hex'); p.ontext = function (data) { assert.strictEqual(data, msg); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame.slice(0, 2)); + setImmediate(() => p.add(frame.slice(2))); }); it('can parse a really long masked text message', function (done) { @@ -349,7 +350,227 @@ describe('Receiver', function () { assert.strictEqual(p.totalPayloadLength, 0); }); - it('will raise an error on a 200 KiB long masked binary message when maxpayload is 20 KiB', function (done) { + it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); + }); + + it('raises an error when RSV1 is on and opcode is 0', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x40, 0x00])); + }); + + it('raises an error when RSV2 is on', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0xa2, 0x00])); + }); + + it('raises an error when RSV3 is on', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x92, 0x00])); + }); + + it('raises an error if the first frame in a fragmented message has opcode 0', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 0'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x00, 0x00])); + }); + + it('raises an error if a frame has opcode 1 in the middle of a fragmented message', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 1'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x01, 0x00])); + p.add(Buffer.from([0x01, 0x00])); + }); + + it('raises an error if a frame has opcode 2 in the middle of a fragmented message', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 2'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x01, 0x00])); + p.add(Buffer.from([0x02, 0x00])); + }); + + it('raises an error when a control frame has the FIN bit off', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'FIN must be set'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x09, 0x00])); + }); + + it('raises an error when a control frame has the RSV1 bit on', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0xc9, 0x00])); + }); + + it('raises an error when a control frame has the FIN bit off', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'FIN must be set'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x09, 0x00])); + }); + + it('raises an error when a control frame has a payload bigger than 125 B', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid payload length'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x89, 0x7e])); + }); + + it('raises an error when a data frame has a payload bigger than 2^53 - 1 B', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); + assert.strictEqual(code, 1009); + done(); + }; + + p.add(Buffer.from([0x82, 0x7f])); + setImmediate(() => p.add(Buffer.from([ + 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + ]))); + }); + + it('raises an error if a text frame contains invalid UTF-8 data', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual(code, 1007); + done(); + }; + + p.add(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); + }); + + it('raises an error if a close frame has a payload of 1 B', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid payload length'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x88, 0x01, 0x00])); + }); + + it('raises an error if a close frame contains a invalid close code', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid status code: 0'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x88, 0x02, 0x00, 0x00])); + }); + + it('raises an error if a close frame contains invalid UTF-8 data', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual(code, 1007); + done(); + }; + + p.add(Buffer.from([0x88, 0x06, 0x03, 0xef, 0xce, 0xba, 0xe1, 0xbd])); + }); + + it('raises an error on a 200 KiB long masked binary message when `maxPayload` is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -357,7 +578,9 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.error = function (reason, code) { + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -365,14 +588,16 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { + it('raises an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + msg.toString('hex'); - p.error = function (reason, code) { + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -380,50 +605,53 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a compressed message that exceeds maxpayload of 3 B', function (done) { - const perMessageDeflate = new PerMessageDeflate({}, false, 3); + it('raises an error on a compressed message that exceeds `maxPayload`', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 3); - const buf = Buffer.from('Hellooooooooooooooooooooooooooooooooooooooo'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const buf = Buffer.from('A'.repeat(50)); - p.onerror = function (reason, code) { + p.onerror = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf, true, function (err, compressed) { + perMessageDeflate.compress(buf, true, function (err, data) { if (err) return done(err); - p.add(Buffer.from([0xc1, compressed.length])); - p.add(compressed); + p.add(Buffer.from([0xc1, data.length])); + p.add(data); }); }); - it('will raise an error on a compressed fragment that exceeds maxpayload of 2 B', function (done) { - const perMessageDeflate = new PerMessageDeflate({}, false, 2); + it('raises an error if the sum of fragment lengths exceeds `maxPayload`', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); - const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); - const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const buf = Buffer.from('A'.repeat(15)); - p.onerror = function (reason, code) { + p.onerror = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf1, false, function (err, compressed1) { + perMessageDeflate.compress(buf, false, function (err, fragment1) { if (err) return done(err); - p.add(Buffer.from([0x41, compressed1.length])); - p.add(compressed1); + p.add(Buffer.from([0x41, fragment1.length])); + p.add(fragment1); - perMessageDeflate.compress(buf2, true, function (err, compressed2) { + perMessageDeflate.compress(buf, true, function (err, fragment2) { if (err) return done(err); - p.add(Buffer.from([0x80, compressed2.length])); - p.add(compressed2); + p.add(Buffer.from([0x80, fragment2.length])); + p.add(fragment2); }); }); }); From 584b81d7bd4c280b186089c9c7d3b4af8e4d1150 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 29 Nov 2016 19:53:00 +0100 Subject: [PATCH 156/489] [minor] Clean up `WebSocket.prototype.addEventListener()` --- lib/WebSocket.js | 63 +++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb00ff3a1..f61b5dade 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -413,60 +413,51 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { }); /** - * Emulates the W3C Browser based WebSocket interface using addEventListener. + * Registers an event listener emulating the `EventTarget` interface. * - * @see https://developer.mozilla.org/en/DOM/element.addEventListener - * @see http://dev.w3.org/html5/websockets/#the-websocket-interface - * @api public + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @param {String} method A string representing the event type to listen for + * @param {Function} listener The listener to add + * @public */ WebSocket.prototype.addEventListener = function (method, listener) { - var target = this; + if (typeof listener !== 'function') return; function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') { data = new Uint8Array(data).buffer; } - listener.call(target, new MessageEvent(data, !!flags.binary, target)); + listener.call(this, new MessageEvent(data, !!flags.binary, this)); } function onClose (code, message) { - listener.call(target, new CloseEvent(code, message, target)); + listener.call(this, new CloseEvent(code, message, this)); } function onError (event) { event.type = 'error'; - event.target = target; - listener.call(target, event); + event.target = this; + listener.call(this, event); } function onOpen () { - listener.call(target, new OpenEvent(target)); - } - - if (typeof listener === 'function') { - if (method === 'message') { - // store a reference so we can return the original function from the - // addEventListener hook - onMessage._listener = listener; - this.on(method, onMessage); - } else if (method === 'close') { - // store a reference so we can return the original function from the - // addEventListener hook - onClose._listener = listener; - this.on(method, onClose); - } else if (method === 'error') { - // store a reference so we can return the original function from the - // addEventListener hook - onError._listener = listener; - this.on(method, onError); - } else if (method === 'open') { - // store a reference so we can return the original function from the - // addEventListener hook - onOpen._listener = listener; - this.on(method, onOpen); - } else { - this.on(method, listener); - } + listener.call(this, new OpenEvent(this)); + } + + if (method === 'message') { + onMessage._listener = listener; + this.on(method, onMessage); + } else if (method === 'close') { + onClose._listener = listener; + this.on(method, onClose); + } else if (method === 'error') { + onError._listener = listener; + this.on(method, onError); + } else if (method === 'open') { + onOpen._listener = listener; + this.on(method, onOpen); + } else { + this.on(method, listener); } }; From 8e1b092dc94e79c548f8b2e6fb38b8fafefc6b69 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 30 Nov 2016 15:01:24 +0100 Subject: [PATCH 157/489] [minor] Clean up `initAsClient()` --- lib/WebSocket.js | 271 +++++++++++++++++++++++------------------------ 1 file changed, 135 insertions(+), 136 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f61b5dade..8fb99f485 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -6,34 +6,23 @@ 'use strict'; -const url = require('url'); -const util = require('util'); -const http = require('http'); -const https = require('https'); +const EventEmitter = require('events'); const crypto = require('crypto'); const stream = require('stream'); const Ultron = require('ultron'); -const Sender = require('./Sender'); -const Receiver = require('./Receiver'); -const Extensions = require('./Extensions'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const EventEmitter = require('events'); - -var isDefinedAndNonNull = function (options, key) { - return options[key] !== undefined && options[key] !== null; -}; - -/** - * Constants - */ - -// Default protocol version - -var protocolVersion = 13; +const https = require('https'); +const http = require('http'); +const util = require('util'); +const url = require('url'); -// Close timeout +const PerMessageDeflate = require('./PerMessageDeflate'); +const Extensions = require('./Extensions'); +const Receiver = require('./Receiver'); +const Sender = require('./Sender'); -var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly +const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; +const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. +const protocolVersion = 13; /** * WebSocket implementation @@ -549,131 +538,128 @@ function initAsServerClient (req, socket, upgradeHead, options) { establishConnection.call(this, socket, upgradeHead); } +/** + * Initialize a WebSocket client. + * + * @param {String} address The URL to which to connect + * @param {String[]} protocols The list of subprotocols + * @param {Object} options Configuration options + * @param {String} option.protocol Value of the `Sec-WebSocket-Protocol` header + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate + * @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 + * @param {String} options.origin Value of the `Sec-WebSocket-Version` header + * @param {http.Agent} options.agent Use the specified Agent + * @param {String} options.host Value of the `Host` header + * @param {Function} options.checkServerIdentity A function to validate the server hostname + * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate + * @param {String} options.passphrase The passphrase for the private key or pfx + * @param {String} options.ciphers The ciphers to use or exclude + * @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key + * @param {(String|String[]|Buffer|Buffer[])} options.key The private key + * @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs + * @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates + * @private + */ function initAsClient (address, protocols, options) { options = Object.assign({ - origin: null, - protocolVersion: protocolVersion, - host: null, - headers: null, protocol: protocols.join(','), + perMessageDeflate: true, + localAddress: null, + protocolVersion, + headers: null, + origin: null, agent: null, + host: null, - // ssl-related options - pfx: null, - key: null, + // + // SSL options. + // + checkServerIdentity: null, + rejectUnauthorized: null, passphrase: null, - cert: null, - ca: null, ciphers: null, - rejectUnauthorized: null, - checkServerIdentity: null, - perMessageDeflate: true, - localAddress: null + cert: null, + key: null, + pfx: null, + ca: null }, options); if (options.protocolVersion !== 8 && options.protocolVersion !== 13) { throw new Error('unsupported protocol version'); } - // verify URL and establish http class - var serverUrl = url.parse(address); - var isUnixSocket = serverUrl.protocol === 'ws+unix:'; + const serverUrl = url.parse(address); + const isUnixSocket = serverUrl.protocol === 'ws+unix:'; + if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url'); - var isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; - var httpObj = isSecure ? https : http; - var port = serverUrl.port || (isSecure ? 443 : 80); - var auth = serverUrl.auth; - // prepare extensions - var extensionsOffer = {}; + const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; + const key = crypto.randomBytes(16).toString('base64'); + const port = serverUrl.port || (isSecure ? 443 : 80); + const httpObj = isSecure ? https : http; + + // + // Prepare extensions. + // + const extensionsOffer = {}; var perMessageDeflate; + if (options.perMessageDeflate) { - var opts = options.perMessageDeflate !== true ? options.perMessageDeflate : {}; - perMessageDeflate = new PerMessageDeflate(opts, false); + perMessageDeflate = new PerMessageDeflate( + options.perMessageDeflate !== true ? options.perMessageDeflate : {}, + false + ); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } - // expose state properties + // + // Expose state properties. + // + this.protocolVersion = options.protocolVersion; + this.readyState = WebSocket.CONNECTING; this._isServer = false; this.url = address; - this.protocolVersion = options.protocolVersion; - - // begin handshake - var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); - var shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - var expectedServerKey = shasum.digest('base64'); var agent = options.agent; - var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port); - - var requestOptions = { - port: port, + const requestOptions = { host: serverUrl.hostname, path: '/', + port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Host': headerHost, + 'Host': buildHostHeader(isSecure, serverUrl.hostname, port), 'Sec-WebSocket-Version': options.protocolVersion, - 'Sec-WebSocket-Key': key + 'Sec-WebSocket-Key': key, + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' } }; - // If we have basic auth. - if (auth) { - requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64'); + if (options.headers) Object.assign(requestOptions.headers, options.headers); + if (Object.keys(extensionsOffer).length) { + requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); } - if (options.protocol) { requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; } - - if (options.host) { - requestOptions.headers.Host = options.host; - } - - if (options.headers) { - for (var header in options.headers) { - if (options.headers.hasOwnProperty(header)) { - requestOptions.headers[header] = options.headers[header]; - } + if (options.origin) { + if (options.protocolVersion < 13) { + requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; + } else { + requestOptions.headers.Origin = options.origin; } } + if (options.host) requestOptions.headers.Host = options.host; - if (Object.keys(extensionsOffer).length) { - requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); - } - - if (isDefinedAndNonNull(options, 'pfx') || - isDefinedAndNonNull(options, 'key') || - isDefinedAndNonNull(options, 'passphrase') || - isDefinedAndNonNull(options, 'cert') || - isDefinedAndNonNull(options, 'ca') || - isDefinedAndNonNull(options, 'ciphers') || - isDefinedAndNonNull(options, 'rejectUnauthorized') || - isDefinedAndNonNull(options, 'checkServerIdentity')) { - if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; - if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; - if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; - if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; - if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; - if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; - if (isDefinedAndNonNull(options, 'rejectUnauthorized')) { - requestOptions.rejectUnauthorized = options.rejectUnauthorized; - } - if (isDefinedAndNonNull(options, 'checkServerIdentity')) { - requestOptions.checkServerIdentity = options.checkServerIdentity; - } + if (options.localAddress) requestOptions.localAddress = options.localAddress; + if (isUnixSocket) requestOptions.socketPath = serverUrl.pathname; + if (serverUrl.auth) requestOptions.auth = serverUrl.auth; - if (!agent) { - // global agent ignores client side certificates - agent = new httpObj.Agent(requestOptions); - } - } - - // make sure that path starts with `/` + // + // Make sure that path starts with `/`. + // if (serverUrl.path) { if (serverUrl.path.charAt(0) !== '/') { requestOptions.path = `/${serverUrl.path}`; @@ -682,31 +668,45 @@ function initAsClient (address, protocols, options) { } } - if (agent) { - requestOptions.agent = agent; - } - - if (isUnixSocket) { - requestOptions.socketPath = serverUrl.pathname; - } + // + // A custom agent is required for these options. + // + if ( + options.rejectUnauthorized != null || + options.checkServerIdentity || + options.passphrase || + options.ciphers || + options.cert || + options.key || + options.pfx || + options.ca + ) { + if (options.passphrase) requestOptions.passphrase = options.passphrase; + if (options.ciphers) requestOptions.ciphers = options.ciphers; + if (options.cert) requestOptions.cert = options.cert; + if (options.key) requestOptions.key = options.key; + if (options.pfx) requestOptions.pfx = options.pfx; + if (options.ca) requestOptions.ca = options.ca; + if (options.checkServerIdentity) { + requestOptions.checkServerIdentity = options.checkServerIdentity; + } + if (options.rejectUnauthorized != null) { + requestOptions.rejectUnauthorized = options.rejectUnauthorized; + } - if (options.localAddress) { - requestOptions.localAddress = options.localAddress; + if (!agent) agent = new httpObj.Agent(requestOptions); } - if (options.origin) { - if (options.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; - else requestOptions.headers.Origin = options.origin; - } + if (agent) requestOptions.agent = agent; - var req = httpObj.request(requestOptions); + const req = httpObj.get(requestOptions); req.on('error', (error) => { this.emit('error', error); cleanupWebsocketResources.call(this, error); }); - req.once('response', (res) => { + req.on('response', (res) => { var error; if (!this.emit('unexpected-response', req, res)) { @@ -718,7 +718,7 @@ function initAsClient (address, protocols, options) { cleanupWebsocketResources.call(this, error); }); - req.once('upgrade', (res, socket, upgradeHead) => { + req.on('upgrade', (res, socket, upgradeHead) => { if (this.readyState === WebSocket.CLOSED) { // client closed before server accepted connection this.emit('close'); @@ -727,17 +727,20 @@ function initAsClient (address, protocols, options) { return; } - var serverKey = res.headers['sec-websocket-accept']; - if (serverKey !== expectedServerKey) { + const digest = crypto.createHash('sha1') + .update(key + GUID, 'binary') + .digest('base64'); + + if (res.headers['sec-websocket-accept'] !== digest) { this.emit('error', new Error('invalid server key')); this.removeAllListeners(); socket.end(); return; } - var serverProt = res.headers['sec-websocket-protocol']; - var protList = (options.protocol || '').split(/, */); - var protError = null; + const serverProt = res.headers['sec-websocket-protocol']; + const protList = (options.protocol || '').split(/, */); + var protError; if (!options.protocol && serverProt) { protError = 'server sent a subprotocol even though none requested'; @@ -752,11 +755,11 @@ function initAsClient (address, protocols, options) { this.removeAllListeners(); socket.end(); return; - } else if (serverProt) { - this.protocol = serverProt; } - var serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); + if (serverProt) this.protocol = serverProt; + + const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); @@ -773,12 +776,8 @@ function initAsClient (address, protocols, options) { // perform cleanup on http resources req.removeAllListeners(); - req = null; agent = null; }); - - req.end(); - this.readyState = WebSocket.CONNECTING; } function establishConnection (socket, upgradeHead) { From c16d595a1862b5f812139de695f515aefd79c5fa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 4 Dec 2016 19:35:00 +0100 Subject: [PATCH 158/489] [minor] Move the EventTarget methods to lib/EventTarget.js --- lib/EventTarget.js | 158 ++++++++++++++++++++++++++++++++++ lib/WebSocket.js | 206 +++++++++++++-------------------------------- 2 files changed, 217 insertions(+), 147 deletions(-) create mode 100644 lib/EventTarget.js diff --git a/lib/EventTarget.js b/lib/EventTarget.js new file mode 100644 index 000000000..1c97e0420 --- /dev/null +++ b/lib/EventTarget.js @@ -0,0 +1,158 @@ +'use strict'; + +/** + * Class representing an event. + * + * @private + */ +class Event { + /** + * Create a new `Event`. + * + * @param {String} type The name of the event + * @param {Object} target A reference to the target to which the event was dispatched + */ + constructor (type, target) { + this.target = target; + this.type = type; + } +} + +/** + * Class representing a message event. + * + * @extends Event + * @private + */ +class MessageEvent extends Event { + /** + * Create a new `MessageEvent`. + * + * @param {(String|Buffer|ArrayBuffer)} data The received data + * @param {Boolean} isBinary Specifies if `data` is binary + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (data, isBinary, target) { + super('message', target); + + this.binary = isBinary; // non-standard. + this.data = data; + } +} + +/** + * Class representing a close event. + * + * @extends Event + * @private + */ +class CloseEvent extends Event { + /** + * Create a new `CloseEvent`. + * + * @param {Number} code The status code explaining why the connection is being closed + * @param {String} reason A human-readable string explaining why the connection is closing + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (code, reason, target) { + super('close', target); + + this.wasClean = code === undefined || code === 1000; + this.reason = reason; + this.target = target; + this.type = 'close'; + this.code = code; + } +} + +/** + * Class representing an open event. + * + * @extends Event + * @private + */ +class OpenEvent extends Event { + /** + * Create a new `OpenEvent`. + * + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (target) { + super('open', target); + } +} + +/** + * This provides methods for emulating the `EventTarget` interface. It's not + * meant to be used directly. + * + * @mixin + */ +const EventTarget = { + /** + * Register an event listener. + * + * @param {String} method A string representing the event type to listen for + * @param {Function} listener The listener to add + * @public + */ + addEventListener (method, listener) { + if (typeof listener !== 'function') return; + + function onMessage (data, flags) { + if (flags.binary && this.binaryType === 'arraybuffer') { + data = new Uint8Array(data).buffer; + } + listener.call(this, new MessageEvent(data, !!flags.binary, this)); + } + + function onClose (code, message) { + listener.call(this, new CloseEvent(code, message, this)); + } + + function onError (event) { + event.type = 'error'; + event.target = this; + listener.call(this, event); + } + + function onOpen () { + listener.call(this, new OpenEvent(this)); + } + + if (method === 'message') { + onMessage._listener = listener; + this.on(method, onMessage); + } else if (method === 'close') { + onClose._listener = listener; + this.on(method, onClose); + } else if (method === 'error') { + onError._listener = listener; + this.on(method, onError); + } else if (method === 'open') { + onOpen._listener = listener; + this.on(method, onOpen); + } else { + this.on(method, listener); + } + }, + + /** + * Remove an event listener. + * + * @param {String} method A string representing the event type to remove + * @param {Function} listener The listener to remove + * @public + */ + removeEventListener (method, listener) { + const listeners = this.listeners(method); + + for (var i = 0; i < listeners.length; i++) { + if (listeners[i]._listener === listener) { + this.removeListener(method, listeners[i]); + } + } + } +}; + +module.exports = EventTarget; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 8fb99f485..af5cd1d48 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -16,6 +16,7 @@ const util = require('util'); const url = require('url'); const PerMessageDeflate = require('./PerMessageDeflate'); +const EventTarget = require('./EventTarget'); const Extensions = require('./Extensions'); const Receiver = require('./Receiver'); const Sender = require('./Sender'); @@ -332,12 +333,12 @@ WebSocket.prototype.terminate = function terminate () { }; /** - * Expose bufferedAmount + * Expose the `bufferedAmount` attribute. * - * @api public + * @public */ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { - get: function get () { + get () { var amount = 0; if (this._socket) { amount = this._socket.bufferSize || 0; @@ -347,19 +348,19 @@ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { }); /** - * Expose binaryType + * Expose the `binaryType` attribute. * - * This deviates from the W3C interface since ws doesn't support the required + * This deviates from the WHATWG interface since ws doesn't support the required * default "blob" type (instead we define a custom "nodebuffer" type). * - * @see http://dev.w3.org/html5/websockets/#the-websocket-interface - * @api public + * @see {@link https://html.spec.whatwg.org/multipage/comms.html#dom-websocket-binarytype} + * @public */ Object.defineProperty(WebSocket.prototype, 'binaryType', { - get: function get () { + get () { return this._binaryType; }, - set: function set (type) { + set (type) { if (type === 'arraybuffer' || type === 'nodebuffer') { this._binaryType = type; } else { @@ -368,174 +369,85 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { } }); -/** - * Emulates the W3C Browser based WebSocket interface using function members. - * - * @see http://dev.w3.org/html5/websockets/#the-websocket-interface - * @api public - */ -['open', 'error', 'close', 'message'].forEach(function (method) { - Object.defineProperty(WebSocket.prototype, 'on' + method, { +// +// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes. +// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface +// +['open', 'error', 'close', 'message'].forEach((method) => { + Object.defineProperty(WebSocket.prototype, `on${method}`, { /** - * Returns the current listener + * Return the listener of the event. * - * @returns {Mixed} the set function or undefined - * @api public + * @return {(Function|undefined)} The event listener or `undefined` + * @public */ - get: function get () { - var listener = this.listeners(method)[0]; - return listener ? (listener._listener ? listener._listener : listener) : undefined; + get () { + const listener = this.listeners(method)[0]; + return listener ? listener._listener ? listener._listener : listener : undefined; }, - /** - * Start listening for events + * Add a listener for the event. * - * @param {Function} listener the listener - * @returns {Mixed} the set function or undefined - * @api public + * @param {Function} listener The listener to add + * @public */ - set: function set (listener) { + set (listener) { this.removeAllListeners(method); this.addEventListener(method, listener); } }); }); -/** - * Registers an event listener emulating the `EventTarget` interface. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - * @param {String} method A string representing the event type to listen for - * @param {Function} listener The listener to add - * @public - */ -WebSocket.prototype.addEventListener = function (method, listener) { - if (typeof listener !== 'function') return; - - function onMessage (data, flags) { - if (flags.binary && this.binaryType === 'arraybuffer') { - data = new Uint8Array(data).buffer; - } - listener.call(this, new MessageEvent(data, !!flags.binary, this)); - } - - function onClose (code, message) { - listener.call(this, new CloseEvent(code, message, this)); - } - - function onError (event) { - event.type = 'error'; - event.target = this; - listener.call(this, event); - } - - function onOpen () { - listener.call(this, new OpenEvent(this)); - } - - if (method === 'message') { - onMessage._listener = listener; - this.on(method, onMessage); - } else if (method === 'close') { - onClose._listener = listener; - this.on(method, onClose); - } else if (method === 'error') { - onError._listener = listener; - this.on(method, onError); - } else if (method === 'open') { - onOpen._listener = listener; - this.on(method, onOpen); - } else { - this.on(method, listener); - } -}; - -/** - * Removes an event listener previously registered with `addEventListener`. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener - * @param {String} method A string representing the event type to remove - * @param {Function} listener The listener to remove - * @public - */ -WebSocket.prototype.removeEventListener = function (method, listener) { - const listeners = this.listeners(method); - - for (var i = 0; i < listeners.length; i++) { - if (listeners[i]._listener === listener) { - this.removeListener(method, listeners[i]); - } - } -}; +WebSocket.prototype.addEventListener = EventTarget.addEventListener; +WebSocket.prototype.removeEventListener = EventTarget.removeEventListener; module.exports = WebSocket; module.exports.buildHostHeader = buildHostHeader; /** - * W3C MessageEvent + * Append port number to Host header, only if specified in the URL and + * non-default. * - * @see http://www.w3.org/TR/html5/comms.html - * @constructor - * @api private - */ -function MessageEvent (dataArg, isBinary, target) { - this.type = 'message'; - this.data = dataArg; - this.target = target; - this.binary = isBinary; // non-standard. -} - -/** - * W3C CloseEvent - * - * @see http://www.w3.org/TR/html5/comms.html - * @constructor - * @api private - */ -function CloseEvent (code, reason, target) { - this.type = 'close'; - this.wasClean = code === undefined || code === 1000; - this.code = code; - this.reason = reason; - this.target = target; -} - -/** - * W3C OpenEvent - * - * @see http://www.w3.org/TR/html5/comms.html - * @constructor - * @api private + * @param {Boolean} isSecure Specifies whether or not the URL scheme is `wss` + * @param {String} hostname The hostname portion of the URL + * @param {Number} port The port portion of the URL + * @return {String} The field value of the `Host` header + * @private */ -function OpenEvent (target) { - this.type = 'open'; - this.target = target; -} - -// Append port number to Host header, only if specified in the url -// and non-default function buildHostHeader (isSecure, hostname, port) { var headerHost = hostname; - if (hostname) { - if ((isSecure && (port !== 443)) || (!isSecure && (port !== 80))) { - headerHost = headerHost + ':' + port; - } + + if (headerHost && (isSecure && port !== 443 || !isSecure && port !== 80)) { + headerHost = `${headerHost}:${port}`; } + return headerHost; } /** - * Entirely private apis, - * which may or may not be bound to a sepcific WebSocket instance. + * Initialize a WebSocket server client. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Object} options WebSocket attributes + * @param {Number} options.protocolVersion The WebSocket protocol version + * @param {Object} options.extensions The negotiated extensions + * @param {Number} options.maxPayload The maximum allowed message size + * @param {String} options.protocol The chosen subprotocol + * @private */ -function initAsServerClient (req, socket, upgradeHead, options) { - // expose state properties - Object.assign(this, options); +function initAsServerClient (req, socket, head, options) { + this.protocolVersion = options.protocolVersion; + this.extensions = options.extensions; + this.maxPayload = options.maxPayload; + this.protocol = options.protocol; + this.readyState = WebSocket.CONNECTING; this.upgradeReq = req; this._isServer = true; - // establish connection - establishConnection.call(this, socket, upgradeHead); + + establishConnection.call(this, socket, head); } /** @@ -543,7 +455,7 @@ function initAsServerClient (req, socket, upgradeHead, options) { * * @param {String} address The URL to which to connect * @param {String[]} protocols The list of subprotocols - * @param {Object} options Configuration options + * @param {Object} options Connection options * @param {String} option.protocol Value of the `Sec-WebSocket-Protocol` header * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate * @param {String} options.localAddress Local interface to bind for network connections From 9896d5b722901c01c7702b8777a84d5253e2a2be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 10:05:34 +0100 Subject: [PATCH 159/489] [ci] Add appveyor.yml --- appveyor.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..954f55608 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,18 @@ +environment: + matrix: + - nodejs_version: "7" + - nodejs_version: "6" + - nodejs_version: "4" +platform: + - x86 + - x64 +matrix: + fast_finish: true +install: + - ps: Install-Product node $env:nodejs_version $env:platform + - npm install +test_script: + - node --version + - npm --version + - npm test +build: off From 4874b5e64846abdb057568453d90ded4de37dadb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 10:07:59 +0100 Subject: [PATCH 160/489] [doc] Add AppVeyor badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b78bb0e5..654c11609 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # ws: a node.js websocket library [![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) -[![Build Status](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) +[![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) +[![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) `ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, From f0caae4a5f1c30024ee96e595b2d2fdd2f374980 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 10:08:42 +0100 Subject: [PATCH 161/489] [ci] Use container-based infrastructure for faster builds --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9c131ecc..3a58aefe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js -sudo: required dist: trusty +sudo: false node_js: - "7" - "6" From e032dd94e88d4a865fc27957beb35687bfd51766 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 13:00:58 +0100 Subject: [PATCH 162/489] [major] Remove `supports` attribute (#918) --- doc/ws.md | 4 ---- lib/WebSocket.js | 1 - test/WebSocket.test.js | 18 ------------------ 3 files changed, 23 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 6ba7e2b2b..c8b6a748d 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -143,10 +143,6 @@ The WebSocket protocol version used for this connection, `8`, `13`. The URL of the WebSocket server (only for clients) -### websocket.supports - -Describes the feature of the used protocol version. E.g. `supports.binary` is a boolean that describes if the connection supports binary messages. - ### websocket.upgradeReq The http request that initiated the upgrade. Useful for parsing authorty headers, cookie headers and other information to associate a specific Websocket to a specific Client. This is only available for WebSockets constructed by a Server. diff --git a/lib/WebSocket.js b/lib/WebSocket.js index af5cd1d48..63363cf41 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -60,7 +60,6 @@ function WebSocket (address, protocols, options) { this._closeReceived = false; this.bytesReceived = 0; this.readyState = null; - this.supports = { binary: true }; this.extensions = {}; this._binaryType = 'nodebuffer'; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 14bd00abb..1d4920df4 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1894,24 +1894,6 @@ describe('WebSocket', function () { }); }); - describe('protocol support discovery', function () { - describe('#supports', function () { - describe('#binary', function () { - it('returns true', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client.supports.binary, true); - wss.close(); - done(); - }); - }); - }); - }); - }); - describe('host and origin headers', function () { it('includes the host header with port number', function (done) { const server = http.createServer(); From 4ea3661e399754d1f272fc409e51747089bb3f18 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 13:06:47 +0100 Subject: [PATCH 163/489] [fix] Call `Sender#close()` callback when data is written out (#908) --- lib/Sender.js | 3 +-- test/Sender.test.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index e0393bc65..b15b20a7e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -67,11 +67,10 @@ class Sender { * @private */ doClose (data, mask, cb) { - this.frameAndSend(0x08, data, false, true, mask, false); + this.frameAndSend(0x08, data, false, true, mask, false, cb); if (this.extensions[PerMessageDeflate.extensionName]) { this.continue(); } - if (cb) cb(); } /** diff --git a/test/Sender.test.js b/test/Sender.test.js index 01342b54d..0600dd9b4 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -221,11 +221,11 @@ describe('Sender', function () { }); it('handles many send calls while processing without crashing on flush', function (done) { - let cnt = 0; + let count = 0; const perMessageDeflate = new PerMessageDeflate(); const sender = new Sender({ write: () => { - if (++cnt > 1e4) done(); + if (++count > 1e4) done(); } }, { 'permessage-deflate': perMessageDeflate @@ -249,7 +249,10 @@ describe('Sender', function () { let count = 0; const sender = new Sender({ - write: (data) => count++ + write: (data, cb) => { + count++; + if (cb) cb(); + } }, { 'permessage-deflate': perMessageDeflate }); @@ -260,9 +263,9 @@ describe('Sender', function () { sender.send('bar', { compress: true, fin: true }); sender.send('baz', { compress: true, fin: true }); - sender.close(1000, null, false, (err) => { + sender.close(1000, null, false, () => { assert.strictEqual(count, 4); - done(err); + done(); }); }); }); From 8303c5c759f9a049e4a8e84bbcc8dd4600b099d9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 21:47:26 +0100 Subject: [PATCH 164/489] [test] Fix failing test on Windows --- test/WebSocket.test.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 1d4920df4..0d85fda2f 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -7,7 +7,6 @@ const crypto = require('crypto'); const https = require('https'); const http = require('http'); const fs = require('fs'); -const os = require('os'); const server = require('./testserver'); const WebSocket = require('..'); @@ -42,24 +41,21 @@ describe('WebSocket', function () { }); it('should accept the localAddress option', function (done) { - // explore existing interfaces - const devs = os.networkInterfaces(); - const localAddresses = []; - - Object.keys(devs).forEach((name) => { - devs[name].forEach((ifc) => { - if (!ifc.internal && ifc.family === 'IPv4') { - localAddresses.push(ifc.address); - } - }); - }); + // + // Skip this test on macOS as by default all loopback addresses other + // than 127.0.0.1 are disabled. + // + if (process.platform === 'darwin') return done(); - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ host: '127.0.0.1', port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - localAddress: localAddresses[0] + localAddress: '127.0.0.2' }); + }); - ws.on('open', () => wss.close(done)); + wss.on('connection', (ws) => { + assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '127.0.0.2'); + wss.close(done); }); }); From 22d3b77cabff3ff71f25594ee2376cd0975fce4d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Dec 2016 11:32:10 +0100 Subject: [PATCH 165/489] [fix] Call close callback when using a precreated server --- lib/WebSocketServer.js | 21 ++++++++++----- test/WebSocketServer.test.js | 50 +++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index a0024760e..4a0bd4e92 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -20,6 +20,8 @@ const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; /** * Class representing a WebSocket server. + * + * @extends EventEmitter */ class WebSocketServer extends EventEmitter { /** @@ -99,19 +101,26 @@ class WebSocketServer extends EventEmitter { * @public */ close (cb) { - // terminate all associated clients + // + // Terminate all associated clients. + // if (this.clients) { for (const client of this.clients) client.terminate(); } - if (this._server) { - // close the http server if it was internally created - if (this.options.port != null) this._server.close(cb); + const server = this._server; + + if (server) { this._ultron.destroy(); this._ultron = this._server = null; - } else if (cb) { - cb(); + + // + // Close the http server if it was internally created. + // + if (this.options.port != null) return server.close(cb); } + + if (cb) cb(); } /** diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index e79aa5257..cceb53f94 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -75,23 +75,25 @@ describe('WebSocketServer', function () { }); }); - // Don't test this on Windows. It throws errors for obvious reasons. - if (process.platform !== 'win32') { - it('uses a precreated http server listening on unix socket', function (done) { - const server = http.createServer(); - const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; + it('uses a precreated http server listening on unix socket', function (done) { + // + // Skip this test on Windows as it throws errors for obvious reasons. + // + if (process.platform === 'win32') return done(); - server.listen(sockPath, () => { - const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws+unix://${sockPath}`); + const server = http.createServer(); + const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; - wss.on('connection', (ws) => { - wss.close(); - server.close(done); - }); + server.listen(sockPath, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws+unix://${sockPath}`); + + wss.on('connection', (ws) => { + wss.close(); + server.close(done); }); }); - } + }); it('emits path specific connection event', function (done) { const server = http.createServer(); @@ -134,7 +136,7 @@ describe('WebSocketServer', function () { }); }); - it('will close all clients', function (done) { + it('closes all clients', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { @@ -171,18 +173,24 @@ describe('WebSocketServer', function () { }); }); + it('invokes the callback in noServer mode', function (done) { + const wss = new WebSocketServer({ noServer: true }); + + wss.close(done); + }); + it('cleans event handlers on precreated server', function (done) { const server = http.createServer(); + const wss = new WebSocketServer({ server }); server.listen(++port, () => { - const wss = new WebSocketServer({ server }); - wss.close(); + wss.close(() => { + assert.strictEqual(server.listeners('listening').length, 0); + assert.strictEqual(server.listeners('upgrade').length, 0); + assert.strictEqual(server.listeners('error').length, 0); - assert.strictEqual(server.listeners('listening').length, 0); - assert.strictEqual(server.listeners('upgrade').length, 0); - assert.strictEqual(server.listeners('error').length, 0); - - server.close(done); + server.close(done); + }); }); }); }); From 5375617e528359749e081a31229f3d6cb3dbfefd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 7 Dec 2016 08:14:12 +0100 Subject: [PATCH 166/489] [major] Remove `WebSocket.createServer()` and `WebSocket.connect()` --- index.js | 46 ++++++---------------------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 3cfe33714..489e16942 100644 --- a/index.js +++ b/index.js @@ -1,49 +1,15 @@ -'use strict'; - /*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ -var WS = module.exports = require('./lib/WebSocket'); - -WS.Server = require('./lib/WebSocketServer'); -WS.Sender = require('./lib/Sender'); -WS.Receiver = require('./lib/Receiver'); - -/** - * Create a new WebSocket server. - * - * @param {Object} options Server options - * @param {Function} fn Optional connection listener. - * @returns {WS.Server} - * @api public - */ -WS.createServer = function createServer (options, fn) { - var server = new WS.Server(options); - - if (typeof fn === 'function') { - server.on('connection', fn); - } - - return server; -}; +'use strict'; -/** - * Create a new WebSocket connection. - * - * @param {String} address The URL/address we need to connect to. - * @param {Function} fn Open listener. - * @returns {WS} - * @api public - */ -WS.connect = WS.createConnection = function connect (address, fn) { - var client = new WS(address); +const WebSocket = require('./lib/WebSocket'); - if (typeof fn === 'function') { - client.on('open', fn); - } +WebSocket.Server = require('./lib/WebSocketServer'); +WebSocket.Receiver = require('./lib/Receiver'); +WebSocket.Sender = require('./lib/Sender'); - return client; -}; +module.exports = WebSocket; From 54f902407c152d2710645f2ec911c54091d1e067 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 7 Dec 2016 09:55:15 +0100 Subject: [PATCH 167/489] [fix] Make `WebSocket#removeEventListener()` work with any listener --- lib/EventTarget.js | 2 +- test/WebSocket.test.js | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index 1c97e0420..9720e4a26 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -148,7 +148,7 @@ const EventTarget = { const listeners = this.listeners(method); for (var i = 0; i < listeners.length; i++) { - if (listeners[i]._listener === listener) { + if (listeners[i] === listener || listeners[i]._listener === listener) { this.removeListener(method, listeners[i]); } } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 0d85fda2f..3337d4c67 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1614,24 +1614,47 @@ describe('WebSocket', function () { }); }); - it('should remove event listeners added with addEventListener', function () { - const message = () => {}; - const open = () => {}; + it('registers listeners for custom events with addEventListener', function () { + const listener = () => {}; const ws = new WebSocket(`ws://localhost:${++port}`); ws.on('error', () => {}); - ws.addEventListener('message', message); - ws.addEventListener('open', open); + ws.addEventListener('foo', listener); + assert.strictEqual(ws.listeners('foo')[0], listener); - assert.strictEqual(ws.listeners('message')[0]._listener, message); - assert.strictEqual(ws.listeners('open')[0]._listener, open); + // + // Fails silently when the `listener` is not a function. + // + ws.addEventListener('bar', {}); + assert.strictEqual(ws.listeners('bar').length, 0); + }); + + it('removes event listeners added with addEventListener', function () { + const listener = () => {}; + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('error', () => {}); + + ws.addEventListener('message', listener); + ws.addEventListener('open', listener); + ws.addEventListener('foo', listener); + + assert.strictEqual(ws.listeners('message')[0]._listener, listener); + assert.strictEqual(ws.listeners('open')[0]._listener, listener); + assert.strictEqual(ws.listeners('foo')[0], listener); + + ws.removeEventListener('message', () => {}); + + assert.strictEqual(ws.listeners('message').length, 1); - ws.removeEventListener('message', message); - ws.removeEventListener('open', open); + ws.removeEventListener('message', listener); + ws.removeEventListener('open', listener); + ws.removeEventListener('foo', listener); assert.strictEqual(ws.listeners('message').length, 0); assert.strictEqual(ws.listeners('open').length, 0); + assert.strictEqual(ws.listeners('foo').length, 0); }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { From 5d2ae65de38029223a7d0a3f3c426a7bc0d001c5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 8 Dec 2016 16:37:20 +0100 Subject: [PATCH 168/489] chore(package): update utf-8-validate to version 2.0.0 (#927) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 162b61c5b..bbc84ce3c 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "eslint-plugin-standard": "~2.0.1", "istanbul": "~0.4.5", "mocha": "~3.2.0", - "utf-8-validate": "~1.2.1" + "utf-8-validate": "~2.0.0" } } From a720a7601cbee38ed4e4c097295d4a8a5bf5c61f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 9 Dec 2016 18:13:57 +0100 Subject: [PATCH 169/489] chore(package): update eslint to version 3.12.0 (#931) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bbc84ce3c..c6b592113 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.3.0", - "eslint": "~3.11.0", + "eslint": "~3.12.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From 53a67ab085a006ee5aec1c7adac72d977436c918 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Dec 2016 20:05:11 +0100 Subject: [PATCH 170/489] [test] Add more tests --- test/WebSocket.test.js | 97 ++++++++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 22 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index e133ac145..3850f14a1 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -14,6 +14,10 @@ const WebSocket = require('..'); const WebSocketServer = WebSocket.Server; let port = 20000; +class CustomAgent extends http.Agent { + createConnection () {} +} + describe('WebSocket', function () { describe('#ctor', function () { it('should return a new instance if called without new', function (done) { @@ -23,21 +27,39 @@ describe('WebSocket', function () { ws.on('error', () => done()); }); - it('throws exception for invalid url', function () { - assert.throws(() => new WebSocket('echo.websocket.org')); + it('throws an error when using an invalid url', function () { + assert.throws( + () => new WebSocket('echo.websocket.org'), + /^Error: invalid url$/ + ); }); }); describe('options', function () { it('should accept an `agent` option', function (done) { - const agent = { addRequest: () => done() }; + const agent = new CustomAgent(); + + agent.createConnection = () => { + done(); + }; + const ws = new WebSocket('ws://localhost', { agent }); }); // GH-227 - it('should accept the `options` object as the 3rd argument', function (done) { - const agent = { addRequest: () => done() }; - const ws = new WebSocket('ws://localhost', [], { agent }); + it('should accept the `options` object as the 3rd argument', function () { + const ws = new WebSocket('ws://localhost', [], { + agent: new CustomAgent() + }); + }); + + it('throws an error when using an invalid `protocolVersion`', function () { + const options = { agent: new CustomAgent(), protocolVersion: 1000 }; + + assert.throws( + () => new WebSocket('ws://localhost', options), + /^Error: unsupported protocol version$/ + ); }); it('should accept the localAddress option', function (done) { @@ -264,8 +286,9 @@ describe('WebSocket', function () { }); it('is property of instance', function () { - const ws = new WebSocket('ws://localhost'); - ws.on('error', () => {}); + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); assert.strictEqual(ws[state], readyStates[state]); }); @@ -996,11 +1019,16 @@ describe('WebSocket', function () { }); }); - describe('W3C API emulation', function () { + describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function (done) { server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); const listener = () => {}; + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.onmessage, undefined); + assert.strictEqual(ws.onclose, undefined); + assert.strictEqual(ws.onerror, undefined); + assert.strictEqual(ws.onopen, undefined); ws.onmessage = listener; ws.onerror = listener; @@ -1010,17 +1038,27 @@ describe('WebSocket', function () { assert.strictEqual(ws.binaryType, 'nodebuffer'); ws.binaryType = 'arraybuffer'; assert.strictEqual(ws.binaryType, 'arraybuffer'); + ws.binaryType = 'nodebuffer'; + assert.strictEqual(ws.binaryType, 'nodebuffer'); - assert.strictEqual(ws.onopen, listener); assert.strictEqual(ws.onmessage, listener); assert.strictEqual(ws.onclose, listener); assert.strictEqual(ws.onerror, listener); + assert.strictEqual(ws.onopen, listener); srv.close(done); ws.terminate(); }); }); + it('should throw an error when setting an invalid binary type', function () { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + assert.throws(() => { + ws.binaryType = 'foo'; + }, /^SyntaxError: unsupported binaryType: must be either "nodebuffer" or "arraybuffer"$/); + }); + it('should work the same as the EventEmitter api', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -1064,9 +1102,7 @@ describe('WebSocket', function () { it('registers listeners for custom events with addEventListener', function () { const listener = () => {}; - const ws = new WebSocket(`ws://localhost:${++port}`); - - ws.on('error', () => {}); + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); ws.addEventListener('foo', listener); assert.strictEqual(ws.listeners('foo')[0], listener); @@ -1080,9 +1116,7 @@ describe('WebSocket', function () { it('removes event listeners added with addEventListener', function () { const listener = () => {}; - const ws = new WebSocket(`ws://localhost:${++port}`); - - ws.on('error', () => {}); + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); ws.addEventListener('message', listener); ws.addEventListener('open', listener); @@ -1094,7 +1128,7 @@ describe('WebSocket', function () { ws.removeEventListener('message', () => {}); - assert.strictEqual(ws.listeners('message').length, 1); + assert.strictEqual(ws.listeners('message')[0]._listener, listener); ws.removeEventListener('message', listener); ws.removeEventListener('open', listener); @@ -1367,7 +1401,7 @@ describe('WebSocket', function () { server.listen(++port, () => { server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['host'], `localhost:${port}`); + assert.strictEqual(req.headers.host, `localhost:${port}`); server.close(done); socket.destroy(); }); @@ -1381,7 +1415,7 @@ describe('WebSocket', function () { server.listen(++port, () => { server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['origin'], undefined); + assert.strictEqual(req.headers.origin, undefined); server.close(done); socket.destroy(); }); @@ -1390,14 +1424,33 @@ describe('WebSocket', function () { }); }); - it('honors origin set in options', function (done) { + it('honors origin set in options (1/2)', function (done) { const server = http.createServer(); server.listen(++port, () => { const options = { origin: 'https://example.com:8000' }; server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['origin'], options.origin); + assert.strictEqual(req.headers.origin, options.origin); + server.close(done); + socket.destroy(); + }); + + const ws = new WebSocket(`ws://localhost:${port}`, options); + }); + }); + + it('honors origin set in options (2/2)', function (done) { + const server = http.createServer(); + + server.listen(++port, () => { + const options = { + origin: 'https://example.com:8000', + protocolVersion: 8 + }; + + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['sec-websocket-origin'], options.origin); server.close(done); socket.destroy(); }); From ff7257ea7dd41ea4ca4c0a2e61054ba520ecb58c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Dec 2016 20:22:22 +0100 Subject: [PATCH 171/489] [test] Fix failing tests on node 4 --- test/WebSocket.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 3850f14a1..9771978cc 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -15,7 +15,7 @@ const WebSocketServer = WebSocket.Server; let port = 20000; class CustomAgent extends http.Agent { - createConnection () {} + addRequest () {} } describe('WebSocket', function () { @@ -39,7 +39,7 @@ describe('WebSocket', function () { it('should accept an `agent` option', function (done) { const agent = new CustomAgent(); - agent.createConnection = () => { + agent.addRequest = () => { done(); }; From 838ba0971b5dcbd8d38f68caab53dc6dd23ec9fe Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 11 Dec 2016 13:38:20 +0100 Subject: [PATCH 172/489] [fix] Convert data to buffer in `Sender#send()` --- lib/Sender.js | 44 +++++++++++++++++++++----------------------- test/Sender.test.js | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index b15b20a7e..0091a849b 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -148,24 +148,36 @@ class Sender { send (data, options, cb) { const pmd = this.extensions[PerMessageDeflate.extensionName]; var opcode = options.binary ? 2 : 1; - var compress = options.compress; + var rsv1 = options.compress; + var readOnly = true; + + if (data && !Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(typeof data === 'number' ? data.toString() : data); + readOnly = false; + } + } if (this.firstFragment) { this.firstFragment = false; - if (compress && data && pmd) compress = data.length >= pmd.threshold; - this.compress = compress; + if (rsv1 && data && pmd) rsv1 = data.length >= pmd.threshold; + this.compress = rsv1; } else { - compress = false; + rsv1 = false; opcode = 0; } if (options.fin) this.firstFragment = true; if (pmd) { - const args = [opcode, data, options.fin, options.mask, compress, cb]; + const args = [opcode, data, readOnly, options.fin, options.mask, rsv1, cb]; this.enqueue([this.sendCompressed, args]); } else { - this.frameAndSend(opcode, data, true, options.fin, options.mask, false, cb); + this.frameAndSend(opcode, data, readOnly, options.fin, options.mask, false, cb); } } @@ -174,20 +186,20 @@ class Sender { * * @param {Number} opcode The opcode * @param {*} data The message to send + * @param {Boolean} readOnly Specifies whether `data` can be modified * @param {Boolean} fin Specifies whether or not to set the FIN bit * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (opcode, data, fin, mask, rsv1, cb) { + sendCompressed (opcode, data, readOnly, fin, mask, rsv1, cb) { if (!this.compress) { - this.frameAndSend(opcode, data, true, fin, mask, false, cb); + this.frameAndSend(opcode, data, readOnly, fin, mask, false, cb); this.continue(); return; } - if (data && !Buffer.isBuffer(data)) data = toBuffer(data); this.extensions[PerMessageDeflate.extensionName].compress(data, fin, (err, buf) => { if (err) { if (cb) cb(err); @@ -344,20 +356,6 @@ function viewToBuffer (view) { return buf; } -/** - * Converts `data` into a buffer. - * - * @param {*} data Data to convert - * @return {Buffer} Converted data - * @private - */ -function toBuffer (data) { - if (data instanceof ArrayBuffer) return Buffer.from(data); - if (ArrayBuffer.isView(data)) return viewToBuffer(data); - - return Buffer.from(typeof data === 'number' ? data.toString() : data); -} - /** * Generates a random mask. * diff --git a/test/Sender.test.js b/test/Sender.test.js index 0600dd9b4..1b7ed063f 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -43,13 +43,38 @@ describe('Sender', function () { }); }); + describe('#ping', function () { + it('works with multiple types of data', function (done) { + let count = 0; + const sender = new Sender({ + write: (data) => { + if (++count < 4) { + assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); + } else { + assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x31, 0x30]))); + done(); + } + } + }); + + const array = new Uint8Array([0x68, 0x69]); + const options = { mask: false }; + + sender.ping(array.buffer, options); + sender.ping(array, options); + sender.ping('hi', options); + sender.ping(10, options); + }); + }); + describe('#send', function () { it('compresses data if compress option is enabled', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + let count = 0; const sender = new Sender({ write: (data) => { assert.strictEqual(data[0] & 0x40, 0x40); - done(); + if (++count === 4) done(); } }, { 'permessage-deflate': perMessageDeflate @@ -57,7 +82,13 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('hi', { compress: true, fin: true }); + const options = { compress: true, fin: true }; + const array = new Uint8Array([0x68, 0x69]); + + sender.send(array.buffer, options); + sender.send(array, options); + sender.send('hi', options); + sender.send(100, options); }); it('does not compress data for small payloads', function (done) { From 3d4273d95208e45af7de45cbbe48984e872b05d7 Mon Sep 17 00:00:00 2001 From: Denis Andrejew Date: Mon, 12 Dec 2016 17:21:46 +0000 Subject: [PATCH 173/489] [doc] Fix typo in ws.md (#935) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index c8b6a748d..0ca28e20a 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -22,7 +22,7 @@ Construct a new server object. Either `port` or `server` must be provided, otherwise you might enable `noServer` if you want to pass the requests directly. Please note that the -`callback` is only used when you supply the a `port` number in the options. +`callback` is only used when you supply a `port` number in the options. ### options.verifyClient From 90269e2dcc8334f98013c1e469f3bfdd9820d95e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 12 Dec 2016 19:45:12 +0100 Subject: [PATCH 174/489] [ignore] Add appveyor.yml to .npmignore --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index e0e4dee67..7c56d8c06 100644 --- a/.npmignore +++ b/.npmignore @@ -3,4 +3,5 @@ examples/ bench/ test/ doc/ +appveyor.yml .* From d93f69f7c0bbb6a11c6f1289d4d7a1add9173eda Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 12 Dec 2016 21:21:07 +0100 Subject: [PATCH 175/489] [license] Add LICENSE file --- LICENSE | 21 +++++++++++++++++++++ README.md | 29 +---------------------------- 2 files changed, 22 insertions(+), 28 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..a145cd1df --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011 Einar Otto Stangvik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 654c11609..53a1a2526 100644 --- a/README.md +++ b/README.md @@ -203,12 +203,6 @@ Note that the usage together with Express 3.0 is quite different from Express Otherwise, see the test cases. -### Running the tests - -``` -make test -``` - ## API Docs See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) for Node.js-like docs for the ws classes. @@ -219,27 +213,6 @@ We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) f ## License -(The MIT License) - -Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +[MIT](LICENSE) [archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs From 5a8ead573f1fb2b7c061431a85b3b79c91013f6c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 13 Dec 2016 18:53:45 +0100 Subject: [PATCH 176/489] [doc] Improve documentation --- doc/ws.md | 455 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 310 insertions(+), 145 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 0ca28e20a..f25035623 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -2,242 +2,407 @@ ## Class: WebSocket.Server -This class is a WebSocket server. It is an `EventEmitter`. +This class represents a WebSocket server. It extends the `EventEmitter`. + +### new WebSocket.Server(options[, callback]) + +- `options` {Object} + - `host` {String} The hostname where to bind the server. + - `port` {Number} The port where to bind the server. + - `backlog` {Number} The maximum length of the queue of pending connections. + - `server` {http.Server|https.Server} A pre-created Node.js HTTP server. + - `verifyClient` {Function} A function which can be used to validate incoming + connections. See description below. + - `handleProtocols` {Function} A function which can be used to handle the + WebSocket subprotocols. See description below. + - `path` {String} Accept only connections matching this path. + - `noServer` {Boolean} Enable no server mode. + - `clientTracking` {Boolean} Specifies whether or not to track clients. + - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate. + - `maxPayload` {Number} The maximum allowed message size in bytes. +- `callback` {Function} + +Create a new server instance. One of `port`, `server` or `noServer` must be +provided or an error is thrown. + + +If `verifyClient` is not set then the handshake is automatically accepted. If +it is is provided with a single argument then that is: + +- `info` {Object} + - `origin` {String} The value in the Origin header indicated by the client. + - `req` {http.IncomingMessage} The client HTTP GET request. + - `secure` {Boolean} `true` if `req.connection.authorized` or + `req.connection.encrypted` is set. + +The return value (Boolean) of the function determines whether or not to accept +the handshake. + +if `verifyClient` is provided with two arguments then those are: + +- `info` {Object} Same as above. +- `cb` {Function} A callback that must be called by the user upon inspection + of the `info` fields. Arguments in this callback are: + - `result` {Boolean} Whether or not to accept the handshake. + - `code` {Number} When `result` is `false` this field determines the HTTP + error status code to be sent to the client. + - `name` {String} When `result` is `false` this field determines the HTTP + reason phrase. + + +If `handleProtocols` is not set then the handshake is automatically accepted, +otherwise the function takes a single argument: + +- `protocols` {Array} The list of WebSocket subprotocols indicated by the + client in the `Sec-WebSocket-Protocol` header. + +If returned value is `false` then the handshake is rejected with the HTTP 401 +status code, otherwise the returned value sets the value of the +`Sec-WebSocket-Protocol` header in the HTTP 101 response. + +`perMessageDeflate` can be used to control the behavior of +[permessage-deflate extension][permessage-deflate]. +The extension is disabled when `false`. Defaults to `true`. If an object is +provided then that is extension parameters: + +- `serverNoContextTakeover` {Boolean} Whether to use context take over or not. +- `clientNoContextTakeover` {Boolean} The value to be requested to clients + whether to use context take over or not. +- `serverMaxWindowBits` {Number} The value of windowBits. +- `clientMaxWindowBits` {Number} The value of max windowBits to be requested + to clients. +- `memLevel` {Number} The value of memLevel. +- `threshold` {Number} Payloads smaller than this will not be compressed. + Defaults to 1024 bytes. + +If a property is empty then either an offered configuration or a default value +is used. +When sending a fragmented message the length of the first fragment is compared +to the threshold. This determines if compression is used for the entire message. + + +`callback` will be added as a listener for the `listening` event when the +HTTP server is created internally and that is when the `port` option is +provided. -### new WebSocket.Server([options], [callback]) +### Event: 'connection' -* `options` Object - * `host` String - * `port` Number - * `server` http.Server - * `verifyClient` Function - * `handleProtocols` Function - * `path` String - * `noServer` Boolean - * `clientTracking` Boolean - * `perMessageDeflate` Boolean|Object -* `callback` Function +- `socket` {WebSocket} -Construct a new server object. +Emitted when the handshake is complete. `socket` is an instance of `WebSocket`. -Either `port` or `server` must be provided, otherwise you might enable -`noServer` if you want to pass the requests directly. Please note that the -`callback` is only used when you supply a `port` number in the options. +### Event: 'error' -### options.verifyClient +- `error` {Error} -`verifyClient` can be used in two different ways. If it is provided with two arguments then those are: -* `info` Object: - * `origin` String: The value in the Origin header indicated by the client. - * `req` http.ClientRequest: The client HTTP GET request. - * `secure` Boolean: `true` if `req.connection.authorized` or `req.connection.encrypted` is set. -* `cb` Function: A callback that must be called by the user upon inspection of the `info` fields. Arguments in this callback are: - * `result` Boolean: Whether the user accepts or not the handshake. - * `code` Number: If `result` is `false` this field determines the HTTP error status code to be sent to the client. - * `name` String: If `result` is `false` this field determines the HTTP reason phrase. +Emitted when an error occurs on the underlying server. -If `verifyClient` is provided with a single argument then that is: -* `info` Object: Same as above. +### Event: 'headers' -In this case the return code (Boolean) of the function determines whether the handshake is accepted or not. +- `headers` {Array} -If `verifyClient` is not set then the handshake is automatically accepted. +Emitted before the response headers are written to the socket as part of the +handshake. This allows you to inspect/modify the headers before they are sent. -### options.handleProtocols +### Event: 'listening' -`handleProtocols` takes a single argument: -* `protocols` Array: The list of WebSocket sub-protocols indicated by the client in the `Sec-WebSocket-Protocol` header. +Emitted when the underlying server has been bound. -If returned value is `false` then the handshake is rejected with the HTTP 401 status code, otherwise the returned value sets the value of the `Sec-WebSocket-Protocol` header in the HTTP 101 response. +### server.clients -If `handleProtocols` is not set then the handshake is automatically accepted. +- {Set} -### options.perMessageDeflate +A set that stores all connected clients. Please note that this property is only +added when the `clientTracking` is truthy. -`perMessageDeflate` can be used to control the behavior of [permessage-deflate extension](https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19). The extension is disabled when `false`. Defaults to `true`. If an object is provided then that is extension parameters: +### server.close([callback]) -* `serverNoContextTakeover` Boolean: Whether to use context take over or not. -* `clientNoContextTakeover` Boolean: The value to be requested to clients whether to use context take over or not. -* `serverMaxWindowBits` Number: The value of windowBits. -* `clientMaxWindowBits` Number: The value of max windowBits to be requested to clients. -* `memLevel` Number: The value of memLevel. -* `threshold` Number: Payloads smaller than this will not be compressed. Default 1024 bytes. +Close the server and terminate all clients, calls callback when done. -If a property is empty then either an offered configuration or a default value is used. -When sending a fragmented message the length of the first fragment is compared to the threshold. This determines if compression is used for the entire message. +### server.handleUpgrade(request, socket, head, callback) -### server.close([callback]) +- `request` {http.IncomingMessage} The client HTTP GET request. +- `socket` {net.Socket} The network socket between the server and client. +- `head` {Buffer} The first packet of the upgraded stream. +- `callback` {Function}. -Close the server and terminate all clients, calls callback when done with an error if one occurred. +Handle a HTTP upgrade request. When the HTTP server is created internally or +when the HTTP server is passed via the `server` option, this method is called +automatically. When operating in "noServer" mode, this method must be called +manually. -### server.handleUpgrade(request, socket, upgradeHead, callback) +If the upgrade is successfull, the `callback` is called with a `WebSocket` +object as parameter. -Handles a HTTP Upgrade request. `request` is an instance of `http.ServerRequest`, `socket` is an instance of `net.Socket`. +### server.shouldHandle(request) + +- `request` {http.IncomingMessage} The client HTTP GET request. + +See if a given request should be handled by this server. +By default this method validates the pathname of the request, matching it +against the `path` option if provided. +The return value, `true` or `false`, determines whether or not to accept the +handshake. + +This method can be overriden when a custom handling logic is required. + +## Class: WebSocket -When the Upgrade was successfully, the `callback` will be called with a `WebSocket` object as parameter. +This class represents a WebSocket. It extends the `EventEmitter`. + +### Ready state constants + +|Constant | Value | Description | +|-----------|-------|--------------------------------------------------| +|CONNECTING | 0 | The connection is not yet open. | +|OPEN | 1 | The connection is open and ready to communicate. | +|CLOSING | 2 | The connection is in the process of closing. | +|CLOSED | 3 | The connection is closed. | + +### new WebSocket(address[, protocols][, options]) + +- `address` {String} The URL to which to connect. +- `protocols` {String|Array} The list of subprotocols. +- `options` {Object} + - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header. + - `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. + - `headers` {Object} An object with custom headers to send along with the + request. + - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header + depending on the `protocolVersion`. + - `agent` {http.Agent|https.Agent} Use the specified Agent, + - `host` {String} Value of the `Host` header. + - `checkServerIdentity` {Function} A function to validate the server hostname. + - `rejectUnauthorized` {Boolean} Verify or not the server certificate. + - `passphrase` {String} The passphrase for the private key or pfx. + - `ciphers` {String} The ciphers to use or exclude + - `cert` {String|Array|Buffer} The certificate key. + - `key` {String|Array|Buffer} The private key. + - `pfx` {String|Buffer} The private key, certificate, and CA certs. + - `ca` {Array} Trusted certificates. + +`perMessageDeflate` parameters are the same of the server, the only difference +is the direction of requests (e.g. `serverNoContextTakeover` is the value to be +requested to the server). + +Create a new WebSocket instance. + +### Event: 'close' + +- `code` {Number} +- `reason` {String} + +Emitted when the connection is closed. `code` is a numeric value indicating the +status code explaining why the connection has been closed. `reason` is a +human-readable string explaining why the connection has been closed. ### Event: 'error' -`function (error) { }` +- `error` {Error} -If the underlying server emits an error, it will be forwarded here. +Emitted when an error occurs. Errors from the underlying `net.Socket` are +forwarded here. -### Event: 'headers' +### Event: 'message' -`function (headers) { }` +- `data` {String|Buffer} +- `flags` {Object} + - `binary` {Boolean} Specifies if `data` is binary. + - `masked` {Boolean} Specifies if `data` was masked. -Emitted with the object of HTTP headers that are going to be written to the `Stream` as part of the handshake. +Emitted when a message is received from the server. -### Event: 'connection' +### Event: 'open' -`function (socket) { }` +Emitted when the connection is established. -When a new WebSocket connection is established. `socket` is an object of type `WebSocket`. +### Event: 'ping' +- `data` {Buffer} +- `flags` {Object} + - `binary` {Boolean} Specifies if `data` is binary. + - `masked` {Boolean} Specifies if `data` was masked. -## Class: WebSocket +Emitted when a ping is received from the server. + +### Event: 'pong' + +- `data` {Buffer} +- `flags` {Object} + - `binary` {Boolean} Specifies if `data` is binary. + - `masked` {Boolean} Specifies if `data` was masked. + +Emitted when a pong is received from the server. + +### Event: 'unexpected-response' + +- `request` {http.ClientRequest} +- `response` {http.IncomingMessage} + +Emitted when the server response is not the expected one, for example a 401 +response. This event gives the ability to read the response in order to extract +useful information. If the server sends an invalid response and there isn't a +listener for this event, an error is emitted. -This class represents a WebSocket connection. It is an `EventEmitter`. +### websocket.addEventListener(type, listener) -### new WebSocket(address, [protocols], [options]) +- `type` {String} A string representing the event type to listen for. +- `listener` {Function} The listener to add. -* `address` String -* `protocols` String|Array -* `options` Object - * `protocol` String - * `agent` Agent - * `headers` Object - * `protocolVersion` Number - -- the following only apply if `address` is a String - * `host` String - * `origin` String - * `pfx` String|Buffer - * `key` String|Buffer - * `passphrase` String - * `cert` String|Buffer - * `ca` Array - * `ciphers` String - * `rejectUnauthorized` Boolean - * `perMessageDeflate` Boolean|Object - * `localAddress` String +Register an event listener emulating the `EventTarget` interface. -Instantiating with an `address` creates a new WebSocket client object. If `address` is an Array (request, socket, rest), it is instantiated as a Server client (e.g. called from the `WebSocket.Server`). +### websocket.binaryType -### options.perMessageDeflate +- {String} -Parameters of permessage-deflate extension which have the same form with the one for `WebSocket.Server` except the direction of requests. (e.g. `serverNoContextTakeover` is the value to be requested to the server) +A string indicating the type of binary data being transmitted by the connection. +This should be either "nodebuffer" or "arraybuffer". Defaults to "nodebuffer". + +### websocket.bufferedAmount + +- {Number} + +The number of bytes of data that have been queued using calls to `send()` but +not yet transmitted to the network. ### websocket.bytesReceived +- {Number} + Received bytes count. -### websocket.readyState +### websocket.close([code][, reason]) -Possible states are `WebSocket.CONNECTING`, `WebSocket.OPEN`, `WebSocket.CLOSING`, `WebSocket.CLOSED`. +- `code` {Number} A numeric value indicating the status code explaining why + the connection is being closed. +- `reason` {String} A human-readable string explaining why the connection is + closing. -### websocket.protocolVersion +Initiate a closing handshake. -The WebSocket protocol version used for this connection, `8`, `13`. +### websocket.extensions -### websocket.url +- {Object} -The URL of the WebSocket server (only for clients) +An object containing the negotiated extensions. -### websocket.upgradeReq +### websocket.onclose -The http request that initiated the upgrade. Useful for parsing authorty headers, cookie headers and other information to associate a specific Websocket to a specific Client. This is only available for WebSockets constructed by a Server. +- {Function} -### websocket.close([code], [data]) +An event listener to be called when connection is closed. The listener receives +a `CloseEvent` named "close". -Gracefully closes the connection, after sending a description message +### websocket.onerror -### websocket.pause() +- {Function} -Pause the client stream +An event listener to be called when an error occurs. The listener receives +an `Error` instance. -### websocket.ping([data], [options], [dontFailWhenClosed]) +### websocket.onmessage -Sends a ping. `data` is sent, `options` is an object with members `mask` and `binary`. `dontFailWhenClosed` indicates whether or not to throw if the connection isnt open. +- {Function} -### websocket.pong([data], [options], [dontFailWhenClosed]) +An event listener to be called when a message is received from the server. The +listener receives a `MessageEvent` named "message". -Sends a pong. `data` is sent, `options` is an object with members `mask` and `binary`. `dontFailWhenClosed` indicates whether or not to throw if the connection isnt open. +### websocket.onopen +- {Function} -### websocket.resume() +An event listener to be called when the connection is established. The listener +receives an `OpenEvent` named "open". -Resume the client stream +### websocket.pause() -### websocket.send(data, [options], [callback]) +Pause the socket. -* `data` Any The data to send. -* `options` Object An options object. - * `compress` Boolean Specifies whether `data` should be compressed or not. - Defaults to `true` when permessage-deflate is enabled. - * `binary` Boolean Specifies whether `data` should be sent as a binary or not. - Default is autodetected. - * `mask` Boolean Specifies whether `data` should be masked or not. Defaults +### websocket.ping([data[, options[, dontFailWhenClosed]]]) + +- `data` {Any} The data to send in the ping frame. +- `options` {Object} + - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. - * `fin` Boolean Specifies whether `data` is the last fragment of a message or - not. Defaults to `true`. -* `callback` Function An optional callback which is invoked when the send - completes. +- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if + the connection is not open. -Sends `data` through the connection. +Send a ping. -### websocket.stream([options], callback) +### websocket.pong([data[, options[, dontFailWhenClosed]]]) -Streams data through calls to a user supplied function. `options` can be an object with members `mask` and `binary`. `callback` is executed on successive ticks of which send is `function (data, final)`. +- `data` {Any} The data to send in the ping frame. +- `options` {Object} + - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. +- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if + the connection is not open. -### websocket.terminate() +Send a pong. -Immediately shuts down the connection +### websocket.protocol -### websocket.onopen -### websocket.onerror -### websocket.onclose -### websocket.onmessage +- {String} -Emulates the W3C Browser based WebSocket interface using function members. +The subprotocol selected by the server. -### websocket.addEventListener(method, listener) +### websocket.protocolVersion -Emulates the W3C Browser based WebSocket interface using addEventListener. +- {Number} -### Event: 'error' +The WebSocket protocol version used for this connection, 8 or 13. -`function (error) { }` +### websocket.readyState -If the client emits an error, this event is emitted (errors from the underlying `net.Socket` are forwarded here). +- {Number} -### Event: 'close' +The current state of the connection. This is one of the ready state constants. -`function (code, message) { }` +### websocket.removeEventListener(type, listener) -Is emitted when the connection is closed. `code` is defined in the WebSocket specification. +- `type` {String} A string representing the event type to remove. +- `listener` {Function} The listener to remove. -The `close` event is also emitted when then underlying `net.Socket` closes the connection (`end` or `close`). +Removes an event listener emulating the `EventTarget` interface. -### Event: 'message' +### websocket.resume() -`function (data, flags) { }` +Resume the socket -Is emitted when data is received. `flags` is an object with member `binary`. +### websocket.send(data, [options][, callback]) -### Event: 'ping' +- `data` {Any} The data to send. +- `options` {Object} + - `compress` {Boolean} Specifies whether `data` should be compressed or not. + Defaults to `true` when permessage-deflate is enabled. + - `binary` {Boolean} Specifies whether `data` should be sent as a binary or not. + Default is autodetected. + - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. + - `fin` {Boolean} Specifies whether `data` is the last fragment of a message or + not. Defaults to `true`. +- `callback` {Function} An optional callback which is invoked when `data` is + written out. -`function (data, flags) { }` +Sends `data` through the connection. -Is emitted when a ping is received. `flags` is an object with member `binary`. +### websocket.terminate() -### Event: 'pong' +Send a FIN packet to the other peer. -`function (data, flags) { }` +### websocket.upgradeReq -Is emitted when a pong is received. `flags` is an object with member `binary`. +- {http.IncomingMessage} -### Event: 'open' +The http GET request sent by the client. Useful for parsing authorty headers, +cookie headers, and other information. This is only available for server clients. -`function () { }` +### websocket.url -Emitted when the connection is established. +- {String} + +The URL of the WebSocket server. Server clients don't have this attribute. + +[permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 From 099c63647fd95249e8e9f3fc980802169fc16729 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 14 Dec 2016 18:00:29 +0100 Subject: [PATCH 177/489] [major] Refactor `WebSocket` to use class syntax (#930) --- lib/WebSocket.js | 667 +++++++++++++++++++++-------------------- test/WebSocket.test.js | 7 - 2 files changed, 335 insertions(+), 339 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 5899c3a1a..794e74692 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -11,7 +11,6 @@ const crypto = require('crypto'); const Ultron = require('ultron'); const https = require('https'); const http = require('http'); -const util = require('util'); const url = require('url'); const PerMessageDeflate = require('./PerMessageDeflate'); @@ -25,277 +24,391 @@ const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection const protocolVersion = 13; /** - * WebSocket implementation + * Class representing a WebSocket. * - * @constructor - * @param {String} address Connection address. - * @param {String|Array} protocols WebSocket protocols. - * @param {Object} options Additional connection options. - * @api public + * @extends EventEmitter */ -function WebSocket (address, protocols, options) { - if (this instanceof WebSocket === false) { - return new WebSocket(address, protocols, options); - } +class WebSocket extends EventEmitter { + /** + * Create a new `WebSocket`. + * + * @param {String} address The URL to which to connect + * @param {(String|String[])} protocols The subprotocols + * @param {Object} options Connection options + */ + constructor (address, protocols, options) { + super(); + + if (typeof protocols === 'object' && !Array.isArray(protocols)) { + // + // Accept the `options` object as the 2nd argument. + // + options = protocols; + protocols = null; + } - EventEmitter.call(this); + if (typeof protocols === 'string') protocols = [protocols]; + if (!Array.isArray(protocols)) protocols = []; - if (protocols && !Array.isArray(protocols) && typeof protocols === 'object') { - // accept the "options" Object as the 2nd argument - options = protocols; - protocols = null; - } - - if (typeof protocols === 'string') { - protocols = [ protocols ]; - } + this._finalize = this.finalize.bind(this); - if (!Array.isArray(protocols)) { - protocols = []; - } + this._binaryType = 'nodebuffer'; + this._closeReceived = false; + this.bytesReceived = 0; + this.readyState = null; + this.extensions = {}; + this._socket = null; + this._ultron = null; - this._socket = null; - this._ultron = null; - this._closeReceived = false; - this.bytesReceived = 0; - this.readyState = null; - this.extensions = {}; - this._binaryType = 'nodebuffer'; - - if (Array.isArray(address)) { - initAsServerClient.apply(this, address.concat(options)); - } else { - initAsClient.apply(this, [address, protocols, options]); + if (Array.isArray(address)) { + initAsServerClient.call(this, address[0], address[1], address[2], options); + } else { + initAsClient.call(this, address, protocols, options); + } } -} -/** - * Inherits from EventEmitter. - */ -util.inherits(WebSocket, EventEmitter); + get CONNECTING () { return WebSocket.CONNECTING; } + get CLOSING () { return WebSocket.CLOSING; } + get CLOSED () { return WebSocket.CLOSED; } + get OPEN () { return WebSocket.OPEN; } -/** - * Ready States - */ -['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each (state, index) { - WebSocket.prototype[state] = WebSocket[state] = index; -}); + /** + * @type {Number} + */ + get bufferedAmount () { + var amount = 0; -/** - * Gracefully closes the connection, after sending a description message to the server - * - * @param {Object} data to be sent to the server - * @api public - */ -WebSocket.prototype.close = function close (code, data) { - if (this.readyState === WebSocket.CLOSED) return; + if (this._socket) amount = this._socket.bufferSize || 0; + return amount; + } - if (this.readyState === WebSocket.CONNECTING) { - this.readyState = WebSocket.CLOSED; - return; + /** + * This deviates from the WHATWG interface since ws doesn't support the required + * default "blob" type (instead we define a custom "nodebuffer" type). + * + * @type {String} + */ + get binaryType () { + return this._binaryType; } - if (this.readyState === WebSocket.CLOSING) { - if (this._closeReceived && this._isServer) { - this.terminate(); + set binaryType (type) { + if (type === 'arraybuffer' || type === 'nodebuffer') { + this._binaryType = type; + } else { + throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); } - return; } - try { - this.readyState = WebSocket.CLOSING; - this._closeCode = code; - this._closeMessage = data; - var mask = !this._isServer; - this._sender.close(code, data, mask, (err) => { - if (err) this.emit('error', err); + /** + * Set up the socket and the internal resources. + * + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @private + */ + setSocket (socket, head) { + socket.setTimeout(0); + socket.setNoDelay(); + + this._receiver = new Receiver(this.extensions, this.maxPayload); + this._sender = new Sender(socket, this.extensions); + this._ultron = new Ultron(socket); + this._socket = socket; + + // socket cleanup handlers + this._ultron.on('close', this._finalize); + this._ultron.on('error', this._finalize); + this._ultron.on('end', this._finalize); + + // ensure that the head is added to the receiver + if (head && head.length > 0) { + socket.unshift(head); + head = null; + } - if (this._closeReceived && this._isServer) { - this.terminate(); - } else { - // ensure that the connection is cleaned up even when no response of closing handshake. - clearTimeout(this._closeTimer); - this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); - } + // subsequent packets are pushed to the receiver + this._ultron.on('data', (data) => { + this.bytesReceived += data.length; + this._receiver.add(data); }); - } catch (e) { - this.emit('error', e); - } -}; -/** - * Pause the client stream - * - * @api public - */ -WebSocket.prototype.pause = function pauser () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + // receiver event handlers + this._receiver.ontext = (data, flags) => this.emit('message', data, flags); + this._receiver.onbinary = (data, flags) => { + flags.binary = true; + this.emit('message', data, flags); + }; + this._receiver.onping = (data, flags) => { + this.pong(data, { mask: !this._isServer }, true); + this.emit('ping', data, flags); + }; + this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); + this._receiver.onclose = (code, data, flags) => { + this._closeReceived = true; + this.close(code, data); + }; + this._receiver.onerror = (error, errorCode) => { + // close the connection when the receiver reports a HyBi error code + this.close(errorCode, ''); + this.emit('error', error); + }; - return this._socket.pause(); -}; + // sender event handlers + this._sender.onerror = (error) => { + this.close(1002, ''); + this.emit('error', error); + }; -/** - * Sends a ping - * - * @param {Object} data to be sent to the server - * @param {Object} Members - mask: boolean, binary: boolean - * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open - * @api public - */ -WebSocket.prototype.ping = function ping (data, options, dontFailWhenClosed) { - if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed === true) return; - throw new Error('not opened'); + this.readyState = WebSocket.OPEN; + this.emit('open'); } - options = options || {}; + /** + * Clean up and release internal resources and emit the `close` event. + * + * @param {(Boolean|Error)} Indicates whether or not an error occurred + * @private + */ + finalize (error) { + if (this.readyState === WebSocket.CLOSED) return; - if (options.mask === undefined) options.mask = !this._isServer; + this.readyState = WebSocket.CLOSED; - this._sender.ping(data, options); -}; + clearTimeout(this._closeTimer); + this._closeTimer = null; -/** - * Sends a pong - * - * @param {Object} data to be sent to the server - * @param {Object} Members - mask: boolean, binary: boolean - * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open - * @api public - */ -WebSocket.prototype.pong = function (data, options, dontFailWhenClosed) { - if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed === true) return; - throw new Error('not opened'); - } + // If the connection was closed abnormally (with an error), or if + // the close control frame was not received then the close code + // must default to 1006. + if (error || !this._closeReceived) { + this._closeCode = 1006; + } + this.emit('close', this._closeCode || 1000, this._closeMessage || ''); - options = options || {}; + if (this._socket) { + if (this._ultron) this._ultron.destroy(); + this._socket.on('error', function onerror () { + try { + this.destroy(); + } catch (e) {} + }); + + try { + if (!error) this._socket.end(); + else this._socket.destroy(); + } catch (e) { /* Ignore termination errors */ } - if (options.mask === undefined) options.mask = !this._isServer; + this._socket = null; + this._ultron = null; + } - this._sender.pong(data, options); -}; + if (this._sender) { + this._sender = this._sender.onerror = null; + } -/** - * Resume the client stream - * - * @api public - */ -WebSocket.prototype.resume = function resume () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this._receiver) { + this._receiver.cleanup(); + this._receiver = null; + } - return this._socket.resume(); -}; + if (this.extensions[PerMessageDeflate.extensionName]) { + this.extensions[PerMessageDeflate.extensionName].cleanup(); + } -/** - * Sends a piece of data - * - * @param {Object} data to be sent to the server - * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean - * @param {function} Optional callback which is executed after the send completes - * @api public - */ + this.extensions = null; -WebSocket.prototype.send = function send (data, options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; + this.removeAllListeners(); + this.on('error', function onerror () {}); // catch all errors after this } - if (this.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else throw new Error('not opened'); - return; + /** + * Pause the socket stream. + * + * @public + */ + pause () { + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + + this._socket.pause(); } - if (!data) data = ''; + /** + * Resume the socket stream + * + * @public + */ + resume () { + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + + this._socket.resume(); + } - options = options || {}; - if (options.fin !== false) options.fin = true; + /** + * Start a closing handshake. + * + * @param {Number} code Status code explaining why the connection is closing + * @param {String} data A string explaining why the connection is closing + * @public + */ + close (code, data) { + if (this.readyState === WebSocket.CLOSED) return; + + if (this.readyState === WebSocket.CONNECTING) { + this.readyState = WebSocket.CLOSED; + return; + } - if (options.binary === undefined) { - options.binary = data instanceof Buffer || data instanceof ArrayBuffer || - ArrayBuffer.isView(data); + if (this.readyState === WebSocket.CLOSING) { + if (this._closeReceived && this._isServer) { + this.terminate(); + } + return; + } + + try { + this.readyState = WebSocket.CLOSING; + this._closeCode = code; + this._closeMessage = data; + var mask = !this._isServer; + this._sender.close(code, data, mask, (err) => { + if (err) this.emit('error', err); + + if (this._closeReceived && this._isServer) { + this.terminate(); + } else { + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } + }); + } catch (e) { + this.emit('error', e); + } } - if (options.mask === undefined) options.mask = !this._isServer; - if (options.compress === undefined) options.compress = true; - if (!this.extensions[PerMessageDeflate.extensionName]) { - options.compress = false; + /** + * Send a ping message. + * + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Indicates whether or not to mask `data` + * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @public + */ + ping (data, options, dontFailWhenClosed) { + if (this.readyState !== WebSocket.OPEN) { + if (dontFailWhenClosed) return; + throw new Error('not opened'); + } + + options = options || {}; + if (options.mask === undefined) options.mask = !this._isServer; + + this._sender.ping(data, options); } - this._sender.send(data, options, cb); -}; + /** + * Send a pong message. + * + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Indicates whether or not to mask `data` + * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @public + */ + pong (data, options, dontFailWhenClosed) { + if (this.readyState !== WebSocket.OPEN) { + if (dontFailWhenClosed) return; + throw new Error('not opened'); + } -/** - * Immediately shuts down the connection - * - * @api public - */ -WebSocket.prototype.terminate = function terminate () { - if (this.readyState === WebSocket.CLOSED) return; + options = options || {}; + if (options.mask === undefined) options.mask = !this._isServer; - if (this._socket) { - this.readyState = WebSocket.CLOSING; + this._sender.pong(data, options); + } - // End the connection - try { - this._socket.end(); - } catch (e) { - // Socket error during end() call, so just destroy it right now - cleanupWebsocketResources.call(this, true); + /** + * Send a data message. + * + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.compress Specifies whether or not to compress `data` + * @param {Boolean} options.binary Specifies whether `data` is binary or text + * @param {Boolean} options.fin Specifies whether the fragment is the last one + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Function} cb Callback which is executed when data is written out + * @public + */ + send (data, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } + + if (this.readyState !== WebSocket.OPEN) { + if (cb) cb(new Error('not opened')); + else throw new Error('not opened'); return; } - // Add a timeout to ensure that the connection is completely - // cleaned up within 30 seconds, even if the clean close procedure - // fails for whatever reason - // First cleanup any pre-existing timeout from an earlier "terminate" call, - // if one exists. Otherwise terminate calls in quick succession will leak timeouts - // and hold the program open for `closeTimout` time. - if (this._closeTimer) { clearTimeout(this._closeTimer); } - this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); - } else if (this.readyState === WebSocket.CONNECTING) { - cleanupWebsocketResources.call(this, true); - } -}; + if (!data) data = ''; -/** - * Expose the `bufferedAmount` attribute. - * - * @public - */ -Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { - get () { - var amount = 0; - if (this._socket) { - amount = this._socket.bufferSize || 0; + options = options || {}; + if (options.fin !== false) options.fin = true; + + if (options.binary === undefined) { + options.binary = data instanceof Buffer || data instanceof ArrayBuffer || + ArrayBuffer.isView(data); } - return amount; + + if (options.mask === undefined) options.mask = !this._isServer; + if (options.compress === undefined) options.compress = true; + if (!this.extensions[PerMessageDeflate.extensionName]) { + options.compress = false; + } + + this._sender.send(data, options, cb); } -}); -/** - * Expose the `binaryType` attribute. - * - * This deviates from the WHATWG interface since ws doesn't support the required - * default "blob" type (instead we define a custom "nodebuffer" type). - * - * @see {@link https://html.spec.whatwg.org/multipage/comms.html#dom-websocket-binarytype} - * @public - */ -Object.defineProperty(WebSocket.prototype, 'binaryType', { - get () { - return this._binaryType; - }, - set (type) { - if (type === 'arraybuffer' || type === 'nodebuffer') { - this._binaryType = type; - } else { - throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); + /** + * Half-close the socket sending a FIN packet. + * + * @public + */ + terminate () { + if (this.readyState === WebSocket.CLOSED) return; + + if (this._socket) { + this.readyState = WebSocket.CLOSING; + + try { + this._socket.end(); + } catch (e) { + this.finalize(true); + return; + } + + // + // Add a timeout to ensure that the connection is completely cleaned up + // within 30 seconds, even if the other peer does not send a FIN packet. + // + if (this._closeTimer) clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } else if (this.readyState === WebSocket.CONNECTING) { + this.finalize(true); } } -}); +} + +WebSocket.CONNECTING = 0; +WebSocket.OPEN = 1; +WebSocket.CLOSING = 2; +WebSocket.CLOSED = 3; // // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes. @@ -375,7 +488,7 @@ function initAsServerClient (req, socket, head, options) { this.upgradeReq = req; this._isServer = true; - establishConnection.call(this, socket, head); + this.setSocket(socket, head); } /** @@ -543,7 +656,7 @@ function initAsClient (address, protocols, options) { req.on('error', (error) => { this.emit('error', error); - cleanupWebsocketResources.call(this, error); + this.finalize(error); }); req.on('response', (res) => { @@ -555,10 +668,10 @@ function initAsClient (address, protocols, options) { this.emit('error', error); } - cleanupWebsocketResources.call(this, error); + this.finalize(error); }); - req.on('upgrade', (res, socket, upgradeHead) => { + req.on('upgrade', (res, socket, head) => { if (this.readyState === WebSocket.CLOSED) { // client closed before server accepted connection this.emit('close'); @@ -612,119 +725,9 @@ function initAsClient (address, protocols, options) { this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - establishConnection.call(this, socket, upgradeHead); + this.setSocket(socket, head); - // perform cleanup on http resources req.removeAllListeners(); agent = null; }); } - -function establishConnection (socket, upgradeHead) { - socket.setTimeout(0); - socket.setNoDelay(); - - this._receiver = new Receiver(this.extensions, this.maxPayload); - this._sender = new Sender(socket, this.extensions); - this._ultron = new Ultron(socket); - this._socket = socket; - - // socket cleanup handlers - this._ultron.on('end', cleanupWebsocketResources.bind(this)); - this._ultron.on('close', cleanupWebsocketResources.bind(this)); - this._ultron.on('error', cleanupWebsocketResources.bind(this)); - - // ensure that the upgradeHead is added to the receiver - if (upgradeHead && upgradeHead.length > 0) { - socket.unshift(upgradeHead); - upgradeHead = null; - } - - // subsequent packets are pushed to the receiver - this._ultron.on('data', (data) => { - this.bytesReceived += data.length; - this._receiver.add(data); - }); - - // receiver event handlers - this._receiver.ontext = (data, flags) => this.emit('message', data, flags); - this._receiver.onbinary = (data, flags) => { - flags.binary = true; - this.emit('message', data, flags); - }; - this._receiver.onping = (data, flags) => { - this.pong(data, { mask: !this._isServer }, true); - this.emit('ping', data, flags); - }; - this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); - this._receiver.onclose = (code, data, flags) => { - this._closeReceived = true; - this.close(code, data); - }; - this._receiver.onerror = (error, errorCode) => { - // close the connection when the receiver reports a HyBi error code - this.close(errorCode, ''); - this.emit('error', error); - }; - - // sender event handlers - this._sender.onerror = (error) => { - this.close(1002, ''); - this.emit('error', error); - }; - - this.readyState = WebSocket.OPEN; - this.emit('open'); -} - -function cleanupWebsocketResources (error) { - if (this.readyState === WebSocket.CLOSED) return; - - this.readyState = WebSocket.CLOSED; - - clearTimeout(this._closeTimer); - this._closeTimer = null; - - // If the connection was closed abnormally (with an error), or if - // the close control frame was not received then the close code - // must default to 1006. - if (error || !this._closeReceived) { - this._closeCode = 1006; - } - this.emit('close', this._closeCode || 1000, this._closeMessage || ''); - - if (this._socket) { - if (this._ultron) this._ultron.destroy(); - this._socket.on('error', function onerror () { - try { - this.destroy(); - } catch (e) {} - }); - - try { - if (!error) this._socket.end(); - else this._socket.destroy(); - } catch (e) { /* Ignore termination errors */ } - - this._socket = null; - this._ultron = null; - } - - if (this._sender) { - this._sender = this._sender.onerror = null; - } - - if (this._receiver) { - this._receiver.cleanup(); - this._receiver = null; - } - - if (this.extensions[PerMessageDeflate.extensionName]) { - this.extensions[PerMessageDeflate.extensionName].cleanup(); - } - - this.extensions = null; - - this.removeAllListeners(); - this.on('error', function onerror () {}); // catch all errors after this -} diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 9771978cc..cd4846557 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -20,13 +20,6 @@ class CustomAgent extends http.Agent { describe('WebSocket', function () { describe('#ctor', function () { - it('should return a new instance if called without new', function (done) { - const ws = WebSocket('ws://localhost'); - - assert.ok(ws instanceof WebSocket); - ws.on('error', () => done()); - }); - it('throws an error when using an invalid url', function () { assert.throws( () => new WebSocket('echo.websocket.org'), From 68994638178123e4fd7ad798d5286e257bda71cb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 14 Dec 2016 19:35:46 +0100 Subject: [PATCH 178/489] [minor] Set `readyState` to `CONNECTING` in the constructor --- lib/WebSocket.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 794e74692..309106743 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -52,10 +52,10 @@ class WebSocket extends EventEmitter { this._finalize = this.finalize.bind(this); + this.readyState = WebSocket.CONNECTING; this._binaryType = 'nodebuffer'; this._closeReceived = false; this.bytesReceived = 0; - this.readyState = null; this.extensions = {}; this._socket = null; this._ultron = null; @@ -484,7 +484,6 @@ function initAsServerClient (req, socket, head, options) { this.maxPayload = options.maxPayload; this.protocol = options.protocol; - this.readyState = WebSocket.CONNECTING; this.upgradeReq = req; this._isServer = true; @@ -571,7 +570,6 @@ function initAsClient (address, protocols, options) { // Expose state properties. // this.protocolVersion = options.protocolVersion; - this.readyState = WebSocket.CONNECTING; this._isServer = false; this.url = address; From cf40f959c3c5bd5baee51432cfeaedd4c3ddb165 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Dec 2016 09:54:02 +0100 Subject: [PATCH 179/489] [minor] Initialize more properties in the constructor --- lib/WebSocket.js | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 309106743..4877e2e3f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -40,9 +40,6 @@ class WebSocket extends EventEmitter { super(); if (typeof protocols === 'object' && !Array.isArray(protocols)) { - // - // Accept the `options` object as the 2nd argument. - // options = protocols; protocols = null; } @@ -50,13 +47,17 @@ class WebSocket extends EventEmitter { if (typeof protocols === 'string') protocols = [protocols]; if (!Array.isArray(protocols)) protocols = []; - this._finalize = this.finalize.bind(this); - this.readyState = WebSocket.CONNECTING; - this._binaryType = 'nodebuffer'; - this._closeReceived = false; this.bytesReceived = 0; this.extensions = {}; + this.protocol = ''; + + this._finalize = this.finalize.bind(this); + this._binaryType = 'nodebuffer'; + this._closeReceived = false; + this._closeTimer = null; + this._receiver = null; + this._sender = null; this._socket = null; this._ultron = null; @@ -501,7 +502,7 @@ function initAsServerClient (req, socket, head, options) { * @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 - * @param {String} options.origin Value of the `Sec-WebSocket-Version` header + * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header * @param {http.Agent} options.agent Use the specified Agent * @param {String} options.host Value of the `Host` header * @param {Function} options.checkServerIdentity A function to validate the server hostname @@ -542,6 +543,10 @@ function initAsClient (address, protocols, options) { throw new Error('unsupported protocol version'); } + this.protocolVersion = options.protocolVersion; + this._isServer = false; + this.url = address; + const serverUrl = url.parse(address); const isUnixSocket = serverUrl.protocol === 'ws+unix:'; @@ -566,15 +571,6 @@ function initAsClient (address, protocols, options) { extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } - // - // Expose state properties. - // - this.protocolVersion = options.protocolVersion; - this._isServer = false; - this.url = address; - - var agent = options.agent; - const requestOptions = { host: serverUrl.hostname, path: '/', @@ -619,6 +615,8 @@ function initAsClient (address, protocols, options) { } } + var agent = options.agent; + // // A custom agent is required for these options. // From 76143e9909f1dc6d4630ad2e3c3c8e9681402eec Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Dec 2016 10:11:16 +0100 Subject: [PATCH 180/489] [minor] Remove unnecessary `buildHostHeader` function (#932) --- lib/WebSocket.js | 25 +------------------------ test/WebSocket.test.js | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 4877e2e3f..fc45b08e3 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -444,27 +444,6 @@ WebSocket.prototype.addEventListener = EventTarget.addEventListener; WebSocket.prototype.removeEventListener = EventTarget.removeEventListener; module.exports = WebSocket; -module.exports.buildHostHeader = buildHostHeader; - -/** - * Append port number to Host header, only if specified in the URL and - * non-default. - * - * @param {Boolean} isSecure Specifies whether or not the URL scheme is `wss` - * @param {String} hostname The hostname portion of the URL - * @param {Number} port The port portion of the URL - * @return {String} The field value of the `Host` header - * @private - */ -function buildHostHeader (isSecure, hostname, port) { - var headerHost = hostname; - - if (headerHost && (isSecure && port !== 443 || !isSecure && port !== 80)) { - headerHost = `${headerHost}:${port}`; - } - - return headerHost; -} /** * Initialize a WebSocket server client. @@ -554,7 +533,6 @@ function initAsClient (address, protocols, options) { const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); - const port = serverUrl.port || (isSecure ? 443 : 80); const httpObj = isSecure ? https : http; // @@ -573,10 +551,9 @@ function initAsClient (address, protocols, options) { const requestOptions = { host: serverUrl.hostname, + port: serverUrl.port, path: '/', - port, headers: { - 'Host': buildHostHeader(isSecure, serverUrl.hostname, port), 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key, 'Connection': 'Upgrade', diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index cd4846557..ffea6767d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1453,17 +1453,26 @@ describe('WebSocket', function () { }); it('excludes default ports from host header', function () { - // can't create a server listening on ports 80 or 443 - // so we need to expose the method that does this - const buildHostHeader = WebSocket.buildHostHeader; - let host = buildHostHeader(false, 'localhost', 80); - assert.strictEqual(host, 'localhost'); - host = buildHostHeader(false, 'localhost', 88); - assert.strictEqual(host, 'localhost:88'); - host = buildHostHeader(true, 'localhost', 443); - assert.strictEqual(host, 'localhost'); - host = buildHostHeader(true, 'localhost', 8443); - assert.strictEqual(host, 'localhost:8443'); + const httpsAgent = new https.Agent(); + const httpAgent = new http.Agent(); + const values = []; + let ws; + + httpsAgent.addRequest = httpAgent.addRequest = (req) => { + values.push(req._headers.host); + }; + + ws = new WebSocket('wss://localhost:8443', { agent: httpsAgent }); + ws = new WebSocket('wss://localhost:443', { agent: httpsAgent }); + ws = new WebSocket('ws://localhost:88', { agent: httpAgent }); + ws = new WebSocket('ws://localhost:80', { agent: httpAgent }); + + assert.deepStrictEqual(values, [ + 'localhost:8443', + 'localhost', + 'localhost:88', + 'localhost' + ]); }); }); From 9c116af775bf37ac1a47c093a2376b2cbe66fdeb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 16 Dec 2016 18:12:30 +0100 Subject: [PATCH 181/489] [minor] Change `Sender.prototype.frameAndSend()` signature (#933) --- bench/sender.benchmark.js | 35 +++++++--- lib/Sender.js | 137 +++++++++++++++++++++++--------------- test/Sender.test.js | 24 ++++++- 3 files changed, 131 insertions(+), 65 deletions(-) diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 8473e561d..a0f19a870 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -17,20 +17,35 @@ const data3 = crypto.randomBytes(64 * 1024); const data4 = crypto.randomBytes(200 * 1024); const data5 = crypto.randomBytes(1024 * 1024); +const opts1 = { + readOnly: false, + mask: false, + rsv1: false, + opcode: 2, + fin: true +}; +const opts2 = { + readOnly: true, + rsv1: false, + mask: true, + opcode: 2, + fin: true +}; + const suite = new benchmark.Suite(); var sender = new Sender(); sender._socket = { write () {} }; -suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(0x2, data1, false, true, false)); -suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(0x2, data1, true, true, true)); -suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(0x2, data2, false, true, false)); -suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(0x2, data2, true, true, true)); -suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(0x2, data3, false, true, false)); -suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(0x2, data3, true, true, true)); -suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data4, false, true, false)); -suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data4, true, true, true)); -suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data5, false, true, false)); -suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data5, true, true, true)); +suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(data1, opts1)); +suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(data1, opts2)); +suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(data2, opts1)); +suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(data2, opts2)); +suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(data3, opts1)); +suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(data3, opts2)); +suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(data4, opts1)); +suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(data4, opts2)); +suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(data5, opts1)); +suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(data5, opts2)); suite.on('cycle', (e) => console.log(e.target.toString())); diff --git a/lib/Sender.js b/lib/Sender.js index 0091a849b..7476524a2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -12,6 +12,8 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil').BufferUtil; const ErrorCodes = require('./ErrorCodes'); +const noop = () => {}; + /** * HyBi Sender implementation. */ @@ -23,12 +25,12 @@ class Sender { * @param {Object} extensions An object containing the negotiated extensions */ constructor (socket, extensions) { - this.extensions = extensions || {}; + this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName]; this.firstFragment = true; this.processing = false; this.compress = false; this._socket = socket; - this.onerror = null; + this.onerror = noop; this.queue = []; } @@ -51,8 +53,8 @@ class Sender { buf.writeUInt16BE(code || 1000, 0, true); if (buf.length > 2) buf.write(data, 2); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doClose, [buf, mask, cb]]); + if (this.perMessageDeflate) { + this.enqueue([this.doClose, buf, mask, cb]); } else { this.doClose(buf, mask, cb); } @@ -67,10 +69,15 @@ class Sender { * @private */ doClose (data, mask, cb) { - this.frameAndSend(0x08, data, false, true, mask, false, cb); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.continue(); - } + this.frameAndSend(data, { + readOnly: false, + opcode: 0x08, + rsv1: false, + fin: true, + mask + }, cb); + + if (this.perMessageDeflate) this.continue(); } /** @@ -82,8 +89,8 @@ class Sender { * @public */ ping (data, options) { - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPing, [data, options.mask]]); + if (this.perMessageDeflate) { + this.enqueue([this.doPing, data, options.mask]); } else { this.doPing(data, options.mask); } @@ -97,10 +104,15 @@ class Sender { * @private */ doPing (data, mask) { - this.frameAndSend(0x09, data, true, true, mask, false); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.continue(); - } + this.frameAndSend(data, { + readOnly: true, + opcode: 0x09, + rsv1: false, + fin: true, + mask + }); + + if (this.perMessageDeflate) this.continue(); } /** @@ -112,8 +124,8 @@ class Sender { * @public */ pong (data, options) { - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPong, [data, options.mask]]); + if (this.perMessageDeflate) { + this.enqueue([this.doPong, data, options.mask]); } else { this.doPong(data, options.mask); } @@ -127,10 +139,15 @@ class Sender { * @private */ doPong (data, mask) { - this.frameAndSend(0x0a, data, true, true, mask, false); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.continue(); - } + this.frameAndSend(data, { + readOnly: true, + opcode: 0x0a, + rsv1: false, + fin: true, + mask + }); + + if (this.perMessageDeflate) this.continue(); } /** @@ -146,7 +163,6 @@ class Sender { * @public */ send (data, options, cb) { - const pmd = this.extensions[PerMessageDeflate.extensionName]; var opcode = options.binary ? 2 : 1; var rsv1 = options.compress; var readOnly = true; @@ -164,7 +180,9 @@ class Sender { if (this.firstFragment) { this.firstFragment = false; - if (rsv1 && data && pmd) rsv1 = data.length >= pmd.threshold; + if (rsv1 && data && this.perMessageDeflate) { + rsv1 = data.length >= this.perMessageDeflate.threshold; + } this.compress = rsv1; } else { rsv1 = false; @@ -173,41 +191,55 @@ class Sender { if (options.fin) this.firstFragment = true; - if (pmd) { - const args = [opcode, data, readOnly, options.fin, options.mask, rsv1, cb]; - this.enqueue([this.sendCompressed, args]); + if (this.perMessageDeflate) { + this.enqueue([this.sendCompressed, data, { + mask: options.mask, + fin: options.fin, + readOnly, + opcode, + rsv1 + }, cb]); } else { - this.frameAndSend(opcode, data, readOnly, options.fin, options.mask, false, cb); + this.frameAndSend(data, { + mask: options.mask, + fin: options.fin, + rsv1: false, + readOnly, + opcode + }, cb); } } /** * Compresses, frames and sends a data message. * - * @param {Number} opcode The opcode - * @param {*} data The message to send - * @param {Boolean} readOnly Specifies whether `data` can be modified - * @param {Boolean} fin Specifies whether or not to set the FIN bit - * @param {Boolean} mask Specifies whether or not to mask `data` - * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit + * @param {Buffer} data The message to send + * @param {Object} options Options object + * @param {Number} options.opcode The opcode + * @param {Boolean} options.readOnly Specifies whether `data` can be modified + * @param {Boolean} options.fin Specifies whether or not to set the FIN bit + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (opcode, data, readOnly, fin, mask, rsv1, cb) { + sendCompressed (data, options, cb) { if (!this.compress) { - this.frameAndSend(opcode, data, readOnly, fin, mask, false, cb); + options.rsv1 = false; + this.frameAndSend(data, options, cb); this.continue(); return; } - this.extensions[PerMessageDeflate.extensionName].compress(data, fin, (err, buf) => { + this.perMessageDeflate.compress(data, options.fin, (err, buf) => { if (err) { if (cb) cb(err); else this.onerror(err); return; } - this.frameAndSend(opcode, buf, false, fin, mask, rsv1, cb); + options.readOnly = false; + this.frameAndSend(buf, options, cb); this.continue(); }); } @@ -215,21 +247,22 @@ class Sender { /** * Frames and sends a piece of data according to the HyBi WebSocket protocol. * - * @param {Number} opcode The opcode * @param {*} data The data to send - * @param {Boolean} readOnly Specifies whether `data` can be modified - * @param {Boolean} fin Specifies whether or not to set the FIN bit - * @param {Boolean} maskData Specifies whether or not to mask `data` - * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit + * @param {Object} options Options object + * @param {Number} options.opcode The opcode + * @param {Boolean} options.readOnly Specifies whether `data` can be modified + * @param {Boolean} options.fin Specifies whether or not to set the FIN bit + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - frameAndSend (opcode, data, readOnly, fin, maskData, rsv1, cb) { + frameAndSend (data, options, cb) { if (!data) { - const bytes = [opcode, 0]; + const bytes = [options.opcode, 0]; - if (fin) bytes[0] |= 0x80; - if (maskData) { + if (options.fin) bytes[0] |= 0x80; + if (options.mask) { bytes[1] |= 0x80; bytes.push(0, 0, 0, 0); } @@ -245,12 +278,12 @@ class Sender { data = viewToBuffer(data); } else { data = Buffer.from(typeof data === 'number' ? data.toString() : data); - readOnly = false; + options.readOnly = false; } } - const mergeBuffers = data.length < 1024 || maskData && readOnly; - var dataOffset = maskData ? 6 : 2; + const mergeBuffers = data.length < 1024 || options.mask && options.readOnly; + var dataOffset = options.mask ? 6 : 2; var payloadLength = data.length; if (data.length >= 65536) { @@ -265,8 +298,8 @@ class Sender { mergeBuffers ? data.length + dataOffset : dataOffset ); - outputBuffer[0] = fin ? opcode | 0x80 : opcode; - if (rsv1) outputBuffer[0] |= 0x40; + outputBuffer[0] = options.fin ? options.opcode | 0x80 : options.opcode; + if (options.rsv1) outputBuffer[0] |= 0x40; if (payloadLength === 126) { outputBuffer.writeUInt16BE(data.length, 2, true); @@ -275,7 +308,7 @@ class Sender { outputBuffer.writeUInt32BE(data.length, 6, true); } - if (maskData) { + if (options.mask) { const mask = getRandomMask(); outputBuffer[1] = payloadLength | 0x80; @@ -310,7 +343,7 @@ class Sender { this.processing = true; - handler[0].apply(this, handler[1]); + handler[0].apply(this, handler.slice(1)); } /** diff --git a/test/Sender.test.js b/test/Sender.test.js index 1b7ed063f..06840ce01 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -17,7 +17,13 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const buf = Buffer.from([1, 2, 3, 4, 5]); - sender.frameAndSend(2, buf, true, true, true); + sender.frameAndSend(buf, { + readOnly: true, + rsv1: false, + mask: true, + opcode: 2, + fin: true + }); assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); @@ -26,7 +32,13 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const text = Buffer.from('hi there'); - sender.frameAndSend(1, text, true, true, true); + sender.frameAndSend(text, { + readOnly: true, + rsv1: false, + mask: true, + opcode: 1, + fin: true + }); assert.ok(text.equals(Buffer.from('hi there'))); }); @@ -39,7 +51,13 @@ describe('Sender', function () { } }); - sender.frameAndSend(1, Buffer.from('hi'), false, true, false, true); + sender.frameAndSend(Buffer.from('hi'), { + readOnly: false, + mask: false, + rsv1: true, + opcode: 1, + fin: true + }); }); }); From 103fddbeafee3a349646908071b6d44bf10dd3ef Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Dec 2016 10:28:21 +0100 Subject: [PATCH 182/489] [test] Clean up some permessage-deflate tests --- test/Receiver.test.js | 10 ++-- test/WebSocket.test.js | 113 ++++++++++++++++------------------------- 2 files changed, 52 insertions(+), 71 deletions(-) diff --git a/test/Receiver.test.js b/test/Receiver.test.js index ef5c249e0..d54cbb65d 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -692,7 +692,7 @@ describe('Receiver', function () { it('can cleanup when consuming data', function (done) { const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); + perMessageDeflate.accept([{ server_no_context_takeover: [true] }]); const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); @@ -701,11 +701,15 @@ describe('Receiver', function () { if (err) return done(err); const data = Buffer.concat([Buffer.from([0xc1, compressed.length]), compressed]); + p.add(data); p.add(data); - p.add(data); + + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, data.length); + + perMessageDeflate._inflate.on('close', done); p.cleanup(); - setTimeout(done, 1000); }); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index ffea6767d..35b004e49 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1523,11 +1523,11 @@ describe('WebSocket', function () { server.on('upgrade', (req, socket, head) => { const extensions = req.headers['sec-websocket-extensions']; - assert.notStrictEqual(extensions.indexOf('permessage-deflate'), -1); - assert.notStrictEqual(extensions.indexOf('server_no_context_takeover'), -1); - assert.notStrictEqual(extensions.indexOf('client_no_context_takeover'), -1); - assert.notStrictEqual(extensions.indexOf('server_max_window_bits=10'), -1); - assert.notStrictEqual(extensions.indexOf('client_max_window_bits'), -1); + assert.ok(extensions.includes('permessage-deflate')); + assert.ok(extensions.includes('server_no_context_takeover')); + assert.ok(extensions.includes('client_no_context_takeover')); + assert.ok(extensions.includes('server_max_window_bits=10')); + assert.ok(extensions.includes('client_max_window_bits')); }); server.listen(++port, () => { @@ -1549,25 +1549,22 @@ describe('WebSocket', function () { it('can send and receive text data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); ws.on('open', () => ws.send('hi', { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); @@ -1579,25 +1576,22 @@ describe('WebSocket', function () { } const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); ws.on('open', () => ws.send(array, { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); @@ -1609,25 +1603,22 @@ describe('WebSocket', function () { } const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); ws.on('open', () => ws.send(array.buffer, { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); @@ -1639,17 +1630,14 @@ describe('WebSocket', function () { }); ws.on('open', () => ws.send('hi', { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); }); @@ -1657,27 +1645,26 @@ describe('WebSocket', function () { describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); - let errorGiven = false; ws.on('open', () => { - ws.send('hi', (error) => { - errorGiven = !!error; - }); + ws.send('hi', (error) => assert.ifError(error)); ws.close(); }); + }); - ws.on('close', () => { - setTimeout(() => { - assert.ok(!errorGiven); - wss.close(); - done(); - }, 1000); + wss.on('connection', (ws) => { + ws.on('message', (message) => { + assert.strictEqual(message, 'hi'); + ws.on('close', (code) => { + assert.strictEqual(code, 1000); + wss.close(done); + }); }); }); }); @@ -1686,52 +1673,42 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); - let errorGiven = false; ws.on('open', () => { ws.send('hi', (error) => { - errorGiven = !!error; + assert.ok(error instanceof Error); + wss.close(done); }); ws.terminate(); }); - - ws.on('close', () => { - setTimeout(() => { - assert.ok(errorGiven); - wss.close(); - done(); - }, 1000); - }); }); }); it('can call during receiving data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); wss.on('connection', (client) => { for (let i = 0; i < 10; i++) { client.send('hi'); } - client.send('hi', () => ws.terminate()); - }); - - ws.on('close', () => { - setTimeout(() => { - wss.close(); - done(); - }, 1000); + client.send('hi', () => { + ws.extensions['permessage-deflate']._inflate.on('close', () => { + wss.close(done); + }); + ws.terminate(); + }); }); }); }); From 5816e33059cfad4bf9fa533fa2474d6e60ed2ca0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Dec 2016 10:32:09 +0100 Subject: [PATCH 183/489] [minor] Remove unnecessary `try...catch` statements (#937) --- lib/Sender.js | 15 ++++-------- lib/WebSocket.js | 61 ++++++++++++++++++------------------------------ 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 7476524a2..eab04dbfd 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -409,15 +409,10 @@ function getRandomMask () { * @private */ function sendFramedData (sender, outputBuffer, data, cb) { - try { - if (data) { - sender._socket.write(outputBuffer); - sender._socket.write(data, cb); - } else { - sender._socket.write(outputBuffer, cb); - } - } catch (e) { - if (cb) cb(e); - else sender.onerror(e); + if (data) { + sender._socket.write(outputBuffer); + sender._socket.write(data, cb); + } else { + sender._socket.write(outputBuffer, cb); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index fc45b08e3..f1f1cd548 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -188,17 +188,13 @@ class WebSocket extends EventEmitter { this.emit('close', this._closeCode || 1000, this._closeMessage || ''); if (this._socket) { - if (this._ultron) this._ultron.destroy(); + this._ultron.destroy(); this._socket.on('error', function onerror () { - try { - this.destroy(); - } catch (e) {} + this.destroy(); }); - try { - if (!error) this._socket.end(); - else this._socket.destroy(); - } catch (e) { /* Ignore termination errors */ } + if (!error) this._socket.end(); + else this._socket.destroy(); this._socket = null; this._ultron = null; @@ -267,28 +263,23 @@ class WebSocket extends EventEmitter { return; } - try { - this.readyState = WebSocket.CLOSING; - this._closeCode = code; - this._closeMessage = data; - var mask = !this._isServer; - this._sender.close(code, data, mask, (err) => { - if (err) this.emit('error', err); - - if (this._closeReceived && this._isServer) { - this.terminate(); - } else { - // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. - // - clearTimeout(this._closeTimer); - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); - } - }); - } catch (e) { - this.emit('error', e); - } + this.readyState = WebSocket.CLOSING; + this._closeCode = code; + this._closeMessage = data; + this._sender.close(code, data, !this._isServer, (err) => { + if (err) this.emit('error', err); + + if (this._closeReceived && this._isServer) { + this.terminate(); + } else { + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } + }); } /** @@ -386,19 +377,13 @@ class WebSocket extends EventEmitter { if (this._socket) { this.readyState = WebSocket.CLOSING; - - try { - this._socket.end(); - } catch (e) { - this.finalize(true); - return; - } + this._socket.end(); // // Add a timeout to ensure that the connection is completely cleaned up // within 30 seconds, even if the other peer does not send a FIN packet. // - if (this._closeTimer) clearTimeout(this._closeTimer); + clearTimeout(this._closeTimer); this._closeTimer = setTimeout(this._finalize, closeTimeout, true); } else if (this.readyState === WebSocket.CONNECTING) { this.finalize(true); From fc956f8071e3930de432676ea2ea9cc075ee9fde Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Dec 2016 12:37:05 +0100 Subject: [PATCH 184/489] [minor] Remove unnecessary `_closeReceived` property --- lib/WebSocket.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f1f1cd548..c1061bc84 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -54,8 +54,9 @@ class WebSocket extends EventEmitter { this._finalize = this.finalize.bind(this); this._binaryType = 'nodebuffer'; - this._closeReceived = false; + this._closeMessage = null; this._closeTimer = null; + this._closeCode = null; this._receiver = null; this._sender = null; this._socket = null; @@ -145,9 +146,10 @@ class WebSocket extends EventEmitter { this.emit('ping', data, flags); }; this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); - this._receiver.onclose = (code, data, flags) => { - this._closeReceived = true; - this.close(code, data); + this._receiver.onclose = (code, reason) => { + this._closeMessage = reason; + this._closeCode = code; + this.close(code, reason); }; this._receiver.onerror = (error, errorCode) => { // close the connection when the receiver reports a HyBi error code @@ -179,13 +181,13 @@ class WebSocket extends EventEmitter { clearTimeout(this._closeTimer); this._closeTimer = null; - // If the connection was closed abnormally (with an error), or if - // the close control frame was not received then the close code - // must default to 1006. - if (error || !this._closeReceived) { - this._closeCode = 1006; - } - this.emit('close', this._closeCode || 1000, this._closeMessage || ''); + // + // If the connection was closed abnormally (with an error), or if the close + // control frame was malformed or not received then the close code must be + // 1006. + // + if (error) this._closeCode = 1006; + this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this._socket) { this._ultron.destroy(); @@ -257,19 +259,17 @@ class WebSocket extends EventEmitter { } if (this.readyState === WebSocket.CLOSING) { - if (this._closeReceived && this._isServer) { + if (this._closeCode && this._isServer) { this.terminate(); } return; } this.readyState = WebSocket.CLOSING; - this._closeCode = code; - this._closeMessage = data; this._sender.close(code, data, !this._isServer, (err) => { if (err) this.emit('error', err); - if (this._closeReceived && this._isServer) { + if (this._closeCode && this._isServer) { this.terminate(); } else { // From 097f39fdd0ea2b4029aa1df05783966aa636fe5a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Dec 2016 15:08:15 +0100 Subject: [PATCH 185/489] [major] Use the same handler for binary and text messages --- lib/Receiver.js | 10 ++++------ lib/WebSocket.js | 10 +++------- test/Receiver.test.js | 34 +++++++++++++++++----------------- test/testserver.js | 9 ++------- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 62d68c361..07cbb9b66 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -53,10 +53,9 @@ class Receiver { this.dead = false; - this.onbinary = noop; + this.onmessage = noop; this.onclose = noop; this.onerror = noop; - this.ontext = noop; this.onping = noop; this.onpong = noop; @@ -350,14 +349,14 @@ class Receiver { this.fragmented = 0; if (this.opcode === 2) { - this.onbinary(buf, { masked: this.masked }); + this.onmessage(buf, { masked: this.masked, binary: true }); } else { if (!Validation.isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(buf.toString(), { masked: this.masked }); + this.onmessage(buf.toString(), { masked: this.masked }); } } @@ -477,10 +476,9 @@ class Receiver { this.buffers = null; this.mask = null; - this.onbinary = null; + this.onmessage = null; this.onclose = null; this.onerror = null; - this.ontext = null; this.onping = null; this.onpong = null; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index c1061bc84..98b5eeec9 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -136,11 +136,7 @@ class WebSocket extends EventEmitter { }); // receiver event handlers - this._receiver.ontext = (data, flags) => this.emit('message', data, flags); - this._receiver.onbinary = (data, flags) => { - flags.binary = true; - this.emit('message', data, flags); - }; + this._receiver.onmessage = (data, flags) => this.emit('message', data, flags); this._receiver.onping = (data, flags) => { this.pong(data, { mask: !this._isServer }, true); this.emit('ping', data, flags); @@ -151,9 +147,9 @@ class WebSocket extends EventEmitter { this._closeCode = code; this.close(code, reason); }; - this._receiver.onerror = (error, errorCode) => { + this._receiver.onerror = (error, code) => { // close the connection when the receiver reports a HyBi error code - this.close(errorCode, ''); + this.close(code, ''); this.emit('error', error); }; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index d54cbb65d..c139b5f3f 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -17,7 +17,7 @@ describe('Receiver', function () { it('can parse unmasked text message', function (done) { const p = new Receiver(); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, 'Hello'); done(); }; @@ -40,7 +40,7 @@ describe('Receiver', function () { it('can parse masked text message', function (done) { const p = new Receiver(); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, '5:::{"name":"echo"}'); done(); }; @@ -56,7 +56,7 @@ describe('Receiver', function () { const frame = Buffer.from('81FE' + util.pack(4, msg.length) + mask + util.mask(msg, mask).toString('hex'), 'hex'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; @@ -73,7 +73,7 @@ describe('Receiver', function () { const frame = '81FF' + util.pack(16, msg.length) + mask + util.mask(msg, mask).toString('hex'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; @@ -94,7 +94,7 @@ describe('Receiver', function () { const frame2 = '80FE' + util.pack(4, fragment2.length) + mask + util.mask(fragment2, mask).toString('hex'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; @@ -148,7 +148,7 @@ describe('Receiver', function () { let gotPing = false; - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); assert.ok(gotPing); done(); @@ -187,7 +187,7 @@ describe('Receiver', function () { let gotPing = false; - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); assert.ok(gotPing); done(); @@ -210,8 +210,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data.toString('hex'), msg.toString('hex')); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -226,8 +226,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data, msg); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -242,8 +242,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data, msg); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -257,8 +257,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + msg.toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data, msg); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -272,7 +272,7 @@ describe('Receiver', function () { const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, 'Hello'); done(); }; @@ -293,7 +293,7 @@ describe('Receiver', function () { const buf1 = Buffer.from('foo'); const buf2 = Buffer.from('bar'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, 'foobar'); done(); }; diff --git a/test/testserver.js b/test/testserver.js index 48e1e287d..8214650e8 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -62,14 +62,9 @@ function validServer (server, req, socket) { receiver.onping = (message, flags) => server.emit('ping', message, flags); receiver.onpong = (message, flags) => server.emit('pong', message, flags); - receiver.ontext = (message, flags) => { + receiver.onmessage = (message, flags) => { server.emit('message', message, flags); - sender.send(message, { fin: true }); - }; - receiver.onbinary = (message, flags) => { - flags.binary = true; - server.emit('message', message, flags); - sender.send(message, { binary: true, fin: true }); + sender.send(message, { binary: flags.binary, fin: true }); }; receiver.onclose = (code, message, flags) => { sender.close(code, message, false, () => { From 4c3586c7f81734417a863190eea7e7c72d2e1558 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Dec 2016 18:26:32 +0100 Subject: [PATCH 186/489] [fix] Prevent callback from being called twice This prevents the callback of `PerMessageDeflate.prototype.decompress()` from being called twice when `maxPayload` is exceeded. --- lib/PerMessageDeflate.js | 47 +++++++++++++++++++--------------- test/PerMessageDeflate.test.js | 31 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index f8400712b..d1569546f 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -215,37 +215,37 @@ class PerMessageDeflate { } /** - * Decompress message + * Decompress data. * - * @api public + * @param {Buffer} data Compressed data + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @public */ decompress (data, fin, callback) { - var endpoint = this._isServer ? 'client' : 'server'; + const endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { - var maxWindowBits = this.params[endpoint + '_max_window_bits']; + const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; this._inflate = zlib.createInflateRaw({ windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS }); } this._inflate.writeInProgress = true; + var totalLength = 0; const buffers = []; - var cumulativeBufferLength = 0; + var err; const onData = (data) => { - if (this._maxPayload > 0) { - cumulativeBufferLength += data.length; - if (cumulativeBufferLength > this._maxPayload) { - const err = new Error('max payload size exceeded'); - err.closeCode = 1009; - buffers.length = 0; - cleanup(); - callback(err); - return; - } + totalLength += data.length; + if (this._maxPayload < 1 || totalLength <= this._maxPayload) { + return buffers.push(data); } - buffers.push(data); + + err = new Error('max payload size exceeded'); + err.closeCode = 1009; + this._inflate.reset(); }; const onError = (err) => { @@ -255,10 +255,15 @@ class PerMessageDeflate { const cleanup = () => { if (!this._inflate) return; + this._inflate.removeListener('error', onError); this._inflate.removeListener('data', onData); this._inflate.writeInProgress = false; - if ((fin && this.params[endpoint + '_no_context_takeover']) || this._inflate.pendingClose) { + + if ( + fin && this.params[`${endpoint}_no_context_takeover`] || + this._inflate.pendingClose + ) { this._inflate.close(); this._inflate = null; } @@ -266,12 +271,12 @@ class PerMessageDeflate { this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); - if (fin) { - this._inflate.write(TRAILER); - } + if (fin) this._inflate.write(TRAILER); + this._inflate.flush(() => { cleanup(); - callback(null, Buffer.concat(buffers)); + if (err) callback(err); + else callback(null, Buffer.concat(buffers, totalLength)); }); } diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index fb1706813..b95a162c8 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -339,5 +339,36 @@ describe('PerMessageDeflate', function () { }); }); }); + + it('should call the callback when an error occurs (inflate)', function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const data = Buffer.from('something invalid'); + + perMessageDeflate.accept([{}]); + perMessageDeflate.decompress(data, true, (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.errno, -3); + done(); + }); + }); + + it('should not call the callback twice when `maxPayload` is exceeded', function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, false, 25); + const buf = Buffer.from('A'.repeat(50)); + const errors = []; + + perMessageDeflate.accept([{}]); + perMessageDeflate.compress(buf, true, (err, data) => { + if (err) return done(err); + + perMessageDeflate.decompress(data, true, (err) => errors.push(err)); + perMessageDeflate._inflate.flush(() => { + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof Error); + assert.strictEqual(errors[0].message, 'max payload size exceeded'); + done(); + }); + }); + }); }); }); From 9b708e82279081082f027469be9b0befffacff14 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Dec 2016 18:50:36 +0100 Subject: [PATCH 187/489] [pkg] Fix package description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6b592113..8c1033663 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", - "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", + "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "version": "1.1.0", "license": "MIT", "main": "index.js", From 51993d41d4dd63cea51525d3c683fd0d7c031625 Mon Sep 17 00:00:00 2001 From: Kevin Vu Date: Thu, 22 Dec 2016 22:50:16 -0800 Subject: [PATCH 188/489] [doc] Fix typo in ws.md (#942) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index f25035623..63f786b5f 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -396,7 +396,7 @@ Send a FIN packet to the other peer. - {http.IncomingMessage} -The http GET request sent by the client. Useful for parsing authorty headers, +The http GET request sent by the client. Useful for parsing authority headers, cookie headers, and other information. This is only available for server clients. ### websocket.url From fa03221b70d5e0864e7b836ded740e950cc7786f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 23 Dec 2016 09:28:09 +0100 Subject: [PATCH 189/489] [test] Remove redundant tests --- test/PerMessageDeflate.test.js | 6 ------ test/Receiver.test.js | 11 +++-------- test/Sender.test.js | 6 ------ 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index b95a162c8..3da9de4bc 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -6,12 +6,6 @@ const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Extensions = require('../lib/Extensions'); describe('PerMessageDeflate', function () { - describe('#ctor', function () { - it('throws TypeError when called without new', function () { - assert.throws(PerMessageDeflate, TypeError); - }); - }); - describe('#offer', function () { it('should create default params', function () { const perMessageDeflate = new PerMessageDeflate(); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index c139b5f3f..18dc711ac 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -8,12 +8,6 @@ const Receiver = require('../lib/Receiver'); const util = require('./hybi-util'); describe('Receiver', function () { - describe('#ctor', function () { - it('throws TypeError when called without new', function () { - assert.throws(Receiver, TypeError); - }); - }); - it('can parse unmasked text message', function (done) { const p = new Receiver(); @@ -692,7 +686,7 @@ describe('Receiver', function () { it('can cleanup when consuming data', function (done) { const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{ server_no_context_takeover: [true] }]); + perMessageDeflate.accept([{}]); const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); @@ -708,8 +702,9 @@ describe('Receiver', function () { assert.strictEqual(p.state, 5); assert.strictEqual(p.bufferedBytes, data.length); - perMessageDeflate._inflate.on('close', done); p.cleanup(); + + perMessageDeflate._inflate.flush(done); }); }); }); diff --git a/test/Sender.test.js b/test/Sender.test.js index 06840ce01..5f33cb955 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -6,12 +6,6 @@ const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Sender = require('../lib/Sender'); describe('Sender', function () { - describe('#ctor', function () { - it('throws TypeError when called without new', function () { - assert.throws(Sender, TypeError); - }); - }); - describe('#frameAndSend', function () { it('does not modify a masked binary buffer', function () { const sender = new Sender({ write: () => {} }); From 549cbe11527320f123e423bb13a1b20bc1807874 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 23 Dec 2016 10:03:04 +0100 Subject: [PATCH 190/489] [test] Simplify some tests --- test/WebSocket.test.js | 73 ++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 35b004e49..24732207d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1390,65 +1390,56 @@ describe('WebSocket', function () { describe('host and origin headers', function () { it('includes the host header with port number', function (done) { - const server = http.createServer(); + const agent = new CustomAgent(); - server.listen(++port, () => { - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers.host, `localhost:${port}`); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers.host, `localhost:${port}`); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`); - }); + const ws = new WebSocket(`ws://localhost:${port}`, { agent }); }); it('lacks default origin header', function (done) { - const server = http.createServer(); + const agent = new CustomAgent(); - server.listen(++port, () => { - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers.origin, undefined); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers.origin, undefined); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`); - }); + const ws = new WebSocket(`ws://localhost:${port}`, { agent }); }); it('honors origin set in options (1/2)', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const options = { origin: 'https://example.com:8000' }; + const agent = new CustomAgent(); - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers.origin, options.origin); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers.origin, 'https://example.com:8000'); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`, options); + const ws = new WebSocket(`ws://localhost:${port}`, { + origin: 'https://example.com:8000', + agent }); }); it('honors origin set in options (2/2)', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const options = { - origin: 'https://example.com:8000', - protocolVersion: 8 - }; + const agent = new CustomAgent(); - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['sec-websocket-origin'], options.origin); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual( + req._headers['sec-websocket-origin'], + 'https://example.com:8000' + ); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`, options); + const ws = new WebSocket(`ws://localhost:${port}`, { + origin: 'https://example.com:8000', + protocolVersion: 8, + agent }); }); From cb58cc97808ef90031c49031f9b2a31c9cd7213b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 1 Jan 2017 11:49:16 +0100 Subject: [PATCH 191/489] [minor] Initialize `Receiver` handlers to `null` --- lib/Receiver.js | 12 +++++------- test/Receiver.test.js | 31 +++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 07cbb9b66..389c173a6 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -20,8 +20,6 @@ const GET_MASK = 3; const GET_DATA = 4; const HANDLE_DATA = 5; -const noop = () => {}; - /** * HyBi Receiver implementation. */ @@ -53,11 +51,11 @@ class Receiver { this.dead = false; - this.onmessage = noop; - this.onclose = noop; - this.onerror = noop; - this.onping = noop; - this.onpong = noop; + this.onmessage = null; + this.onclose = null; + this.onerror = null; + this.onping = null; + this.onpong = null; this.state = START; } diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 18dc711ac..1ee811cea 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -309,39 +309,50 @@ describe('Receiver', function () { it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { const p = new Receiver({}, 10); + let message; + + p.onmessage = function (msg) { + message = msg; + }; assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from('810548656c6c6f', 'hex')); assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(message, 'Hello'); }); it('resets `totalPayloadLength` only on final frame (fragmented)', function () { const p = new Receiver({}, 10); + let message; - const frame1 = '01024865'; - const frame2 = '80036c6c6f'; + p.onmessage = function (msg) { + message = msg; + }; assert.strictEqual(p.totalPayloadLength, 0); - p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from('01024865', 'hex')); assert.strictEqual(p.totalPayloadLength, 2); - p.add(Buffer.from(frame2, 'hex')); + p.add(Buffer.from('80036c6c6f', 'hex')); assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(message, 'Hello'); }); it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function () { const p = new Receiver({}, 10); + const data = []; - const frame1 = '01024865'; - const frame2 = '8900'; - const frame3 = '80036c6c6f'; + p.onmessage = p.onping = function (buf) { + data.push(buf.toString()); + }; assert.strictEqual(p.totalPayloadLength, 0); - p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from('02024865', 'hex')); assert.strictEqual(p.totalPayloadLength, 2); - p.add(Buffer.from(frame2, 'hex')); + p.add(Buffer.from('8900', 'hex')); assert.strictEqual(p.totalPayloadLength, 2); - p.add(Buffer.from(frame3, 'hex')); + p.add(Buffer.from('80036c6c6f', 'hex')); assert.strictEqual(p.totalPayloadLength, 0); + assert.deepStrictEqual(data, ['', 'Hello']); }); it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { From 2ffd470e9408d086c0e8fe5ddedc11bd19acb9ea Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 1 Jan 2017 17:30:28 +0100 Subject: [PATCH 192/489] [minor] Prevent out-of-range slices in `Receiver#readBuffer()` --- lib/Receiver.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 389c173a6..07aa05967 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -67,19 +67,17 @@ class Receiver { * @private */ readBuffer (bytes) { - var bufoff = 0; + var offset = 0; var dst; var l; - if (bytes === this.buffers[0].length) { - this.bufferedBytes -= bytes; - return this.buffers.shift(); - } + this.bufferedBytes -= bytes; + + if (bytes === this.buffers[0].length) return this.buffers.shift(); if (bytes < this.buffers[0].length) { dst = this.buffers[0].slice(0, bytes); this.buffers[0] = this.buffers[0].slice(bytes); - this.bufferedBytes -= bytes; return dst; } @@ -88,15 +86,13 @@ class Receiver { while (bytes > 0) { l = this.buffers[0].length; - if (bytes > l) { - this.buffers[0].copy(dst, bufoff); - bufoff += l; + if (bytes >= l) { + this.buffers[0].copy(dst, offset); + offset += l; this.buffers.shift(); - this.bufferedBytes -= l; } else { - this.buffers[0].copy(dst, bufoff, 0, bytes); + this.buffers[0].copy(dst, offset, 0, bytes); this.buffers[0] = this.buffers[0].slice(bytes); - this.bufferedBytes -= bytes; } bytes -= l; From 8846f14396e4cb0b52d3c4146e00478754d209f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 1 Jan 2017 22:12:31 +0100 Subject: [PATCH 193/489] [benchmark] Fix parser benchmark --- bench/parser.benchmark.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 63883f8ea..2924b5449 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -43,8 +43,10 @@ const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024); const binaryDataPacket4 = createBinaryPacket(1024 * 1024); -const receiver = new Receiver(); const suite = new benchmark.Suite(); +const receiver = new Receiver(); + +receiver.onmessage = receiver.onclose = receiver.onping = () => {}; suite.add('ping message', () => receiver.add(pingPacket1)); suite.add('ping with no data', () => receiver.add(pingPacket2)); From 338327c8ef834fe751609550163896517e6ebd6c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Jan 2017 15:39:19 +0100 Subject: [PATCH 194/489] [major] Use a boolean instead of an options object --- doc/ws.md | 22 ++++++++++------------ lib/Sender.js | 22 +++++++++------------- lib/WebSocket.js | 32 +++++++++++++------------------- test/Sender.test.js | 9 ++++----- test/WebSocket.test.js | 8 ++++---- 5 files changed, 40 insertions(+), 53 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 63f786b5f..7839ff433 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -320,25 +320,23 @@ receives an `OpenEvent` named "open". Pause the socket. -### websocket.ping([data[, options[, dontFailWhenClosed]]]) +### websocket.ping([data[, mask[, failSilently]]]) - `data` {Any} The data to send in the ping frame. -- `options` {Object} - - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults - to `true` when `websocket` is not a server client. -- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if - the connection is not open. +- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. +- `failSilently` {Boolean} Specifies whether or not to throw an error if the + connection is not open. Send a ping. -### websocket.pong([data[, options[, dontFailWhenClosed]]]) +### websocket.pong([data[, mask[, failSilently]]]) - `data` {Any} The data to send in the ping frame. -- `options` {Object} - - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults - to `true` when `websocket` is not a server client. -- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if - the connection is not open. +- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. +- `failSilently` {Boolean} Specifies whether or not to throw an error if the + connection is not open. Send a pong. diff --git a/lib/Sender.js b/lib/Sender.js index eab04dbfd..c1f4eaae3 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -12,8 +12,6 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil').BufferUtil; const ErrorCodes = require('./ErrorCodes'); -const noop = () => {}; - /** * HyBi Sender implementation. */ @@ -30,7 +28,7 @@ class Sender { this.processing = false; this.compress = false; this._socket = socket; - this.onerror = noop; + this.onerror = null; this.queue = []; } @@ -84,15 +82,14 @@ class Sender { * Sends a ping message to the other peer. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} mask Specifies whether or not to mask `data` * @public */ - ping (data, options) { + ping (data, mask) { if (this.perMessageDeflate) { - this.enqueue([this.doPing, data, options.mask]); + this.enqueue([this.doPing, data, mask]); } else { - this.doPing(data, options.mask); + this.doPing(data, mask); } } @@ -119,15 +116,14 @@ class Sender { * Sends a pong message to the other peer. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} mask Specifies whether or not to mask `data` * @public */ - pong (data, options) { + pong (data, mask) { if (this.perMessageDeflate) { - this.enqueue([this.doPong, data, options.mask]); + this.enqueue([this.doPong, data, mask]); } else { - this.doPong(data, options.mask); + this.doPong(data, mask); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 98b5eeec9..8565b39c3 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -138,7 +138,7 @@ class WebSocket extends EventEmitter { // receiver event handlers this._receiver.onmessage = (data, flags) => this.emit('message', data, flags); this._receiver.onping = (data, flags) => { - this.pong(data, { mask: !this._isServer }, true); + this.pong(data, !this._isServer, true); this.emit('ping', data, flags); }; this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); @@ -282,42 +282,36 @@ class WebSocket extends EventEmitter { * Send a ping message. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Indicates whether or not to mask `data` - * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @param {Boolean} mask Indicates whether or not to mask `data` + * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` * @public */ - ping (data, options, dontFailWhenClosed) { + ping (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed) return; + if (failSilently) return; throw new Error('not opened'); } - options = options || {}; - if (options.mask === undefined) options.mask = !this._isServer; - - this._sender.ping(data, options); + if (mask === undefined) mask = !this._isServer; + this._sender.ping(data, mask); } /** * Send a pong message. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Indicates whether or not to mask `data` - * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @param {Boolean} mask Indicates whether or not to mask `data` + * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` * @public */ - pong (data, options, dontFailWhenClosed) { + pong (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed) return; + if (failSilently) return; throw new Error('not opened'); } - options = options || {}; - if (options.mask === undefined) options.mask = !this._isServer; - - this._sender.pong(data, options); + if (mask === undefined) mask = !this._isServer; + this._sender.pong(data, mask); } /** diff --git a/test/Sender.test.js b/test/Sender.test.js index 5f33cb955..b0ce4598f 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -70,12 +70,11 @@ describe('Sender', function () { }); const array = new Uint8Array([0x68, 0x69]); - const options = { mask: false }; - sender.ping(array.buffer, options); - sender.ping(array, options); - sender.ping('hi', options); - sender.ping(10, options); + sender.ping(array.buffer, false); + sender.ping(array, false); + sender.ping('hi', false); + sender.ping(10, false); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 24732207d..751d1c5b2 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -491,7 +491,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', () => {}); - ws.ping('', {}, true); + ws.ping('', true, true); srv.close(done); ws.terminate(); @@ -550,7 +550,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.ping('hi', { mask: true })); + ws.on('open', () => ws.ping('hi', true)); }); }); }); @@ -576,7 +576,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', () => {}); - ws.pong('', {}, true); + ws.pong('', true, true); srv.close(done); ws.terminate(); @@ -621,7 +621,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.pong('hi', { mask: true })); + ws.on('open', () => ws.pong('hi', true)); }); }); }); From 623872f13f6ed5aa14dcb2c0ee54fc427e80740a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 08:24:07 +0100 Subject: [PATCH 195/489] [minor] Simplify closing handshake (#914) Fixes #784 --- lib/WebSocket.js | 6 ++---- test/WebSocket.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 98b5eeec9..ecede686e 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -255,9 +255,7 @@ class WebSocket extends EventEmitter { } if (this.readyState === WebSocket.CLOSING) { - if (this._closeCode && this._isServer) { - this.terminate(); - } + if (this._closeCode) this.terminate(); return; } @@ -265,7 +263,7 @@ class WebSocket extends EventEmitter { this._sender.close(code, data, !this._isServer, (err) => { if (err) this.emit('error', err); - if (this._closeCode && this._isServer) { + if (this._closeCode) { this.terminate(); } else { // diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 24732207d..cc7004316 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1010,6 +1010,39 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1013)); }); + + it('closes the connection when an error occurs', function (done) { + const server = http.createServer(); + const wss = new WebSocketServer({ server }); + let closed = false; + + wss.on('connection', (ws) => { + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1006); + assert.strictEqual(reason, ''); + + closed = true; + }); + }); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws._socket.write(Buffer.from([0xa1, 0x00]))); + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1002); + assert.strictEqual(reason, ''); + assert.ok(closed); + + server.close(done); + }); + }); + }); }); describe('WHATWG API emulation', function () { From 44f9e89a501026b0459bad725216dc9500355d63 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 08:27:42 +0100 Subject: [PATCH 196/489] [fix] Consume all data before emitting the `close` event (#945) Fixes #799 Fixes #923 --- lib/Receiver.js | 66 +++++++++++++------ lib/WebSocket.js | 26 ++++++-- test/Receiver.test.js | 142 +++++++++++++++++++++++++++++++++-------- test/WebSocket.test.js | 62 ++++++++++++------ 4 files changed, 224 insertions(+), 72 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 07aa05967..b79b19370 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -49,6 +49,8 @@ class Receiver { this.messageLength = 0; this.fragments = []; + this.cleanupCallback = null; + this.hadError = false; this.dead = false; this.onmessage = null; @@ -101,6 +103,21 @@ class Receiver { return dst; } + /** + * Checks if the number of buffered bytes is bigger or equal than `n` and + * calls `cleanup` if necessary. + * + * @param {Number} n The number of bytes to check against + * @return {Boolean} `true` if `bufferedBytes >= n`, else `false` + * @private + */ + hasBufferedBytes (n) { + if (this.bufferedBytes >= n) return true; + + if (this.dead) this.cleanup(this.cleanupCallback); + return false; + } + /** * Adds new data to the parser. * @@ -136,7 +153,7 @@ class Receiver { * @private */ start () { - if (this.bufferedBytes < 2) return; + if (!this.hasBufferedBytes(2)) return; const buf = this.readBuffer(2); @@ -216,7 +233,7 @@ class Receiver { * @private */ getPayloadLength16 () { - if (this.bufferedBytes < 2) return; + if (!this.hasBufferedBytes(2)) return; this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); this.haveLength(); @@ -228,7 +245,7 @@ class Receiver { * @private */ getPayloadLength64 () { - if (this.bufferedBytes < 8) return; + if (!this.hasBufferedBytes(8)) return; const buf = this.readBuffer(8); const num = buf.readUInt32BE(0, true); @@ -271,7 +288,7 @@ class Receiver { * @private */ getMask () { - if (this.bufferedBytes < 4) return; + if (!this.hasBufferedBytes(4)) return; this.mask = this.readBuffer(4); this.state = GET_DATA; @@ -287,7 +304,7 @@ class Receiver { var data = EMPTY_BUFFER; if (this.payloadLength) { - if (this.bufferedBytes < this.payloadLength) return; + if (!this.hasBufferedBytes(this.payloadLength)) return; data = this.readBuffer(this.payloadLength); if (this.masked) bufferUtil.unmask(data, this.mask); @@ -313,8 +330,6 @@ class Receiver { const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(data, this.fin, (err, buf) => { - if (this.dead) return; - if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; @@ -368,7 +383,7 @@ class Receiver { if (this.opcode === 0x08) { if (data.length === 0) { this.onclose(1000, '', { masked: this.masked }); - this.cleanup(); + this.cleanup(this.cleanupCallback); } else if (data.length === 1) { this.error(new Error('invalid payload length'), 1002); } else { @@ -387,7 +402,7 @@ class Receiver { } this.onclose(code, buf.toString(), { masked: this.masked }); - this.cleanup(); + this.cleanup(this.cleanupCallback); } return; @@ -411,7 +426,8 @@ class Receiver { */ error (err, code) { this.onerror(err, code); - this.cleanup(); + this.hadError = true; + this.cleanup(this.cleanupCallback); } /** @@ -460,21 +476,29 @@ class Receiver { /** * Releases resources used by the receiver. * + * @param {Function} cb Callback * @public */ - cleanup () { + cleanup (cb) { this.dead = true; - this.extensions = null; - this.fragments = null; - this.buffers = null; - this.mask = null; - - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; + if (!this.hadError && this.state === HANDLE_DATA) { + this.cleanupCallback = cb; + } else { + this.extensions = null; + this.fragments = null; + this.buffers = null; + this.mask = null; + + this.cleanupCallback = null; + this.onmessage = null; + this.onclose = null; + this.onerror = null; + this.onping = null; + this.onpong = null; + + if (cb) cb(); + } } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ecede686e..b2cc91966 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -22,6 +22,7 @@ const Sender = require('./Sender'); const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. const protocolVersion = 13; +const noop = () => {}; /** * Class representing a WebSocket. @@ -54,6 +55,7 @@ class WebSocket extends EventEmitter { this._finalize = this.finalize.bind(this); this._binaryType = 'nodebuffer'; + this._finalizeCalled = false; this._closeMessage = null; this._closeTimer = null; this._closeCode = null; @@ -164,15 +166,16 @@ class WebSocket extends EventEmitter { } /** - * Clean up and release internal resources and emit the `close` event. + * Clean up and release internal resources. * * @param {(Boolean|Error)} Indicates whether or not an error occurred * @private */ finalize (error) { - if (this.readyState === WebSocket.CLOSED) return; + if (this._finalizeCalled) return; - this.readyState = WebSocket.CLOSED; + this.readyState = WebSocket.CLOSING; + this._finalizeCalled = true; clearTimeout(this._closeTimer); this._closeTimer = null; @@ -183,7 +186,6 @@ class WebSocket extends EventEmitter { // 1006. // if (error) this._closeCode = 1006; - this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this._socket) { this._ultron.destroy(); @@ -203,9 +205,21 @@ class WebSocket extends EventEmitter { } if (this._receiver) { - this._receiver.cleanup(); + this._receiver.cleanup(() => this.emitClose()); this._receiver = null; + } else { + this.emitClose(); } + } + + /** + * Emit the `close` event. + * + * @private + */ + emitClose () { + this.readyState = WebSocket.CLOSED; + this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this.extensions[PerMessageDeflate.extensionName]) { this.extensions[PerMessageDeflate.extensionName].cleanup(); @@ -214,7 +228,7 @@ class WebSocket extends EventEmitter { this.extensions = null; this.removeAllListeners(); - this.on('error', function onerror () {}); // catch all errors after this + this.on('error', noop); // Catch all errors after this. } /** diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 1ee811cea..7129228be 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -549,7 +549,7 @@ describe('Receiver', function () { p.add(Buffer.from([0x88, 0x01, 0x00])); }); - it('raises an error if a close frame contains a invalid close code', function (done) { + it('raises an error if a close frame contains an invalid close code', function (done) { const p = new Receiver(); p.error = function (err, code) { @@ -593,7 +593,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('raises an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { + it('raises an error on a 200 KiB long unmasked binary message when `maxPayload` is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -661,13 +661,9 @@ describe('Receiver', function () { }); }); - it('will not crash if another message is received after receiving a message that exceeds maxpayload', function (done) { - const perMessageDeflate = new PerMessageDeflate({}, false, 2); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); - const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); - const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + it('doesn\'t crash if data is received after `maxPayload` is exceeded', function (done) { + const p = new Receiver({}, 5); + const buf = crypto.randomBytes(10); let gotError = false; @@ -676,46 +672,140 @@ describe('Receiver', function () { assert.strictEqual(code, 1009); }; - perMessageDeflate.compress(buf1, false, function (err, compressed1) { + p.add(Buffer.from([0x82, buf.length])); + + assert.ok(gotError); + assert.strictEqual(p.onerror, null); + + p.add(buf); + done(); + }); + + it('consumes all data before calling `cleanup` callback (1/4)', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); + const results = []; + + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - p.add(Buffer.from([0x41, compressed1.length])); - p.add(compressed1); + const frame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - assert.ok(gotError); - assert.strictEqual(p.onerror, null); + p.add(frame); + p.add(frame); - perMessageDeflate.compress(buf2, true, function (err, compressed2) { - if (err) return done(err); + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, frame.length); - p.add(Buffer.from([0x80, compressed2.length])); - p.add(compressed2); + p.cleanup(() => { + assert.deepStrictEqual(results, ['Hello', 'Hello']); + assert.strictEqual(p.onmessage, null); done(); }); }); }); - it('can cleanup when consuming data', function (done) { + it('consumes all data before calling `cleanup` callback (2/4)', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); + const results = []; - perMessageDeflate.compress(buf, true, function (err, compressed) { + p.onclose = (code, reason) => results.push(code, reason); + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - const data = Buffer.concat([Buffer.from([0xc1, compressed.length]), compressed]); + const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); + const closeFrame = Buffer.from([0x88, 0x00]); - p.add(data); - p.add(data); + p.add(textFrame); + p.add(textFrame); + p.add(closeFrame); assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, data.length); + assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length); - p.cleanup(); + p.cleanup(() => { + assert.deepStrictEqual(results, ['Hello', 'Hello', 1000, '']); + assert.strictEqual(p.onmessage, null); + done(); + }); + }); + }); - perMessageDeflate._inflate.flush(done); + it('consumes all data before calling `cleanup` callback (3/4)', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); + const results = []; + + p.onerror = (err, code) => results.push(err.message, code); + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { + if (err) return done(err); + + const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); + const invalidFrame = Buffer.from([0xa0, 0x00]); + + p.add(textFrame); + p.add(textFrame); + p.add(invalidFrame); + + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length); + + p.cleanup(() => { + assert.deepStrictEqual(results, [ + 'Hello', + 'Hello', + 'RSV2 and RSV3 must be clear', + 1002 + ]); + assert.strictEqual(p.onmessage, null); + done(); + }); + }); + }); + + it('consumes all data before calling `cleanup` callback (4/4)', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); + const results = []; + + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { + if (err) return done(err); + + const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); + const incompleteFrame = Buffer.from([0x82, 0x0a, 0x00, 0x00]); + + p.add(textFrame); + p.add(incompleteFrame); + + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, incompleteFrame.length); + + p.cleanup(() => { + assert.deepStrictEqual(results, ['Hello']); + assert.strictEqual(p.onmessage, null); + done(); + }); }); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index cc7004316..7d938880a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -945,7 +945,7 @@ describe('WebSocket', function () { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.close(1000, 'some reason', { mask: true })); + ws.on('open', () => ws.close(1000, 'some reason')); srv.on('close', (code, message, flags) => { assert.ok(flags.masked); @@ -963,7 +963,7 @@ describe('WebSocket', function () { ws.on('open', () => { connectedOnce = true; - ws.close(1000, 'some reason', {mask: true}); + ws.close(1000, 'some reason'); }); ws.on('close', () => { @@ -974,28 +974,28 @@ describe('WebSocket', function () { }); }); - it('consumes all data when the server socket closed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - wss.on('connection', (conn) => { - conn.send('foo'); - conn.send('bar'); - conn.send('baz'); - conn.close(); - }); - + it('permits all buffered data to be delivered', function (done) { + const wss = new WebSocketServer({ + perMessageDeflate: { threshold: 0 }, + port: ++port + }, () => { const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; - ws.on('message', (message) => { - messages.push(message); - if (messages.length === 3) { - assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); - - wss.close(done); - ws.terminate(); - } + ws.on('message', (message) => messages.push(message)); + ws.on('close', (code) => { + assert.strictEqual(code, 1000); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); + wss.close(done); }); }); + + wss.on('connection', (ws) => { + ws.send('foo'); + ws.send('bar'); + ws.send('baz'); + ws.close(); + }); }); it('allows close code 1013', function (done) { @@ -1646,6 +1646,30 @@ describe('WebSocket', function () { }); }); + it('consumes all received data when connection is closed abnormally', function (done) { + const wss = new WebSocketServer({ + perMessageDeflate: { threshold: 0 }, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + const messages = []; + + ws.on('message', (message) => messages.push(message)); + ws.on('close', (code) => { + assert.strictEqual(code, 1006); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz', 'qux']); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + ws.send('foo'); + ws.send('bar'); + ws.send('baz'); + ws.send('qux', () => ws._socket.end()); + }); + }); + describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { From aabbffc956a6360cf740cdd7a049235be2392260 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 08:42:55 +0100 Subject: [PATCH 197/489] [test] Increase test timeout --- test/WebSocket.test.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 7d938880a..be523dc03 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -254,10 +254,6 @@ describe('WebSocket', function () { }); }); - /* - * Ready state constants - */ - const readyStates = { CONNECTING: 0, OPEN: 1, @@ -265,10 +261,6 @@ describe('WebSocket', function () { CLOSED: 3 }; - /* - * Ready state constant tests - */ - Object.keys(readyStates).forEach((state) => { describe(`.${state}`, function () { it('is enumerable property of class', function () { @@ -1393,6 +1385,8 @@ describe('WebSocket', function () { }); it('can send and receive very long binary data', function (done) { + this.timeout(4000); + const buf = crypto.randomBytes(5 * 1024 * 1024); const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), From 45d5c389ca54fa8fa111dc103540680fb532c051 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 15:14:48 +0100 Subject: [PATCH 198/489] [fix] Treat 0 like any other number --- lib/Sender.js | 4 ++-- lib/WebSocket.js | 12 +++++------- test/Sender.test.js | 12 +++--------- test/WebSocket.test.js | 43 +++++++++++++++++++++++++++++++++++------- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index c1f4eaae3..74f6e85d1 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -169,7 +169,7 @@ class Sender { } else if (ArrayBuffer.isView(data)) { data = viewToBuffer(data); } else { - data = Buffer.from(typeof data === 'number' ? data.toString() : data); + data = Buffer.from(data); readOnly = false; } } @@ -273,7 +273,7 @@ class Sender { } else if (ArrayBuffer.isView(data)) { data = viewToBuffer(data); } else { - data = Buffer.from(typeof data === 'number' ? data.toString() : data); + data = Buffer.from(data); options.readOnly = false; } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 4a773ac13..4baf5a8eb 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -304,6 +304,7 @@ class WebSocket extends EventEmitter { throw new Error('not opened'); } + if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; this._sender.ping(data, mask); } @@ -322,6 +323,7 @@ class WebSocket extends EventEmitter { throw new Error('not opened'); } + if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; this._sender.pong(data, mask); } @@ -350,16 +352,12 @@ class WebSocket extends EventEmitter { return; } - if (!data) data = ''; + if (typeof data === 'number') data = data.toString(); + else if (!data) data = ''; options = options || {}; if (options.fin !== false) options.fin = true; - - if (options.binary === undefined) { - options.binary = data instanceof Buffer || data instanceof ArrayBuffer || - ArrayBuffer.isView(data); - } - + if (options.binary === undefined) options.binary = typeof data !== 'string'; if (options.mask === undefined) options.mask = !this._isServer; if (options.compress === undefined) options.compress = true; if (!this.extensions[PerMessageDeflate.extensionName]) { diff --git a/test/Sender.test.js b/test/Sender.test.js index b0ce4598f..48c126963 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -60,12 +60,8 @@ describe('Sender', function () { let count = 0; const sender = new Sender({ write: (data) => { - if (++count < 4) { - assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); - } else { - assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x31, 0x30]))); - done(); - } + assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); + if (++count === 3) done(); } }); @@ -74,7 +70,6 @@ describe('Sender', function () { sender.ping(array.buffer, false); sender.ping(array, false); sender.ping('hi', false); - sender.ping(10, false); }); }); @@ -85,7 +80,7 @@ describe('Sender', function () { const sender = new Sender({ write: (data) => { assert.strictEqual(data[0] & 0x40, 0x40); - if (++count === 4) done(); + if (++count === 3) done(); } }, { 'permessage-deflate': perMessageDeflate @@ -99,7 +94,6 @@ describe('Sender', function () { sender.send(array.buffer, options); sender.send(array, options); sender.send('hi', options); - sender.send(100, options); }); it('does not compress data for small payloads', function (done) { diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 85d445c66..318feed30 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -517,17 +517,17 @@ describe('WebSocket', function () { }); }); - it('can send safely receive numbers as ping payload', function (done) { + it('can send numbers as ping payload', function (done) { server.createServer(++port, (srv) => { srv.on('ping', (message) => { - assert.strictEqual(message.toString(), '200'); + assert.strictEqual(message.toString(), '0'); srv.close(done); ws.terminate(); }); const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.ping(200)); + ws.on('open', () => ws.ping(0)); }); }); @@ -602,6 +602,21 @@ describe('WebSocket', function () { }); }); + it('can send numbers as pong payload', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong(0)); + }); + + wss.on('connection', (ws) => { + ws.on('pong', (message) => { + assert.strictEqual(message.toString(), '0'); + wss.close(done); + }); + }); + }); + it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { srv.on('pong', (message, flags) => { @@ -670,7 +685,22 @@ describe('WebSocket', function () { }); }); - it('send and receive binary data as an array', function (done) { + it('sends numbers as strings', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(0)); + }); + + wss.on('connection', (ws) => { + ws.on('message', (msg) => { + assert.strictEqual(msg, '0'); + wss.close(done); + }); + }); + }); + + it('can send binary data as an array', function (done) { server.createServer(++port, (srv) => { const array = new Float32Array(6); @@ -688,14 +718,13 @@ describe('WebSocket', function () { ws.on('message', (message, flags) => { assert.ok(flags.binary); assert.ok(message.equals(buf)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); - it('binary data can be sent and received as buffer', function (done) { + it('can send binary data as a buffer', function (done) { server.createServer(++port, (srv) => { const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${port}`); From 55843a9c0ad68658d120de2f2dbf38e478bca601 Mon Sep 17 00:00:00 2001 From: Danny Ho Date: Wed, 4 Jan 2017 03:36:29 -0800 Subject: [PATCH 199/489] [example] Update fileapi example to work with Express 4 (#953) --- examples/fileapi/package.json | 2 +- examples/fileapi/server.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/fileapi/package.json b/examples/fileapi/package.json index 7816f2737..1a374d4ac 100644 --- a/examples/fileapi/package.json +++ b/examples/fileapi/package.json @@ -10,7 +10,7 @@ "node": "~0.6.8" }, "dependencies": { - "express": "latest", + "express": "~4.0.0", "ansi": "https://github.com/einaros/ansi.js/tarball/master" }, "devDependencies": {}, diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index e7d80f009..380ce152d 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -3,7 +3,8 @@ var express = require('express'); var fs = require('fs'); var util = require('util'); var path = require('path'); -var app = express.createServer(); +var app = express(); +var server = require('http').Server(app); var events = require('events'); var ansi = require('ansi'); var cursor = ansi(process.stdout); @@ -45,7 +46,7 @@ cursor.eraseData(2).goto(1, 1); app.use(express.static(path.join(__dirname, '/public'))); var clientId = 0; -var wss = new WebSocketServer({server: app}); +var wss = new WebSocketServer({server: server}); wss.on('connection', function (ws) { var thisId = ++clientId; cursor.goto(1, 4 + thisId).eraseLine(); @@ -101,6 +102,7 @@ fs.mkdir(path.join(__dirname, '/uploaded'), function () { // ignore errors, most likely means directory exists console.log('Uploaded files will be saved to %s/uploaded.', __dirname); console.log('Remember to wipe this directory if you upload lots and lots.'); - app.listen(8080); - console.log('Listening on http://localhost:8080'); + server.listen(8080, function () { + console.log('Listening on http://localhost:8080'); + }); }); From 323db27415a3d35ee168756118e86fa10a985bcf Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 4 Jan 2017 14:59:51 +0100 Subject: [PATCH 200/489] [example] Update express to version 4.14.0 --- examples/fileapi/package.json | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/examples/fileapi/package.json b/examples/fileapi/package.json index 1a374d4ac..1341f59e9 100644 --- a/examples/fileapi/package.json +++ b/examples/fileapi/package.json @@ -2,17 +2,9 @@ "author": "", "name": "fileapi", "version": "0.0.0", - "repository": { - "type": "git", - "url": "git://github.com/einaros/ws.git" - }, - "engines": { - "node": "~0.6.8" - }, + "repository": "websockets/ws", "dependencies": { - "express": "~4.0.0", + "express": "~4.14.0", "ansi": "https://github.com/einaros/ansi.js/tarball/master" - }, - "devDependencies": {}, - "optionalDependencies": {} + } } From 8aa65e2f4da33083d1c20e46240aacc4fe40822c Mon Sep 17 00:00:00 2001 From: Danny Ho Date: Wed, 4 Jan 2017 22:31:41 -0800 Subject: [PATCH 201/489] [example] Update serverstats example to work with Express 4 (#954) --- examples/serverstats-express_3/package.json | 17 ---------- .../serverstats-express_3/public/index.html | 33 ------------------- examples/serverstats-express_3/server.js | 22 ------------- examples/serverstats/package.json | 14 ++------ examples/serverstats/server.js | 11 +++++-- 5 files changed, 11 insertions(+), 86 deletions(-) delete mode 100644 examples/serverstats-express_3/package.json delete mode 100644 examples/serverstats-express_3/public/index.html delete mode 100644 examples/serverstats-express_3/server.js diff --git a/examples/serverstats-express_3/package.json b/examples/serverstats-express_3/package.json deleted file mode 100644 index 99722c422..000000000 --- a/examples/serverstats-express_3/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "author": "", - "name": "serverstats", - "version": "0.0.0", - "repository": { - "type": "git", - "url": "git://github.com/einaros/ws.git" - }, - "engines": { - "node": ">0.4.0" - }, - "dependencies": { - "express": "~3.0.0" - }, - "devDependencies": {}, - "optionalDependencies": {} -} diff --git a/examples/serverstats-express_3/public/index.html b/examples/serverstats-express_3/public/index.html deleted file mode 100644 index 24d84e120..000000000 --- a/examples/serverstats-express_3/public/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - Server Stats
- RSS:

- Heap total:

- Heap used:

- - diff --git a/examples/serverstats-express_3/server.js b/examples/serverstats-express_3/server.js deleted file mode 100644 index 16e584f95..000000000 --- a/examples/serverstats-express_3/server.js +++ /dev/null @@ -1,22 +0,0 @@ -var WebSocketServer = require('../../').Server; -var http = require('http'); -var express = require('express'); -var path = require('path'); -var app = express(); - -app.use(express.static(path.join(__dirname, '/public'))); - -var server = http.createServer(app); -server.listen(8080); - -var wss = new WebSocketServer({server: server}); -wss.on('connection', function (ws) { - var id = setInterval(function () { - ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); - }, 100); - console.log('started client interval'); - ws.on('close', function () { - console.log('stopping client interval'); - clearInterval(id); - }); -}); diff --git a/examples/serverstats/package.json b/examples/serverstats/package.json index 65c900ab1..321049ab5 100644 --- a/examples/serverstats/package.json +++ b/examples/serverstats/package.json @@ -2,16 +2,8 @@ "author": "", "name": "serverstats", "version": "0.0.0", - "repository": { - "type": "git", - "url": "git://github.com/einaros/ws.git" - }, - "engines": { - "node": ">0.4.0" - }, + "repository": "websockets/ws", "dependencies": { - "express": "2.x" - }, - "devDependencies": {}, - "optionalDependencies": {} + "express": "~4.14.0" + } } diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index 73e4fcf8f..b1e167806 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -1,12 +1,12 @@ var WebSocketServer = require('../../').Server; var express = require('express'); var path = require('path'); -var app = express.createServer(); +var app = express(); +var server = require('http').createServer(); app.use(express.static(path.join(__dirname, '/public'))); -app.listen(8080); -var wss = new WebSocketServer({server: app}); +var wss = new WebSocketServer({server: server}); wss.on('connection', function (ws) { var id = setInterval(function () { ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); @@ -17,3 +17,8 @@ wss.on('connection', function (ws) { clearInterval(id); }); }); + +server.on('request', app); +server.listen(8080, function () { + console.log('Listening on http://localhost:8080'); +}); From 758e6a06f0ef8ed092b699fcfc1d0f52067ed425 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 6 Jan 2017 22:12:54 +0100 Subject: [PATCH 202/489] chore(package): update eslint to version 3.13.0 (#957) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c1033663..e8a12c227 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.3.0", - "eslint": "~3.12.0", + "eslint": "~3.13.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From c843e6ac9db14bec7c6271d6faa246ac9015d5b9 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 9 Jan 2017 17:53:37 +0800 Subject: [PATCH 203/489] [doc] Update coding style for README.md (#960) --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 53a1a2526..2076c3b63 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ compiler is installed on the host system. ### Sending and receiving text data ```js -var WebSocket = require('ws'); -var ws = new WebSocket('ws://www.host.com/path'); +const WebSocket = require('ws'); +const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { ws.send('something'); @@ -58,8 +58,8 @@ ws.on('message', function(data, flags) { ### Sending binary data ```js -var WebSocket = require('ws'); -var ws = new WebSocket('ws://www.host.com/path'); +const WebSocket = require('ws'); +const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { var array = new Float32Array(5); @@ -79,8 +79,8 @@ data. ### Server example ```js -var WebSocketServer = require('ws').Server - , wss = new WebSocketServer({ port: 8080 }); +const WebSocketServer = require('ws').Server; +const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', function connection(ws) { ws.on('message', function incoming(message) { @@ -94,13 +94,13 @@ wss.on('connection', function connection(ws) { ### ExpressJS example ```js -var server = require('http').createServer() - , url = require('url') - , WebSocketServer = require('ws').Server - , wss = new WebSocketServer({ server: server }) - , express = require('express') - , app = express() - , port = 4080; +const server = require('http').createServer(); +const url = require('url'); +const WebSocketServer = require('ws').Server; +const wss = new WebSocketServer({ server: server }); +const express = require('express'); +const app = express(); +const port = 4080; app.use(function (req, res) { res.send({ msg: "hello" }); @@ -125,8 +125,8 @@ server.listen(port, function () { console.log('Listening on ' + server.address() ### Server sending broadcast data ```js -var WebSocketServer = require('ws').Server - , wss = new WebSocketServer({ port: 8080 }); +const WebSocketServer = require('ws').Server; +const wss = new WebSocketServer({ port: 8080 }); // Broadcast to all. wss.broadcast = function broadcast(data) { @@ -169,8 +169,8 @@ catch (e) { /* handle error */ } ### echo.websocket.org demo ```js -var WebSocket = require('ws'); -var ws = new WebSocket('ws://echo.websocket.org/', { +const WebSocket = require('ws'); +const ws = new WebSocket('ws://echo.websocket.org/', { protocolVersion: 8, origin: 'http://websocket.org' }); From 20585ee8de2740e14c85b4e27db33ad038eabe14 Mon Sep 17 00:00:00 2001 From: Jacob Bogers Date: Mon, 9 Jan 2017 11:39:10 +0100 Subject: [PATCH 204/489] [fix] Prevent `on` setters from removing all listeners (#955) --- lib/WebSocket.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 4baf5a8eb..1abbb7407 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -409,8 +409,10 @@ WebSocket.CLOSED = 3; * @public */ get () { - const listener = this.listeners(method)[0]; - return listener ? listener._listener ? listener._listener : listener : undefined; + const listeners = this.listeners(method); + for (var i = 0; i < listeners.length; i++) { + if (listeners[i]._listener) return listeners[i]._listener; + } }, /** * Add a listener for the event. @@ -419,7 +421,13 @@ WebSocket.CLOSED = 3; * @public */ set (listener) { - this.removeAllListeners(method); + const listeners = this.listeners(method); + for (var i = 0; i < listeners.length; i++) { + // + // Remove only the listeners added via `addEventListener`. + // + if (listeners[i]._listener) this.removeListener(method, listeners[i]); + } this.addEventListener(method, listener); } }); From f0cc971bcf5067c43c010835e599c043b4b159c6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 9 Jan 2017 12:57:37 +0100 Subject: [PATCH 205/489] [test] Add test for 20585ee --- test/WebSocketServer.test.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index cceb53f94..78c0d7768 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -238,7 +238,7 @@ describe('WebSocketServer', function () { }); }); - it('is updated when client closes the connection', function (done) { + it('is updated when client closes the connection (1/2)', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -253,6 +253,21 @@ describe('WebSocketServer', function () { }); }); }); + + it('is updated when client closes the connection (2/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.onopen = () => ws.close(); + }); + + wss.on('connection', (ws) => { + ws.onclose = () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(done); + }; + }); + }); }); describe('#options', function () { From 07974eefb21bd375e8ecd1cd14fb008c36f7a1d6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 9 Jan 2017 13:49:19 +0000 Subject: [PATCH 206/489] fix(package): update ultron to version 1.1.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8a12c227..57f3e7baa 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "lint": "eslint ." }, "dependencies": { - "ultron": "~1.0.2" + "ultron": "~1.1.0" }, "devDependencies": { "benchmark": "~2.1.2", From 443d0baa814f6dae4e6f1fb7c5aec40676f04d4b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 9 Jan 2017 14:41:47 +0100 Subject: [PATCH 207/489] [test] Increase code coverage --- test/WebSocket.test.js | 109 ++++++++++++++++++++++++----------- test/WebSocketServer.test.js | 17 +----- 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 318feed30..242fb294a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -420,6 +420,18 @@ describe('WebSocket', function () { }); describe('#pause and #resume', function () { + it('throws an error when `readyState` is not `OPEN` (pause)', function () { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + assert.throws(() => ws.pause(), /^Error: not opened$/); + }); + + it('throws an error when `readyState` is not `OPEN` (resume)', function () { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + assert.throws(() => ws.resume(), /^Error: not opened$/); + }); + it('pauses the underlying stream', function (done) { // this test is sort-of racecondition'y, since an unlikely slow connection // to localhost can cause the test to succeed even when the stream pausing @@ -1067,35 +1079,30 @@ describe('WebSocket', function () { }); describe('WHATWG API emulation', function () { - it('should not throw errors when getting and setting', function (done) { - server.createServer(++port, (srv) => { - const listener = () => {}; - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.onmessage, undefined); - assert.strictEqual(ws.onclose, undefined); - assert.strictEqual(ws.onerror, undefined); - assert.strictEqual(ws.onopen, undefined); + it('should not throw errors when getting and setting', function () { + const listener = () => {}; + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.onmessage = listener; - ws.onerror = listener; - ws.onclose = listener; - ws.onopen = listener; + assert.strictEqual(ws.onmessage, undefined); + assert.strictEqual(ws.onclose, undefined); + assert.strictEqual(ws.onerror, undefined); + assert.strictEqual(ws.onopen, undefined); - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'arraybuffer'; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); + ws.onmessage = listener; + ws.onerror = listener; + ws.onclose = listener; + ws.onopen = listener; - assert.strictEqual(ws.onmessage, listener); - assert.strictEqual(ws.onclose, listener); - assert.strictEqual(ws.onerror, listener); - assert.strictEqual(ws.onopen, listener); + assert.strictEqual(ws.binaryType, 'nodebuffer'); + ws.binaryType = 'arraybuffer'; + assert.strictEqual(ws.binaryType, 'arraybuffer'); + ws.binaryType = 'nodebuffer'; + assert.strictEqual(ws.binaryType, 'nodebuffer'); - srv.close(done); - ws.terminate(); - }); + assert.strictEqual(ws.onmessage, listener); + assert.strictEqual(ws.onclose, listener); + assert.strictEqual(ws.onerror, listener); + assert.strictEqual(ws.onopen, listener); }); it('should throw an error when setting an invalid binary type', function () { @@ -1134,17 +1141,36 @@ describe('WebSocket', function () { }); }); - it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); + it('doesn\'t return event listeners added with `on`', function () { + const listener = () => {}; + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.addEventListener('open', () => ws.send('hi')); - ws.addEventListener('message', (messageEvent) => { - assert.strictEqual(messageEvent.data, 'hi'); - srv.close(done); - ws.terminate(); - }); - }); + ws.on('open', listener); + + assert.deepStrictEqual(ws.listeners('open'), [listener]); + assert.strictEqual(ws.onopen, undefined); + }); + + it('doesn\'t remove event listeners added with `on`', function () { + const listener = () => {}; + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + ws.on('close', listener); + ws.onclose = listener; + + let listeners = ws.listeners('close'); + + assert.strictEqual(listeners.length, 2); + assert.strictEqual(listeners[0], listener); + assert.strictEqual(listeners[1]._listener, listener); + + ws.onclose = listener; + + listeners = ws.listeners('close'); + + assert.strictEqual(listeners.length, 2); + assert.strictEqual(listeners[0], listener); + assert.strictEqual(listeners[1]._listener, listener); }); it('registers listeners for custom events with addEventListener', function () { @@ -1186,6 +1212,19 @@ describe('WebSocket', function () { assert.strictEqual(ws.listeners('foo').length, 0); }); + it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('open', () => ws.send('hi')); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.data, 'hi'); + srv.close(done); + ws.terminate(); + }); + }); + }); + it('should receive valid CloseEvent when server closes with code 1000', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 78c0d7768..cceb53f94 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -238,7 +238,7 @@ describe('WebSocketServer', function () { }); }); - it('is updated when client closes the connection (1/2)', function (done) { + it('is updated when client closes the connection', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -253,21 +253,6 @@ describe('WebSocketServer', function () { }); }); }); - - it('is updated when client closes the connection (2/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.onopen = () => ws.close(); - }); - - wss.on('connection', (ws) => { - ws.onclose = () => { - assert.strictEqual(wss.clients.size, 0); - wss.close(done); - }; - }); - }); }); describe('#options', function () { From 772a814fbe999b41dae0bf8ae6b09a5c079c4a40 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 10 Jan 2017 14:43:50 +0100 Subject: [PATCH 208/489] [fix] Abort the request if `close` is called while connecting (#956) Fixes #388 --- lib/WebSocket.js | 66 ++++++++----------- test/WebSocket.test.js | 122 ++++++++++++++++++++++------------- test/WebSocketServer.test.js | 38 ++++++----- 3 files changed, 122 insertions(+), 104 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 1abbb7407..0da5c0f78 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -262,10 +262,10 @@ class WebSocket extends EventEmitter { */ close (code, data) { if (this.readyState === WebSocket.CLOSED) return; - if (this.readyState === WebSocket.CONNECTING) { - this.readyState = WebSocket.CLOSED; - return; + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + return this.finalize(true); } if (this.readyState === WebSocket.CLOSING) { @@ -374,6 +374,11 @@ class WebSocket extends EventEmitter { */ terminate () { if (this.readyState === WebSocket.CLOSED) return; + if (this.readyState === WebSocket.CONNECTING) { + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + return this.finalize(true); + } if (this._socket) { this.readyState = WebSocket.CLOSING; @@ -385,8 +390,6 @@ class WebSocket extends EventEmitter { // clearTimeout(this._closeTimer); this._closeTimer = setTimeout(this._finalize, closeTimeout, true); - } else if (this.readyState === WebSocket.CONNECTING) { - this.finalize(true); } } } @@ -618,43 +621,32 @@ function initAsClient (address, protocols, options) { if (agent) requestOptions.agent = agent; - const req = httpObj.get(requestOptions); + this._req = httpObj.get(requestOptions); + + this._req.on('error', (error) => { + if (this._req.aborted) return; - req.on('error', (error) => { this.emit('error', error); - this.finalize(error); + this.finalize(true); }); - req.on('response', (res) => { - var error; - - if (!this.emit('unexpected-response', req, res)) { - error = new Error(`unexpected server response (${res.statusCode})`); - req.abort(); - this.emit('error', error); + this._req.on('response', (res) => { + if (!this.emit('unexpected-response', this._req, res)) { + this._req.abort(); + this.emit('error', new Error(`unexpected server response (${res.statusCode})`)); + this.finalize(true); } - - this.finalize(error); }); - req.on('upgrade', (res, socket, head) => { - if (this.readyState === WebSocket.CLOSED) { - // client closed before server accepted connection - this.emit('close'); - this.removeAllListeners(); - socket.end(); - return; - } - + this._req.on('upgrade', (res, socket, head) => { const digest = crypto.createHash('sha1') .update(key + GUID, 'binary') .digest('base64'); if (res.headers['sec-websocket-accept'] !== digest) { + socket.destroy(); this.emit('error', new Error('invalid server key')); - this.removeAllListeners(); - socket.end(); - return; + return this.finalize(true); } const serverProt = res.headers['sec-websocket-protocol']; @@ -670,30 +662,28 @@ function initAsClient (address, protocols, options) { } if (protError) { + socket.destroy(); this.emit('error', new Error(protError)); - this.removeAllListeners(); - socket.end(); - return; + return this.finalize(true); } if (serverProt) this.protocol = serverProt; const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); + if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); } catch (err) { + socket.destroy(); this.emit('error', new Error('invalid extension parameter')); - this.removeAllListeners(); - socket.end(); - return; + return this.finalize(true); } + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } + this._req = null; this.setSocket(socket, head); - - req.removeAllListeners(); - agent = null; }); } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 242fb294a..debaf5d0b 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -95,39 +95,28 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send('foobar')); }); - it('#url exposes the server url', function (done) { - server.createServer(++port, (srv) => { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - - assert.strictEqual(ws.url, url); + it('#url exposes the server url', function () { + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url, { agent: new CustomAgent() }); - ws.on('close', () => srv.close(done)); - ws.close(); - }); + assert.strictEqual(ws.url, url); }); - it('#protocolVersion exposes the protocol version', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.protocolVersion, 13); - - ws.on('close', () => srv.close(done)); - ws.close(); + it('#protocolVersion exposes the protocol version', function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + agent: new CustomAgent() }); + + assert.strictEqual(ws.protocolVersion, 13); }); describe('#bufferedAmount', function () { - it('defaults to zero', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.bufferedAmount, 0); - - ws.on('close', () => srv.close(done)); - ws.close(); + it('defaults to zero', function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + agent: new CustomAgent() }); + + assert.strictEqual(ws.bufferedAmount, 0); }); it('defaults to zero upon "open"', function (done) { @@ -203,15 +192,12 @@ describe('WebSocket', function () { }); describe('#readyState', function () { - it('defaults to connecting', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.readyState, WebSocket.CONNECTING); - - ws.on('close', () => srv.close(done)); - ws.close(); + it('defaults to connecting', function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + agent: new CustomAgent() }); + + assert.strictEqual(ws.readyState, WebSocket.CONNECTING); }); it('set to open once connection is established', function (done) { @@ -236,7 +222,7 @@ describe('WebSocket', function () { srv.close(done); }); - ws.close(1001); + ws.on('open', () => ws.close(1001)); }); }); @@ -249,7 +235,7 @@ describe('WebSocket', function () { srv.close(done); }); - ws.terminate(); + ws.on('open', () => ws.terminate()); }); }); }); @@ -308,26 +294,68 @@ describe('WebSocket', function () { }); describe('connection establishing', function () { - it('can disconnect before connection is established', function (done) { - server.createServer(++port, (srv) => { + it('can terminate before connection is established (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('close', () => srv.close(done)); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); ws.terminate(); }); }); - it('can close before connection is established', function (done) { - server.createServer(++port, (srv) => { + it('can terminate before connection is established (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('close', () => srv.close(done)); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.terminate(), 150); + }); + }); + + it('can close before connection is established (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); ws.close(1001); }); }); + it('can close before connection is established (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.close(1001), 150); + }); + }); + it('can handle error before request is upgraded', function (done) { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded @@ -803,15 +831,17 @@ describe('WebSocket', function () { }); it('before connect should pass error through callback, if present', function (done) { - server.createServer(++port, (srv) => { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.send('hi', (error) => { - assert.ok(error instanceof Error); - srv.close(done); - ws.terminate(); + ws.send('hi', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'not opened'); + ws.on('close', () => wss.close(done)); }); }); + + wss.on('connection', (ws) => ws.close()); }); it('without data should be successful', function (done) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index cceb53f94..3579dcf8f 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -202,10 +202,9 @@ describe('WebSocketServer', function () { const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', (client) => { + wss.on('connection', (ws) => { assert.strictEqual(wss.clients.size, 1); - wss.close(); - done(); + wss.close(done); }); }); @@ -213,12 +212,13 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port, clientTracking: false }, () => { assert.strictEqual(wss.clients, undefined); const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.close()); }); - wss.on('connection', (client) => { + wss.on('connection', (ws) => { assert.strictEqual(wss.clients, undefined); - wss.close(); - done(); + ws.on('close', () => wss.close(done)); }); }); @@ -226,14 +226,13 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - wss.on('connection', (client) => { - client.on('close', () => { - assert.strictEqual(wss.clients.size, 0); - wss.close(); - done(); - }); + ws.on('open', () => ws.terminate()); + }); - ws.close(); + wss.on('connection', (ws) => { + ws.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(done); }); }); }); @@ -242,14 +241,13 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - wss.on('connection', (client) => { - client.on('close', () => { - assert.strictEqual(wss.clients.size, 0); - wss.close(); - done(); - }); + ws.on('open', () => ws.close()); + }); - ws.close(); + wss.on('connection', (ws) => { + ws.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(done); }); }); }); From 19ce183fad0e826a025bf9709eef48e279a1cb75 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 10 Jan 2017 15:05:23 +0100 Subject: [PATCH 209/489] [dist] 2.0.0-beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57f3e7baa..6ccf4657e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "1.1.0", + "version": "2.0.0-beta.0", "license": "MIT", "main": "index.js", "keywords": [ From fd910f1460d12bc669afde6ca80a4ba9969e8b99 Mon Sep 17 00:00:00 2001 From: Hanson Wang Date: Wed, 11 Jan 2017 01:41:34 -0800 Subject: [PATCH 210/489] [feature] Accept hostname lookup `family` option (#962) --- doc/ws.md | 1 + lib/WebSocket.js | 5 ++++- test/WebSocket.test.js | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 7839ff433..39d1b9126 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -174,6 +174,7 @@ This class represents a WebSocket. It extends the `EventEmitter`. depending on the `protocolVersion`. - `agent` {http.Agent|https.Agent} Use the specified Agent, - `host` {String} Value of the `Host` header. + - `family` {Number} IP address family to use during hostname lookup (4 or 6). - `checkServerIdentity` {Function} A function to validate the server hostname. - `rejectUnauthorized` {Boolean} Verify or not the server certificate. - `passphrase` {String} The passphrase for the private key or pfx. diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 0da5c0f78..741a05066 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -472,7 +472,7 @@ function initAsServerClient (req, socket, head, options) { * @param {String} address The URL to which to connect * @param {String[]} protocols The list of subprotocols * @param {Object} options Connection options - * @param {String} option.protocol Value of the `Sec-WebSocket-Protocol` header + * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate * @param {String} options.localAddress Local interface to bind for network connections * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header @@ -480,6 +480,7 @@ function initAsServerClient (req, socket, head, options) { * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header * @param {http.Agent} options.agent Use the specified Agent * @param {String} options.host Value of the `Host` header + * @param {Number} options.family IP address family to use during hostname lookup (4 or 6). * @param {Function} options.checkServerIdentity A function to validate the server hostname * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate * @param {String} options.passphrase The passphrase for the private key or pfx @@ -500,6 +501,7 @@ function initAsClient (address, protocols, options) { origin: null, agent: null, host: null, + family: null, // // SSL options. @@ -572,6 +574,7 @@ function initAsClient (address, protocols, options) { } } if (options.host) requestOptions.headers.Host = options.host; + if (options.family) requestOptions.family = options.family; if (options.localAddress) requestOptions.localAddress = options.localAddress; if (isUnixSocket) requestOptions.socketPath = serverUrl.pathname; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index debaf5d0b..cf44a69fb 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -80,6 +80,17 @@ describe('WebSocket', function () { /must be a valid IP: 123.456.789.428/ ); }); + + it('should accept the family option', function (done) { + const wss = new WebSocketServer({ host: '::1', port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + }); + + wss.on('connection', (ws) => { + assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '::1'); + wss.close(done); + }); + }); }); describe('properties', function () { From 7bec220f9e3650afc5d92c17fb04a949dce0e7fd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 11 Jan 2017 16:20:42 +0100 Subject: [PATCH 211/489] [doc] Modernize examples --- README.md | 82 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 2076c3b63..15f4ab00b 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,14 @@ compiler is installed on the host system. ```js const WebSocket = require('ws'); + const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { ws.send('something'); }); -ws.on('message', function(data, flags) { +ws.on('message', function incoming(data, flags) { // flags.binary will be set if a binary data is received. // flags.masked will be set if the data was masked. }); @@ -59,28 +60,26 @@ ws.on('message', function(data, flags) { ```js const WebSocket = require('ws'); + const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { - var array = new Float32Array(5); + const array = new Float32Array(5); for (var i = 0; i < array.length; ++i) { array[i] = i / 2; } - ws.send(array, { binary: true, mask: true }); + ws.send(array); }); ``` -Setting `mask`, as done for the send options above, will cause the data to be -masked according to the WebSocket protocol. The same option applies for text -data. - ### Server example ```js -const WebSocketServer = require('ws').Server; -const wss = new WebSocketServer({ port: 8080 }); +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', function connection(ws) { ws.on('message', function incoming(message) { @@ -94,21 +93,23 @@ wss.on('connection', function connection(ws) { ### ExpressJS example ```js -const server = require('http').createServer(); -const url = require('url'); -const WebSocketServer = require('ws').Server; -const wss = new WebSocketServer({ server: server }); const express = require('express'); +const http = require('http'); +const url = require('url'); +const WebSocket = require('ws'); + const app = express(); -const port = 4080; app.use(function (req, res) { res.send({ msg: "hello" }); }); +const server = http.createServer(app); +const wss = new WebSocket.Server({ server }); + wss.on('connection', function connection(ws) { - var location = url.parse(ws.upgradeReq.url, true); - // you might use location.query.access_token to authenticate or share sessions + const location = url.parse(ws.upgradeReq.url, true); + // You might use location.query.access_token to authenticate or share sessions // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) ws.on('message', function incoming(message) { @@ -118,28 +119,34 @@ wss.on('connection', function connection(ws) { ws.send('something'); }); -server.on('request', app); -server.listen(port, function () { console.log('Listening on ' + server.address().port) }); +server.listen(8080, function listening() { + console.log('Listening on %d', server.address().port); +}); ``` ### Server sending broadcast data ```js -const WebSocketServer = require('ws').Server; -const wss = new WebSocketServer({ port: 8080 }); +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); // Broadcast to all. wss.broadcast = function broadcast(data) { wss.clients.forEach(function each(client) { - client.send(data); + if (client.readyState === WebSocket.OPEN) { + client.send(data); + } }); }; wss.on('connection', function connection(ws) { - ws.on('message', function message(data) { + ws.on('message', function incoming(data) { // Broadcast to everyone else. wss.clients.forEach(function each(client) { - if (client !== ws) client.send(data); + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(data); + } }); }); }); @@ -155,11 +162,11 @@ ws.send('something'); // callback. The callback is also the only way of being notified that data has // actually been sent. ws.send('something', function ack(error) { - // if error is not defined, the send has been completed, - // otherwise the error object will indicate what failed. + // If error is not defined, the send has been completed, otherwise the error + // object will indicate what failed. }); -// Immediate errors can also be handled with try/catch-blocks, but **note** that +// Immediate errors can also be handled with `try...catch`, but **note** that // since sends are inherently asynchronous, socket write failures will *not* be // captured when this technique is used. try { ws.send('something'); } @@ -170,25 +177,25 @@ catch (e) { /* handle error */ } ```js const WebSocket = require('ws'); -const ws = new WebSocket('ws://echo.websocket.org/', { - protocolVersion: 8, - origin: 'http://websocket.org' + +const ws = new WebSocket('wss://echo.websocket.org/', { + origin: 'https://websocket.org' }); ws.on('open', function open() { console.log('connected'); - ws.send(Date.now().toString(), {mask: true}); + ws.send(Date.now()); }); ws.on('close', function close() { console.log('disconnected'); }); -ws.on('message', function message(data, flags) { - console.log('Roundtrip time: ' + (Date.now() - parseInt(data)) + 'ms', flags); +ws.on('message', function incoming(data, flags) { + console.log(`Roundtrip time: ${Date.now() - data} ms`, flags); setTimeout(function timeout() { - ws.send(Date.now().toString(), {mask: true}); + ws.send(Date.now()); }, 500); }); ``` @@ -198,18 +205,17 @@ ws.on('message', function message(data, flags) { For a full example with a browser client communicating with a ws server, see the examples folder. -Note that the usage together with Express 3.0 is quite different from Express -2.x. The difference is expressed in the two different serverstats-examples. - Otherwise, see the test cases. ## API Docs -See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) for Node.js-like docs for the ws classes. +See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) +for Node.js-like docs for the ws classes. ## Changelog -We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) for changelog entries. +We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) +for changelog entries. ## License From 4d9209246dc9b748b4226d4158fbcc0dff523a2e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 12 Jan 2017 12:50:09 +0100 Subject: [PATCH 212/489] [doc] Add WebSocket compression section --- README.md | 144 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 15f4ab00b..2573f1f79 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ws: a node.js websocket library +# ws: a node.js WebSocket library [![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) [![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) @@ -16,7 +16,7 @@ for the full reports. * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) * **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) -### Installing +## Installing ``` npm install --save ws @@ -39,6 +39,46 @@ compiler is installed on the host system. validation. But if you want to be 100% spec-conforming and have fast validation of UTF-8 then this module is a must. +## API Docs + +See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) +for Node.js-like docs for the ws classes. + +## WebSocket compression + +`ws` supports the [permessage-deflate extension][permessage-deflate] extension +which enables the client and server to negotiate a compression algorithm and +its parameters, and then selectively apply it to the data payloads of each +WebSocket message. + +The extension is enabled by default but adds a significant overhead in terms of +performance and memory comsumption. We suggest to use WebSocket compression +only if it is really needed. + +To disable the extension you can set the `perMessageDeflate` option to `false`. +On the server: + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ + perMessageDeflate: false, + port: 8080 +}); +``` + +On the client: + +```js +const WebSocket = require('ws'); + +const ws = new WebSocket('ws://www.host.com/path', { + perMessageDeflate: false +}); +``` + +## Usage examples + ### Sending and receiving text data ```js @@ -90,41 +130,7 @@ wss.on('connection', function connection(ws) { }); ``` -### ExpressJS example - -```js -const express = require('express'); -const http = require('http'); -const url = require('url'); -const WebSocket = require('ws'); - -const app = express(); - -app.use(function (req, res) { - res.send({ msg: "hello" }); -}); - -const server = http.createServer(app); -const wss = new WebSocket.Server({ server }); - -wss.on('connection', function connection(ws) { - const location = url.parse(ws.upgradeReq.url, true); - // You might use location.query.access_token to authenticate or share sessions - // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) - - ws.on('message', function incoming(message) { - console.log('received: %s', message); - }); - - ws.send('something'); -}); - -server.listen(8080, function listening() { - console.log('Listening on %d', server.address().port); -}); -``` - -### Server sending broadcast data +### Broadcast example ```js const WebSocket = require('ws'); @@ -152,25 +158,38 @@ wss.on('connection', function connection(ws) { }); ``` -### Error handling best practices +### ExpressJS example ```js -// If the WebSocket is closed before the following send is attempted -ws.send('something'); +const express = require('express'); +const http = require('http'); +const url = require('url'); +const WebSocket = require('ws'); -// Errors (both immediate and async write errors) can be detected in an optional -// callback. The callback is also the only way of being notified that data has -// actually been sent. -ws.send('something', function ack(error) { - // If error is not defined, the send has been completed, otherwise the error - // object will indicate what failed. +const app = express(); + +app.use(function (req, res) { + res.send({ msg: "hello" }); }); -// Immediate errors can also be handled with `try...catch`, but **note** that -// since sends are inherently asynchronous, socket write failures will *not* be -// captured when this technique is used. -try { ws.send('something'); } -catch (e) { /* handle error */ } +const server = http.createServer(app); +const wss = new WebSocket.Server({ server }); + +wss.on('connection', function connection(ws) { + const location = url.parse(ws.upgradeReq.url, true); + // You might use location.query.access_token to authenticate or share sessions + // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) + + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); + +server.listen(8080, function listening() { + console.log('Listening on %d', server.address().port); +}); ``` ### echo.websocket.org demo @@ -207,10 +226,26 @@ examples folder. Otherwise, see the test cases. -## API Docs +## Error handling best practices -See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) -for Node.js-like docs for the ws classes. +```js +// If the WebSocket is closed before the following send is attempted +ws.send('something'); + +// Errors (both immediate and async write errors) can be detected in an optional +// callback. The callback is also the only way of being notified that data has +// actually been sent. +ws.send('something', function ack(error) { + // If error is not defined, the send has been completed, otherwise the error + // object will indicate what failed. +}); + +// Immediate errors can also be handled with `try...catch`, but **note** that +// since sends are inherently asynchronous, socket write failures will *not* be +// captured when this technique is used. +try { ws.send('something'); } +catch (e) { /* handle error */ } +``` ## Changelog @@ -222,3 +257,4 @@ for changelog entries. [MIT](LICENSE) [archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs +[permessage-deflate]: https://tools.ietf.org/html/rfc7692 From bd41a05db9aae496615c5cb3a7fbeb3ef9e47e0b Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 14 Jan 2017 15:05:12 +0100 Subject: [PATCH 213/489] [fix] Use `Object.assign()` for `send()` options (#968) This avoids mutating the passed in arguments. --- lib/WebSocket.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 741a05066..c93ed05ba 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -355,16 +355,18 @@ class WebSocket extends EventEmitter { if (typeof data === 'number') data = data.toString(); else if (!data) data = ''; - options = options || {}; - if (options.fin !== false) options.fin = true; - if (options.binary === undefined) options.binary = typeof data !== 'string'; - if (options.mask === undefined) options.mask = !this._isServer; - if (options.compress === undefined) options.compress = true; + const opts = Object.assign({ + fin: true, + binary: typeof data !== 'string', + mask: !this._isServer, + compress: true + }, options); + if (!this.extensions[PerMessageDeflate.extensionName]) { - options.compress = false; + opts.compress = false; } - this._sender.send(data, options, cb); + this._sender.send(data, opts, cb); } /** From ac2dade21a553a0d2c3f529f935f9fbfd5487c78 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 14 Jan 2017 15:06:01 +0100 Subject: [PATCH 214/489] [fix] Prevent WebSocket#close() from triggering an infinite loop (#969) This prevents `WebSocket.prototype.close()` from triggering an infinite loop if called from an error listener while connecting. --- lib/WebSocket.js | 22 +++-- test/WebSocket.test.js | 182 +++++++++++++++++++++-------------------- 2 files changed, 107 insertions(+), 97 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index c93ed05ba..ea3513559 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -263,9 +263,12 @@ class WebSocket extends EventEmitter { close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - return this.finalize(true); + if (this._req && !this._req.aborted) { + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + this.finalize(true); + } + return; } if (this.readyState === WebSocket.CLOSING) { @@ -377,9 +380,12 @@ class WebSocket extends EventEmitter { terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - return this.finalize(true); + if (this._req && !this._req.aborted) { + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + this.finalize(true); + } + return; } if (this._socket) { @@ -631,6 +637,7 @@ function initAsClient (address, protocols, options) { this._req.on('error', (error) => { if (this._req.aborted) return; + this._req = null; this.emit('error', error); this.finalize(true); }); @@ -644,6 +651,8 @@ function initAsClient (address, protocols, options) { }); this._req.on('upgrade', (res, socket, head) => { + this._req = null; + const digest = crypto.createHash('sha1') .update(key + GUID, 'binary') .digest('base64'); @@ -688,7 +697,6 @@ function initAsClient (address, protocols, options) { this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - this._req = null; this.setSocket(socket, head); }); } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index cf44a69fb..7ac77dd96 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -305,77 +305,6 @@ describe('WebSocket', function () { }); describe('connection establishing', function () { - it('can terminate before connection is established (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - ws.terminate(); - }); - }); - - it('can terminate before connection is established (2/2)', function (done) { - const wss = new WebSocketServer({ - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.terminate(), 150); - }); - }); - - it('can close before connection is established (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - ws.close(1001); - }); - }); - - it('can close before connection is established (2/2)', function (done) { - const wss = new WebSocketServer({ - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.close(1001), 150); - }); - }); - - it('can handle error before request is upgraded', function (done) { - // Here, we don't create a server, to guarantee that the connection will - // fail before the request is upgraded - const ws = new WebSocket(`ws://localhost:${++port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', () => done()); - }); - it('invalid server key is denied', function (done) { server.createServer(++port, server.handlers.invalidKey, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -956,7 +885,50 @@ describe('WebSocket', function () { }); describe('#close', function () { - it('without invalid first argument throws exception', function (done) { + it('closes the connection if called while connecting (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + ws.close(1001); + }); + }); + + it('closes the connection if called while connecting (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.close(1001), 150); + }); + }); + + it('can be called from an error listener while connecting', function (done) { + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ECONNREFUSED'); + ws.close(); + ws.on('close', () => done()); + }); + }); + + it('throws an error if the first argument is invalid (1/2)', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -971,7 +943,7 @@ describe('WebSocket', function () { }); }); - it('without reserved error code 1004 throws exception', function (done) { + it('throws an error if the first argument is invalid (2/2)', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -986,7 +958,7 @@ describe('WebSocket', function () { }); }); - it('without message is successfully transmitted to the server', function (done) { + it('works when close reason is not specified', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -1000,22 +972,7 @@ describe('WebSocket', function () { }); }); - it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => ws.close(1000, 'some reason')); - - srv.on('close', (code, message, flags) => { - assert.ok(flags.masked); - assert.strictEqual(message, 'some reason'); - srv.close(done); - ws.terminate(); - }); - }); - }); - - it('with encoded message is successfully transmitted to the server', function (done) { + it('works when close reason is specified', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -1119,6 +1076,51 @@ describe('WebSocket', function () { }); }); + describe('#terminate', function () { + it('closes the connection if called while connecting (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + ws.terminate(); + }); + }); + + it('closes the connection if called while connecting (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.terminate(), 150); + }); + }); + + it('can be called from an error listener while connecting', function (done) { + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ECONNREFUSED'); + ws.terminate(); + ws.on('close', () => done()); + }); + }); + }); + describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function () { const listener = () => {}; From e62b9ba0c89aaf7b3a2d17084c23f4f983acf339 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 14 Jan 2017 17:20:23 +0100 Subject: [PATCH 215/489] [dist] 2.0.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ccf4657e..628cd215d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0-beta.0", + "version": "2.0.0-beta.1", "license": "MIT", "main": "index.js", "keywords": [ From 42f364e91b85644306d8864f9b26cd95a308fdaa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 12 Jan 2017 12:04:14 +0100 Subject: [PATCH 216/489] [doc] Remove outdated link and rephrase sentence accordingly --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2573f1f79..b30735d5c 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) -`ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, -and [probably the fastest WebSocket library for node.js][archive]. +`ws` is a simple to use, blazing fast, and thoroughly tested WebSocket client +and server implementation. Passes the quite extensive Autobahn test suite. See http://websockets.github.com/ws for the full reports. @@ -256,5 +256,4 @@ for changelog entries. [MIT](LICENSE) -[archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs [permessage-deflate]: https://tools.ietf.org/html/rfc7692 From bc35fa4eee26c4dfea20b0998893515487b2f0a7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 21 Jan 2017 16:12:25 +0100 Subject: [PATCH 217/489] chore(package): update eslint to version 3.14.0 (#974) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 628cd215d..597951a65 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.3.0", - "eslint": "~3.13.0", + "eslint": "~3.14.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From d74a32e1761a4016764b44f617d57180e9e3af5b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 25 Jan 2017 09:36:26 +0100 Subject: [PATCH 218/489] [fix] Take into account the data queued in the sender (#971) This makes the `bufferedAmount` getter take into account the data queued in the sender. --- lib/Sender.js | 75 +++++++++++++++++++++++++++--------------- lib/WebSocket.js | 4 ++- test/Sender.test.js | 18 ++++++++++ test/WebSocket.test.js | 30 +++++++++++++---- 4 files changed, 94 insertions(+), 33 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 74f6e85d1..3913a8139 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -24,12 +24,16 @@ class Sender { */ constructor (socket, extensions) { this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName]; + this._socket = socket; + this.firstFragment = true; - this.processing = false; this.compress = false; - this._socket = socket; - this.onerror = null; + + this.processing = false; + this.bufferedBytes = 0; this.queue = []; + + this.onerror = null; } /** @@ -86,10 +90,23 @@ class Sender { * @public */ ping (data, mask) { + var readOnly = true; + + if (data && !Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(data); + readOnly = false; + } + } + if (this.perMessageDeflate) { - this.enqueue([this.doPing, data, mask]); + this.enqueue([this.doPing, data, mask, readOnly]); } else { - this.doPing(data, mask); + this.doPing(data, mask, readOnly); } } @@ -98,14 +115,15 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Boolean} readOnly Specifies whether `data` can be modified * @private */ - doPing (data, mask) { + doPing (data, mask, readOnly) { this.frameAndSend(data, { - readOnly: true, opcode: 0x09, rsv1: false, fin: true, + readOnly, mask }); @@ -120,10 +138,23 @@ class Sender { * @public */ pong (data, mask) { + var readOnly = true; + + if (data && !Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(data); + readOnly = false; + } + } + if (this.perMessageDeflate) { - this.enqueue([this.doPong, data, mask]); + this.enqueue([this.doPong, data, mask, readOnly]); } else { - this.doPong(data, mask); + this.doPong(data, mask, readOnly); } } @@ -132,14 +163,15 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Boolean} readOnly Specifies whether `data` can be modified * @private */ - doPong (data, mask) { + doPong (data, mask, readOnly) { this.frameAndSend(data, { - readOnly: true, opcode: 0x0a, rsv1: false, fin: true, + readOnly, mask }); @@ -243,7 +275,7 @@ class Sender { /** * Frames and sends a piece of data according to the HyBi WebSocket protocol. * - * @param {*} data The data to send + * @param {Buffer} data The data to send * @param {Object} options Options object * @param {Number} options.opcode The opcode * @param {Boolean} options.readOnly Specifies whether `data` can be modified @@ -267,17 +299,6 @@ class Sender { return; } - if (!Buffer.isBuffer(data)) { - if (data instanceof ArrayBuffer) { - data = Buffer.from(data); - } else if (ArrayBuffer.isView(data)) { - data = viewToBuffer(data); - } else { - data = Buffer.from(data); - options.readOnly = false; - } - } - const mergeBuffers = data.length < 1024 || options.mask && options.readOnly; var dataOffset = options.mask ? 6 : 2; var payloadLength = data.length; @@ -334,12 +355,13 @@ class Sender { dequeue () { if (this.processing) return; - const handler = this.queue.shift(); - if (!handler) return; + const params = this.queue.shift(); + if (!params) return; + if (params[1]) this.bufferedBytes -= params[1].length; this.processing = true; - handler[0].apply(this, handler.slice(1)); + params[0].apply(this, params.slice(1)); } /** @@ -361,6 +383,7 @@ class Sender { * @private */ enqueue (params) { + if (params[1]) this.bufferedBytes += params[1].length; this.queue.push(params); this.dequeue(); } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ea3513559..ec7490bd0 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -82,7 +82,9 @@ class WebSocket extends EventEmitter { get bufferedAmount () { var amount = 0; - if (this._socket) amount = this._socket.bufferSize || 0; + if (this._socket) { + amount = this._socket.bufferSize + this._sender.bufferedBytes; + } return amount; } diff --git a/test/Sender.test.js b/test/Sender.test.js index 48c126963..d6454b47a 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -73,6 +73,24 @@ describe('Sender', function () { }); }); + describe('#pong', function () { + it('works with multiple types of data', function (done) { + let count = 0; + const sender = new Sender({ + write: (data) => { + assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69]))); + if (++count === 3) done(); + } + }); + + const array = new Uint8Array([0x68, 0x69]); + + sender.pong(array.buffer, false); + sender.pong(array, false); + sender.pong('hi', false); + }); + }); + describe('#send', function () { it('compresses data if compress option is enabled', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 7ac77dd96..13b9d4bb4 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -131,19 +131,34 @@ describe('WebSocket', function () { }); it('defaults to zero upon "open"', function (done) { - server.createServer(++port, (srv) => { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.onopen = () => { assert.strictEqual(ws.bufferedAmount, 0); - - ws.on('close', () => srv.close(done)); - ws.close(); + wss.close(done); }; }); }); - it('stress kernel write buffer', function (done) { + it('takes into account the data in the sender queue', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { + ws.send('foo'); + ws.send('bar', (err) => { + assert.ifError(err); + assert.strictEqual(ws.bufferedAmount, 0); + wss.close(done); + }); + + assert.strictEqual(ws.bufferedAmount, 3); + }); + }); + }); + + it('takes into account the data in the socket queue', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false @@ -152,7 +167,10 @@ describe('WebSocket', function () { wss.on('connection', (ws) => { while (true) { - if (ws.bufferedAmount > 0) break; + if (ws._socket.bufferSize > 0) { + assert.strictEqual(ws.bufferedAmount, ws._socket.bufferSize); + break; + } ws.send('hello'.repeat(1e4)); } wss.close(done); From 236ea222f8ddde18fbac0e234ec7297cfd66f4ab Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 25 Jan 2017 09:45:08 +0100 Subject: [PATCH 219/489] [dist] 2.0.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 597951a65..ed02968f5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "license": "MIT", "main": "index.js", "keywords": [ From cb50a2958523770735be0a4118027ef6d1262328 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Jan 2017 12:45:05 +0100 Subject: [PATCH 220/489] [dist] 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed02968f5..2cdc1c9ac 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0-beta.2", + "version": "2.0.0", "license": "MIT", "main": "index.js", "keywords": [ From d856dcb3b698c6184b393b93fe9e8e382fa971b8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 10:44:13 +0100 Subject: [PATCH 221/489] [fix] Save the value of the `compress` flag Fixes #983 --- lib/Sender.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 3913a8139..92cdfb7f4 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -220,7 +220,8 @@ class Sender { if (options.fin) this.firstFragment = true; if (this.perMessageDeflate) { - this.enqueue([this.sendCompressed, data, { + this.enqueue([this.dispatch, data, { + compress: this.compress, mask: options.mask, fin: options.fin, readOnly, @@ -239,21 +240,21 @@ class Sender { } /** - * Compresses, frames and sends a data message. + * Dispatches a data message. * * @param {Buffer} data The message to send * @param {Object} options Options object * @param {Number} options.opcode The opcode * @param {Boolean} options.readOnly Specifies whether `data` can be modified * @param {Boolean} options.fin Specifies whether or not to set the FIN bit + * @param {Boolean} options.compress Specifies whether or not to compress `data` * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (data, options, cb) { - if (!this.compress) { - options.rsv1 = false; + dispatch (data, options, cb) { + if (!options.compress) { this.frameAndSend(data, options, cb); this.continue(); return; From 715c1da2b2114e1cb9538d5b3ab6c4a664ab3adf Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 11:17:02 +0100 Subject: [PATCH 222/489] [test] Remove unused fixture --- test/fixtures/textfile | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 test/fixtures/textfile diff --git a/test/fixtures/textfile b/test/fixtures/textfile deleted file mode 100644 index a10483b0e..000000000 --- a/test/fixtures/textfile +++ /dev/null @@ -1,9 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam egestas, massa at aliquam luctus, sapien erat viverra elit, nec pulvinar turpis eros sagittis urna. Pellentesque imperdiet tempor varius. Pellentesque blandit, ipsum in imperdiet venenatis, mi elit faucibus odio, id condimentum ante enim sed lectus. Aliquam et odio non odio pellentesque pulvinar. Vestibulum a erat dolor. Integer pretium risus sit amet nisl volutpat nec venenatis magna egestas. Ut bibendum felis eu tellus laoreet eleifend. Nam pulvinar auctor tortor, eu iaculis leo vestibulum quis. In euismod risus ac purus vehicula et fermentum ligula consectetur. Vivamus condimentum tempus lacinia. - -Curabitur sodales condimentum urna id dictum. Sed quis justo sit amet quam ultrices tincidunt vel laoreet nulla. Nullam quis ipsum sed nisi mollis bibendum at sit amet nisi. Donec laoreet consequat velit sit amet mollis. Nam sed sapien a massa iaculis dapibus. Sed dui nunc, ultricies et pellentesque ullamcorper, aliquet vitae ligula. Integer eu velit in neque iaculis venenatis. Ut rhoncus cursus est, ac dignissim leo vehicula a. Nulla ullamcorper vulputate mauris id blandit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eleifend, nisi a tempor sollicitudin, odio massa pretium urna, quis congue sapien elit at tortor. Curabitur ipsum orci, vehicula non commodo molestie, laoreet id enim. Pellentesque convallis ultrices congue. Pellentesque nec iaculis lorem. In sagittis pharetra ipsum eget sodales. - -Fusce id nulla odio. Nunc nibh justo, placerat vel tincidunt sed, ornare et enim. Nulla vel urna vel ante commodo bibendum in vitae metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis erat nunc, semper eget sagittis sit amet, ullamcorper eget lacus. Donec hendrerit ipsum vitae eros vestibulum eu gravida neque tincidunt. Ut molestie lacinia nulla. Donec mattis odio at magna egestas at pellentesque eros accumsan. Praesent interdum sem sit amet nibh commodo dignissim. Duis laoreet, enim ultricies fringilla suscipit, enim libero cursus nulla, sollicitudin adipiscing erat velit ut dui. Nulla eleifend mauris at velit fringilla a molestie lorem venenatis. - -Donec sit amet scelerisque metus. Cras ac felis a nulla venenatis vulputate. Duis porttitor eros ac neque rhoncus eget aliquet neque egestas. Quisque sed nunc est, vitae dapibus quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In vehicula, est vitae posuere ultricies, diam purus pretium sapien, nec rhoncus dolor nisl eget arcu. Aliquam et nisi vitae risus tincidunt auctor. In vehicula, erat a cursus adipiscing, lorem orci congue est, nec ultricies elit dui in nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Lorem ipsum dolor sit amet, consectetur adipiscing elit. - -Duis congue tempus elit sit amet auctor. Duis dignissim, risus ut sollicitudin ultricies, dolor ligula gravida odio, nec congue orci purus ut ligula. Fusce pretium dictum lectus at volutpat. Sed non auctor mauris. Etiam placerat vestibulum massa id blandit. Quisque consequat lacus ut nulla euismod facilisis. Sed aliquet ipsum nec mi imperdiet viverra. Pellentesque ullamcorper, lectus nec varius gravida, odio justo cursus risus, eu sagittis metus arcu quis felis. Phasellus consectetur vehicula libero, at condimentum orci euismod vel. Nunc purus tortor, suscipit nec fringilla nec, vulputate et nibh. Nam porta vehicula neque. Praesent porttitor, sapien eu auctor euismod, arcu quam elementum urna, sed hendrerit magna augue sed quam. \ No newline at end of file From 6a49182a9078cc3758c0a7c7aebcfc165d13e13f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 11:24:27 +0100 Subject: [PATCH 223/489] [doc] Update URLs based on HTTP redirects --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b30735d5c..714f1a8d1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ws: a node.js WebSocket library +# ws: a Node.js WebSocket library [![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) [![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) @@ -8,7 +8,7 @@ `ws` is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation. -Passes the quite extensive Autobahn test suite. See http://websockets.github.com/ws +Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/ for the full reports. ## Protocol support From a8d21d40b3852f49e8a902ddc95055e9a1957130 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 11:25:59 +0100 Subject: [PATCH 224/489] [dist] 2.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2cdc1c9ac..487199922 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "main": "index.js", "keywords": [ From 466e210b7eb81a5428e33ae8f605944216168b26 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 3 Feb 2017 17:53:56 +0100 Subject: [PATCH 225/489] [minor] Add support for bufferutil@2 and utf-8-validate@3 --- lib/BufferUtil.fallback.js | 66 ++++++++++++++++++++++++++------------ lib/BufferUtil.js | 4 ++- lib/Receiver.js | 8 ++--- lib/Sender.js | 2 +- lib/Validation.fallback.js | 6 +--- lib/Validation.js | 6 +++- test/Validation.test.js | 14 ++++---- 7 files changed, 66 insertions(+), 40 deletions(-) diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index a9607e7a0..5fcde1c3f 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -6,25 +6,51 @@ 'use strict'; -exports.BufferUtil = { - merge: function (mergedBuffer, buffers) { - var offset = 0; - for (var i = 0, l = buffers.length; i < l; ++i) { - var buf = buffers[i]; - buf.copy(mergedBuffer, offset); - offset += buf.length; - } - }, - mask: function (source, mask, output, offset, length) { - for (var i = 0; i < length; i++) { - output[offset + i] = source[i] ^ mask[i & 3]; - } - }, - unmask: function (data, mask) { - // required until https://github.com/nodejs/node/issues/9006 is resolved - var length = data.length; - for (var i = 0; i < length; i++) { - data[i] ^= mask[i & 3]; - } +/** + * Merges an array of buffers into a target buffer. + * + * @param {Buffer} target The target buffer + * @param {Buffer[]} buffers The array of buffers to merge + * @public + */ +const merge = (target, buffers) => { + var offset = 0; + for (var i = 0; i < buffers.length; i++) { + const buf = buffers[i]; + buf.copy(target, offset); + offset += buf.length; + } +}; + +/** + * Masks a buffer using the given mask. + * + * @param {Buffer} source The buffer to mask + * @param {Buffer} mask The mask to use + * @param {Buffer} output The buffer where to store the result + * @param {Number} offset The offset at which to start writing + * @param {Number} length The number of bytes to mask. + * @public + */ +const mask = (source, mask, output, offset, length) => { + for (var i = 0; i < length; i++) { + output[offset + i] = source[i] ^ mask[i & 3]; } }; + +/** + * Unmasks a buffer using the given mask. + * + * @param {Buffer} buffer The buffer to unmask + * @param {Buffer} mask The mask to use + * @public + */ +const unmask = (buffer, mask) => { + // Required until https://github.com/nodejs/node/issues/9006 is resolved. + const length = buffer.length; + for (var i = 0; i < length; i++) { + buffer[i] ^= mask[i & 3]; + } +}; + +module.exports = { merge, mask, unmask }; diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js index 18c699894..7220fbc86 100644 --- a/lib/BufferUtil.js +++ b/lib/BufferUtil.js @@ -7,7 +7,9 @@ */ try { - module.exports = require('bufferutil'); + const bufferUtil = require('bufferutil'); + + module.exports = bufferUtil.BufferUtil || bufferUtil; } catch (e) { module.exports = require('./BufferUtil.fallback'); } diff --git a/lib/Receiver.js b/lib/Receiver.js index b79b19370..4a23311d6 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -7,8 +7,8 @@ 'use strict'; const PerMessageDeflate = require('./PerMessageDeflate'); -const bufferUtil = require('./BufferUtil').BufferUtil; -const Validation = require('./Validation').Validation; +const isValidUTF8 = require('./Validation'); +const bufferUtil = require('./BufferUtil'); const ErrorCodes = require('./ErrorCodes'); const EMPTY_BUFFER = Buffer.alloc(0); @@ -360,7 +360,7 @@ class Receiver { if (this.opcode === 2) { this.onmessage(buf, { masked: this.masked, binary: true }); } else { - if (!Validation.isValidUTF8(buf)) { + if (!isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } @@ -396,7 +396,7 @@ class Receiver { const buf = data.slice(2); - if (!Validation.isValidUTF8(buf)) { + if (!isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } diff --git a/lib/Sender.js b/lib/Sender.js index 92cdfb7f4..b36d60138 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -9,7 +9,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('./PerMessageDeflate'); -const bufferUtil = require('./BufferUtil').BufferUtil; +const bufferUtil = require('./BufferUtil'); const ErrorCodes = require('./ErrorCodes'); /** diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js index 1b1432463..60663149a 100644 --- a/lib/Validation.fallback.js +++ b/lib/Validation.fallback.js @@ -6,8 +6,4 @@ 'use strict'; -exports.Validation = { - isValidUTF8: function (buffer) { - return true; - } -}; +module.exports = () => true; diff --git a/lib/Validation.js b/lib/Validation.js index 284ab2fb1..7fa5f5084 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -7,7 +7,11 @@ 'use strict'; try { - module.exports = require('utf-8-validate'); + const isValidUTF8 = require('utf-8-validate'); + + module.exports = typeof isValidUTF8 === 'object' + ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 + : isValidUTF8; } catch (e) { module.exports = require('./Validation.fallback'); } diff --git a/test/Validation.test.js b/test/Validation.test.js index d09b6631d..d13cf460a 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -2,9 +2,7 @@ const assert = require('assert'); -const validation = require('../lib/Validation'); - -const Validation = validation.Validation; +const isValidUTF8 = require('../lib/Validation'); describe('Validation', function () { describe('isValidUTF8', function () { @@ -27,7 +25,7 @@ describe('Validation', function () { 'vulputate quis. Morbi ut pulvinar augue.' ); - assert.ok(Validation.isValidUTF8(validBuffer)); + assert.ok(isValidUTF8(validBuffer)); }); it('should return false for an erroneous string', function () { @@ -37,16 +35,16 @@ describe('Validation', function () { 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 ]); - assert.ok(!Validation.isValidUTF8(invalidBuffer)); + assert.ok(!isValidUTF8(invalidBuffer)); }); it('should return true for valid cases from the autobahn test suite', function () { - assert.ok(Validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); - assert.ok(Validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); + assert.ok(isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); + assert.ok(isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); }); it('should return false for erroneous autobahn strings', function () { - assert.ok(!Validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); + assert.ok(!isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); }); }); }); From f0d03cc79fb55d2df438120dedab01e016ba67b2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 3 Feb 2017 18:10:46 +0100 Subject: [PATCH 226/489] [dist] 2.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 487199922..95eb72d4d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.1", + "version": "2.0.2", "license": "MIT", "main": "index.js", "keywords": [ From 04f9ebcdbd3391ffc126e243a93f7b8624e7f6dd Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 3 Feb 2017 20:57:08 +0100 Subject: [PATCH 227/489] chore(package): update bufferutil to version 2.0.0 (#986) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95eb72d4d..5b78cdfa4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "benchmark": "~2.1.2", - "bufferutil": "~1.3.0", + "bufferutil": "~2.0.0", "eslint": "~3.14.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", From ed18a8f2c9daa59df8511ec0ef1594ca052b892f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 3 Feb 2017 20:57:26 +0100 Subject: [PATCH 228/489] chore(package): update utf-8-validate to version 3.0.0 (#987) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b78cdfa4..452031298 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "eslint-plugin-standard": "~2.0.1", "istanbul": "~0.4.5", "mocha": "~3.2.0", - "utf-8-validate": "~2.0.0" + "utf-8-validate": "~3.0.0" } } From 66917d04d18e1e6b13d0102dc4d4914886e03943 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 4 Feb 2017 07:39:03 +0100 Subject: [PATCH 229/489] chore(package): update eslint to version 3.15.0 (#988) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 452031298..a990b0de4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~2.0.0", - "eslint": "~3.14.0", + "eslint": "~3.15.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From f043b52aea2d8e739fe5b3a9d6b776e7d7500739 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 6 Feb 2017 18:54:04 +0100 Subject: [PATCH 230/489] Restore support for default port numbers --- lib/WebSocket.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ec7490bd0..a655a134f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -541,6 +541,7 @@ function initAsClient (address, protocols, options) { const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); + const port = serverUrl.port || (isSecure ? 443 : 80); const httpObj = isSecure ? https : http; // @@ -559,7 +560,7 @@ function initAsClient (address, protocols, options) { const requestOptions = { host: serverUrl.hostname, - port: serverUrl.port, + port, path: '/', headers: { 'Sec-WebSocket-Version': options.protocolVersion, From d90f481426c51539309ff7f547a38f1766a27177 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Feb 2017 14:24:28 +0100 Subject: [PATCH 231/489] [fix] Prevent the parser from triggering a stack overflow (#992) This makes the parser work with buffers containing thousands of frames. --- bench/parser.benchmark.js | 1 - lib/Receiver.js | 137 +++++++++++++++++++++----------------- test/Receiver.test.js | 27 ++++++-- 3 files changed, 99 insertions(+), 66 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 2924b5449..c28e82583 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -18,7 +18,6 @@ const Receiver = require('../').Receiver; // Receiver.prototype.cleanup = function () { this.state = 0; - this.start(); }; function createBinaryPacket (length) { diff --git a/lib/Receiver.js b/lib/Receiver.js index 4a23311d6..2c2d52b03 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -13,12 +13,14 @@ const ErrorCodes = require('./ErrorCodes'); const EMPTY_BUFFER = Buffer.alloc(0); -const START = 0; +const GET_INFO = 0; const GET_PAYLOAD_LENGTH_16 = 1; const GET_PAYLOAD_LENGTH_64 = 2; -const GET_MASK = 3; -const GET_DATA = 4; -const HANDLE_DATA = 5; +const HAVE_LENGTH = 3; +const GET_MASK = 4; +const GET_DATA = 5; +const HANDLE_DATA = 6; +const INFLATING = 7; /** * HyBi Receiver implementation. @@ -59,13 +61,14 @@ class Receiver { this.onping = null; this.onpong = null; - this.state = START; + this.state = GET_INFO; } /** * Consumes bytes from the available buffered data. * * @param {Number} bytes The number of bytes to consume + * @return {Buffer} Consumed bytes * @private */ readBuffer (bytes) { @@ -128,45 +131,55 @@ class Receiver { this.bufferedBytes += data.length; this.buffers.push(data); + this.startLoop(); + } - switch (this.state) { - case START: - this.start(); - break; - case GET_PAYLOAD_LENGTH_16: - this.getPayloadLength16(); - break; - case GET_PAYLOAD_LENGTH_64: - this.getPayloadLength64(); - break; - case GET_MASK: - this.getMask(); + /** + * Starts the parsing loop. + * + * @private + */ + startLoop () { + while (true) { + if (this.state === GET_INFO) { + if (!this.getInfo()) break; + } else if (this.state === GET_PAYLOAD_LENGTH_16) { + if (!this.getPayloadLength16()) break; + } else if (this.state === GET_PAYLOAD_LENGTH_64) { + if (!this.getPayloadLength64()) break; + } else if (this.state === HAVE_LENGTH) { + if (!this.haveLength()) break; + } else if (this.state === GET_MASK) { + if (!this.getMask()) break; + } else if (this.state === GET_DATA) { + if (!this.getData()) break; + } else { // `HANDLE_DATA` or `INFLATING` break; - case GET_DATA: - this.getData(); + } } } /** * Reads the first two bytes of a frame. * + * @return {Boolean} `true` if the operation is successful, else `false` * @private */ - start () { - if (!this.hasBufferedBytes(2)) return; + getInfo () { + if (!this.hasBufferedBytes(2)) return false; const buf = this.readBuffer(2); if ((buf[0] & 0x30) !== 0x00) { this.error(new Error('RSV2 and RSV3 must be clear'), 1002); - return; + return false; } const compressed = (buf[0] & 0x40) === 0x40; if (compressed && !this.extensions[PerMessageDeflate.extensionName]) { this.error(new Error('RSV1 must be clear'), 1002); - return; + return false; } this.fin = (buf[0] & 0x80) === 0x80; @@ -176,76 +189,75 @@ class Receiver { if (this.opcode === 0x00) { if (compressed) { this.error(new Error('RSV1 must be clear'), 1002); - return; + return false; } if (!this.fragmented) { this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); - return; + return false; } else { this.opcode = this.fragmented; } } else if (this.opcode === 0x01 || this.opcode === 0x02) { if (this.fragmented) { this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); - return; + return false; } this.compressed = compressed; } else if (this.opcode > 0x07 && this.opcode < 0x0b) { if (!this.fin) { this.error(new Error('FIN must be set'), 1002); - return; + return false; } if (compressed) { this.error(new Error('RSV1 must be clear'), 1002); - return; + return false; } if (this.payloadLength > 0x7d) { this.error(new Error('invalid payload length'), 1002); - return; + return false; } } else { this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); - return; + return false; } if (!this.fin && !this.fragmented) this.fragmented = this.opcode; this.masked = (buf[1] & 0x80) === 0x80; - if (this.payloadLength === 126) { - this.state = GET_PAYLOAD_LENGTH_16; - this.getPayloadLength16(); - } else if (this.payloadLength === 127) { - this.state = GET_PAYLOAD_LENGTH_64; - this.getPayloadLength64(); - } else { - this.haveLength(); - } + if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16; + else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64; + else this.state = HAVE_LENGTH; + + return true; } /** * Gets extended payload length (7+16). * + * @return {Boolean} `true` if payload length has been read, else `false` * @private */ getPayloadLength16 () { - if (!this.hasBufferedBytes(2)) return; + if (!this.hasBufferedBytes(2)) return false; this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); - this.haveLength(); + this.state = HAVE_LENGTH; + return true; } /** * Gets extended payload length (7+64). * + * @return {Boolean} `true` if payload length has been read, else `false` * @private */ getPayloadLength64 () { - if (!this.hasBufferedBytes(8)) return; + if (!this.hasBufferedBytes(8)) return false; const buf = this.readBuffer(8); const num = buf.readUInt32BE(0, true); @@ -256,68 +268,72 @@ class Receiver { // if (num > Math.pow(2, 53 - 32) - 1) { this.error(new Error('max payload size exceeded'), 1009); - return; + return false; } this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); - this.haveLength(); + this.state = HAVE_LENGTH; + return true; } /** * Payload length has been read. * + * @return {Boolean} `false` if payload length exceeds `maxPayload`, else `true` * @private */ haveLength () { if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) { - return; + return false; } - if (this.masked) { - this.state = GET_MASK; - this.getMask(); - } else { - this.state = GET_DATA; - this.getData(); - } + if (this.masked) this.state = GET_MASK; + else this.state = GET_DATA; + return true; } /** * Reads mask bytes. * + * @return {Boolean} `true` if the mask has been read, else `false` * @private */ getMask () { - if (!this.hasBufferedBytes(4)) return; + if (!this.hasBufferedBytes(4)) return false; this.mask = this.readBuffer(4); this.state = GET_DATA; - this.getData(); + return true; } /** * Reads data bytes. * + * @return {Boolean} `true` if the data bytes have been read, else `false` * @private */ getData () { var data = EMPTY_BUFFER; if (this.payloadLength) { - if (!this.hasBufferedBytes(this.payloadLength)) return; + if (!this.hasBufferedBytes(this.payloadLength)) return false; data = this.readBuffer(this.payloadLength); if (this.masked) bufferUtil.unmask(data, this.mask); } + this.state = HANDLE_DATA; + if (this.opcode > 0x07) { this.controlMessage(data); } else if (this.compressed) { - this.state = HANDLE_DATA; + this.state = INFLATING; this.decompress(data); } else if (this.pushFragment(data)) { this.dataMessage(); } + + return true; } /** @@ -336,6 +352,7 @@ class Receiver { } if (this.pushFragment(buf)) this.dataMessage(); + if (this.state === GET_INFO) this.startLoop(); }); } @@ -369,8 +386,7 @@ class Receiver { } } - this.state = START; - this.start(); + this.state = GET_INFO; } /** @@ -413,8 +429,7 @@ class Receiver { if (this.opcode === 0x09) this.onping(data, flags); else this.onpong(data, flags); - this.state = START; - this.start(); + this.state = GET_INFO; } /** @@ -482,7 +497,7 @@ class Receiver { cleanup (cb) { this.dead = true; - if (!this.hadError && this.state === HANDLE_DATA) { + if (!this.hadError && this.state === INFLATING) { this.cleanupCallback = cb; } else { this.extensions = null; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 7129228be..db7c721bb 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -307,6 +307,25 @@ describe('Receiver', function () { }); }); + it('can parse a buffer with thousands of frames', function (done) { + const buf = Buffer.allocUnsafe(40000); + + for (let i = 0; i < buf.length; i += 2) { + buf[i] = 0x81; + buf[i + 1] = 0x00; + } + + const p = new Receiver(); + let counter = 0; + + p.onmessage = function (data) { + assert.strictEqual(data, ''); + if (++counter === 20000) done(); + }; + + p.add(buf); + }); + it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { const p = new Receiver({}, 10); let message; @@ -699,7 +718,7 @@ describe('Receiver', function () { p.add(frame); p.add(frame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, frame.length); p.cleanup(() => { @@ -731,7 +750,7 @@ describe('Receiver', function () { p.add(textFrame); p.add(closeFrame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length); p.cleanup(() => { @@ -763,7 +782,7 @@ describe('Receiver', function () { p.add(textFrame); p.add(invalidFrame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length); p.cleanup(() => { @@ -798,7 +817,7 @@ describe('Receiver', function () { p.add(textFrame); p.add(incompleteFrame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, incompleteFrame.length); p.cleanup(() => { From 3918e11d200e574beca9d5abd61fbe3020434aed Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Feb 2017 14:32:12 +0100 Subject: [PATCH 232/489] [dist] 2.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a990b0de4..17d91b579 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.2", + "version": "2.0.3", "license": "MIT", "main": "index.js", "keywords": [ From e86d28421bf3a4f7a0dc6a1d47608c8c5d1f117a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 9 Feb 2017 10:22:49 +0100 Subject: [PATCH 233/489] [minor] Remove unnecessary `if` statement Optimize for the most, hopefully, common case (no error). --- lib/Receiver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 2c2d52b03..09e9973bd 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -352,7 +352,7 @@ class Receiver { } if (this.pushFragment(buf)) this.dataMessage(); - if (this.state === GET_INFO) this.startLoop(); + this.startLoop(); }); } From 92d869695978dc73350be6590456c83ac9e80593 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 9 Feb 2017 14:38:42 +0100 Subject: [PATCH 234/489] [fix] Accept only GET requests --- lib/WebSocketServer.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4a0bd4e92..1f812eb6e 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -153,11 +153,9 @@ class WebSocketServer extends EventEmitter { const version = +req.headers['sec-websocket-version']; if ( - !this.shouldHandle(req) || - !req.headers.upgrade || - req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] || - version !== 8 && version !== 13 + req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || + !req.headers['sec-websocket-key'] || version !== 8 && version !== 13 || + !this.shouldHandle(req) ) { return abortConnection(socket, 400); } From 72cf8bee2dcdbdfdc523a5117fb0d82bcfb878bc Mon Sep 17 00:00:00 2001 From: Vasily Loginov Date: Tue, 26 Jul 2016 17:56:50 +0700 Subject: [PATCH 235/489] [example] Add Express session parsing example --- examples/express-session-parse/index.js | 80 +++++++++++++++++++ examples/express-session-parse/package.json | 11 +++ examples/express-session-parse/public/app.js | 46 +++++++++++ .../express-session-parse/public/index.html | 21 +++++ 4 files changed, 158 insertions(+) create mode 100644 examples/express-session-parse/index.js create mode 100644 examples/express-session-parse/package.json create mode 100644 examples/express-session-parse/public/app.js create mode 100644 examples/express-session-parse/public/index.html diff --git a/examples/express-session-parse/index.js b/examples/express-session-parse/index.js new file mode 100644 index 000000000..bac2d8e97 --- /dev/null +++ b/examples/express-session-parse/index.js @@ -0,0 +1,80 @@ +'use strict'; + +const session = require('express-session'); +const express = require('express'); +const http = require('http'); +const uuid = require('uuid'); + +const WebSocket = require('../..'); + +const app = express(); + +// +// We need the same instance of the session parser in express and +// WebSocket server. +// +const sessionParser = session({ + saveUninitialized: false, + secret: '$eCuRiTy', + resave: false +}); + +// +// Serve static files from the 'public' folder. +// +app.use(express.static('public')); +app.use(sessionParser); + +app.post('/login', (req, res) => { + // + // "Log in" user and set userId to session. + // + const id = uuid.v4(); + + console.log(`Updating session for user ${id}`); + req.session.userId = id; + res.send({ result: 'OK', message: 'Session updated' }); +}); + +app.delete('/logout', (request, response) => { + console.log('Destroying session'); + request.session.destroy(); + response.send({ result: 'OK', message: 'Session destroyed' }); +}); + +// +// Create HTTP server by ourselves. +// +const server = http.createServer(app); + +const wss = new WebSocket.Server({ + verifyClient: (info, done) => { + console.log('Parsing session from request...'); + sessionParser(info.req, {}, () => { + console.log('Session is parsed!'); + + // + // We can reject the connection by returning false to done(). For example, + // reject here if user is unknown. + // + done(info.req.session.userId); + }); + }, + server +}); + +wss.on('connection', (ws) => { + ws.on('message', (message) => { + const session = ws.upgradeReq.session; + + // + // Here we can now use session parameters. + // + console.log(`WS message ${message} from user ${session.userId}`); + }); +}); + +// +// Start the server. +// +server.listen(8080, () => console.log('Listening on http://localhost:8080')); diff --git a/examples/express-session-parse/package.json b/examples/express-session-parse/package.json new file mode 100644 index 000000000..cf96cbc49 --- /dev/null +++ b/examples/express-session-parse/package.json @@ -0,0 +1,11 @@ +{ + "author": "", + "name": "express-session-parse", + "version": "0.0.0", + "repository": "websockets/ws", + "dependencies": { + "express": "~4.14.1", + "express-session": "~1.15.1", + "uuid": "~3.0.1" + } +} diff --git a/examples/express-session-parse/public/app.js b/examples/express-session-parse/public/app.js new file mode 100644 index 000000000..916fafd41 --- /dev/null +++ b/examples/express-session-parse/public/app.js @@ -0,0 +1,46 @@ +/* global fetch, WebSocket, location */ +(() => { + const messages = document.querySelector('#messages'); + const wsButton = document.querySelector('#wsButton'); + const logout = document.querySelector('#logout'); + const login = document.querySelector('#login'); + + const showMessage = (message) => { + messages.textContent += `\n${message}`; + messages.scrollTop = messages.scrollHeight; + }; + + const handleResponse = (response) => { + return response.ok + ? response.json().then((data) => JSON.stringify(data, null, 2)) + : Promise.reject(new Error('Unexpected response')); + }; + + login.onclick = () => { + fetch('/login', { method: 'POST', credentials: 'same-origin' }) + .then(handleResponse) + .then(showMessage) + .catch((err) => showMessage(err.message)); + }; + + logout.onclick = () => { + fetch('/logout', { method: 'DELETE', credentials: 'same-origin' }) + .then(handleResponse) + .then(showMessage) + .catch((err) => showMessage(err.message)); + }; + + let ws; + + wsButton.onclick = () => { + if (ws) { + ws.onerror = ws.onopen = ws.onclose = null; + ws.close(); + } + + ws = new WebSocket(`ws://${location.host}`); + ws.onerror = () => showMessage('WebSocket error'); + ws.onopen = () => showMessage('WebSocket connection established'); + ws.onclose = () => showMessage('WebSocket connection closed'); + }; +})(); diff --git a/examples/express-session-parse/public/index.html b/examples/express-session-parse/public/index.html new file mode 100644 index 000000000..c99949c77 --- /dev/null +++ b/examples/express-session-parse/public/index.html @@ -0,0 +1,21 @@ + + + + + Express session demo + + +

Choose an action.

+ + + +

+    
+  
+

From ac8fcce52d10dc6fbc303f61650c4c2cd211fbfe Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 15 Feb 2017 21:17:17 +0100
Subject: [PATCH 236/489] [test] Do not override `Receiver#error()`

---
 test/Receiver.test.js | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index db7c721bb..5d8c3b020 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -377,7 +377,7 @@ describe('Receiver', function () {
   it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV1 must be clear');
       assert.strictEqual(code, 1002);
@@ -393,7 +393,7 @@ describe('Receiver', function () {
 
     const p = new Receiver({ 'permessage-deflate': perMessageDeflate });
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV1 must be clear');
       assert.strictEqual(code, 1002);
@@ -406,7 +406,7 @@ describe('Receiver', function () {
   it('raises an error when RSV2 is on', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear');
       assert.strictEqual(code, 1002);
@@ -419,7 +419,7 @@ describe('Receiver', function () {
   it('raises an error when RSV3 is on', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear');
       assert.strictEqual(code, 1002);
@@ -432,7 +432,7 @@ describe('Receiver', function () {
   it('raises an error if the first frame in a fragmented message has opcode 0', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid opcode: 0');
       assert.strictEqual(code, 1002);
@@ -445,7 +445,7 @@ describe('Receiver', function () {
   it('raises an error if a frame has opcode 1 in the middle of a fragmented message', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid opcode: 1');
       assert.strictEqual(code, 1002);
@@ -459,7 +459,7 @@ describe('Receiver', function () {
   it('raises an error if a frame has opcode 2 in the middle of a fragmented message', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid opcode: 2');
       assert.strictEqual(code, 1002);
@@ -473,7 +473,7 @@ describe('Receiver', function () {
   it('raises an error when a control frame has the FIN bit off', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'FIN must be set');
       assert.strictEqual(code, 1002);
@@ -489,7 +489,7 @@ describe('Receiver', function () {
 
     const p = new Receiver({ 'permessage-deflate': perMessageDeflate });
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV1 must be clear');
       assert.strictEqual(code, 1002);
@@ -502,7 +502,7 @@ describe('Receiver', function () {
   it('raises an error when a control frame has the FIN bit off', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'FIN must be set');
       assert.strictEqual(code, 1002);
@@ -515,7 +515,7 @@ describe('Receiver', function () {
   it('raises an error when a control frame has a payload bigger than 125 B', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid payload length');
       assert.strictEqual(code, 1002);
@@ -528,7 +528,7 @@ describe('Receiver', function () {
   it('raises an error when a data frame has a payload bigger than 2^53 - 1 B', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'max payload size exceeded');
       assert.strictEqual(code, 1009);
@@ -545,7 +545,7 @@ describe('Receiver', function () {
   it('raises an error if a text frame contains invalid UTF-8 data', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid utf8 sequence');
       assert.strictEqual(code, 1007);
@@ -558,7 +558,7 @@ describe('Receiver', function () {
   it('raises an error if a close frame has a payload of 1 B', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid payload length');
       assert.strictEqual(code, 1002);
@@ -571,7 +571,7 @@ describe('Receiver', function () {
   it('raises an error if a close frame contains an invalid close code', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid status code: 0');
       assert.strictEqual(code, 1002);
@@ -584,7 +584,7 @@ describe('Receiver', function () {
   it('raises an error if a close frame contains invalid UTF-8 data', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid utf8 sequence');
       assert.strictEqual(code, 1007);
@@ -602,7 +602,7 @@ describe('Receiver', function () {
     const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask +
       util.mask(msg, mask).toString('hex');
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'max payload size exceeded');
       assert.strictEqual(code, 1009);
@@ -619,7 +619,7 @@ describe('Receiver', function () {
     const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) +
       msg.toString('hex');
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'max payload size exceeded');
       assert.strictEqual(code, 1009);

From 6695bd44dc2d899ab7f774222f7e07295b786000 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 15 Feb 2017 19:36:00 +0100
Subject: [PATCH 237/489] [fix] Fix parser crash on synchronous socket error

This commit fixes an issue that could happen when the parser was
processing data and an error was emitted synchronously on the socket,
for example when writing to a socket whose connection had been
abruptly closed (ECONNRESET).
In these cases the parser was cleaned up prematurely and crashed when
trying to use resources no longer available.
---
 lib/Receiver.js       | 105 ++++++++++++++++++++----------------------
 test/Receiver.test.js |   8 ++--
 2 files changed, 53 insertions(+), 60 deletions(-)

diff --git a/lib/Receiver.js b/lib/Receiver.js
index 09e9973bd..71da72c7e 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -16,11 +16,9 @@ const EMPTY_BUFFER = Buffer.alloc(0);
 const GET_INFO = 0;
 const GET_PAYLOAD_LENGTH_16 = 1;
 const GET_PAYLOAD_LENGTH_64 = 2;
-const HAVE_LENGTH = 3;
-const GET_MASK = 4;
-const GET_DATA = 5;
-const HANDLE_DATA = 6;
-const INFLATING = 7;
+const GET_MASK = 3;
+const GET_DATA = 4;
+const INFLATING = 5;
 
 /**
  * HyBi Receiver implementation.
@@ -54,6 +52,7 @@ class Receiver {
     this.cleanupCallback = null;
     this.hadError = false;
     this.dead = false;
+    this.loop = false;
 
     this.onmessage = null;
     this.onclose = null;
@@ -117,6 +116,7 @@ class Receiver {
   hasBufferedBytes (n) {
     if (this.bufferedBytes >= n) return true;
 
+    this.loop = false;
     if (this.dead) this.cleanup(this.cleanupCallback);
     return false;
   }
@@ -140,21 +140,27 @@ class Receiver {
    * @private
    */
   startLoop () {
-    while (true) {
-      if (this.state === GET_INFO) {
-        if (!this.getInfo()) break;
-      } else if (this.state === GET_PAYLOAD_LENGTH_16) {
-        if (!this.getPayloadLength16()) break;
-      } else if (this.state === GET_PAYLOAD_LENGTH_64) {
-        if (!this.getPayloadLength64()) break;
-      } else if (this.state === HAVE_LENGTH) {
-        if (!this.haveLength()) break;
-      } else if (this.state === GET_MASK) {
-        if (!this.getMask()) break;
-      } else if (this.state === GET_DATA) {
-        if (!this.getData()) break;
-      } else { // `HANDLE_DATA` or `INFLATING`
-        break;
+    this.loop = true;
+
+    while (this.loop) {
+      switch (this.state) {
+        case GET_INFO:
+          this.getInfo();
+          break;
+        case GET_PAYLOAD_LENGTH_16:
+          this.getPayloadLength16();
+          break;
+        case GET_PAYLOAD_LENGTH_64:
+          this.getPayloadLength64();
+          break;
+        case GET_MASK:
+          this.getMask();
+          break;
+        case GET_DATA:
+          this.getData();
+          break;
+        default: // `INFLATING`
+          this.loop = false;
       }
     }
   }
@@ -162,24 +168,23 @@ class Receiver {
   /**
    * Reads the first two bytes of a frame.
    *
-   * @return {Boolean} `true` if the operation is successful, else `false`
    * @private
    */
   getInfo () {
-    if (!this.hasBufferedBytes(2)) return false;
+    if (!this.hasBufferedBytes(2)) return;
 
     const buf = this.readBuffer(2);
 
     if ((buf[0] & 0x30) !== 0x00) {
       this.error(new Error('RSV2 and RSV3 must be clear'), 1002);
-      return false;
+      return;
     }
 
     const compressed = (buf[0] & 0x40) === 0x40;
 
     if (compressed && !this.extensions[PerMessageDeflate.extensionName]) {
       this.error(new Error('RSV1 must be clear'), 1002);
-      return false;
+      return;
     }
 
     this.fin = (buf[0] & 0x80) === 0x80;
@@ -189,40 +194,40 @@ class Receiver {
     if (this.opcode === 0x00) {
       if (compressed) {
         this.error(new Error('RSV1 must be clear'), 1002);
-        return false;
+        return;
       }
 
       if (!this.fragmented) {
         this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
-        return false;
+        return;
       } else {
         this.opcode = this.fragmented;
       }
     } else if (this.opcode === 0x01 || this.opcode === 0x02) {
       if (this.fragmented) {
         this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
-        return false;
+        return;
       }
 
       this.compressed = compressed;
     } else if (this.opcode > 0x07 && this.opcode < 0x0b) {
       if (!this.fin) {
         this.error(new Error('FIN must be set'), 1002);
-        return false;
+        return;
       }
 
       if (compressed) {
         this.error(new Error('RSV1 must be clear'), 1002);
-        return false;
+        return;
       }
 
       if (this.payloadLength > 0x7d) {
         this.error(new Error('invalid payload length'), 1002);
-        return false;
+        return;
       }
     } else {
       this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
-      return false;
+      return;
     }
 
     if (!this.fin && !this.fragmented) this.fragmented = this.opcode;
@@ -231,33 +236,28 @@ class Receiver {
 
     if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16;
     else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64;
-    else this.state = HAVE_LENGTH;
-
-    return true;
+    else this.haveLength();
   }
 
   /**
    * Gets extended payload length (7+16).
    *
-   * @return {Boolean} `true` if payload length has been read, else `false`
    * @private
    */
   getPayloadLength16 () {
-    if (!this.hasBufferedBytes(2)) return false;
+    if (!this.hasBufferedBytes(2)) return;
 
     this.payloadLength = this.readBuffer(2).readUInt16BE(0, true);
-    this.state = HAVE_LENGTH;
-    return true;
+    this.haveLength();
   }
 
   /**
    * Gets extended payload length (7+64).
    *
-   * @return {Boolean} `true` if payload length has been read, else `false`
    * @private
    */
   getPayloadLength64 () {
-    if (!this.hasBufferedBytes(8)) return false;
+    if (!this.hasBufferedBytes(8)) return;
 
     const buf = this.readBuffer(8);
     const num = buf.readUInt32BE(0, true);
@@ -268,62 +268,54 @@ class Receiver {
     //
     if (num > Math.pow(2, 53 - 32) - 1) {
       this.error(new Error('max payload size exceeded'), 1009);
-      return false;
+      return;
     }
 
     this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true);
-    this.state = HAVE_LENGTH;
-    return true;
+    this.haveLength();
   }
 
   /**
    * Payload length has been read.
    *
-   * @return {Boolean} `false` if payload length exceeds `maxPayload`, else `true`
    * @private
    */
   haveLength () {
     if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) {
-      return false;
+      return;
     }
 
     if (this.masked) this.state = GET_MASK;
     else this.state = GET_DATA;
-    return true;
   }
 
   /**
    * Reads mask bytes.
    *
-   * @return {Boolean} `true` if the mask has been read, else `false`
    * @private
    */
   getMask () {
-    if (!this.hasBufferedBytes(4)) return false;
+    if (!this.hasBufferedBytes(4)) return;
 
     this.mask = this.readBuffer(4);
     this.state = GET_DATA;
-    return true;
   }
 
   /**
    * Reads data bytes.
    *
-   * @return {Boolean} `true` if the data bytes have been read, else `false`
    * @private
    */
   getData () {
     var data = EMPTY_BUFFER;
 
     if (this.payloadLength) {
-      if (!this.hasBufferedBytes(this.payloadLength)) return false;
+      if (!this.hasBufferedBytes(this.payloadLength)) return;
 
       data = this.readBuffer(this.payloadLength);
       if (this.masked) bufferUtil.unmask(data, this.mask);
     }
 
-    this.state = HANDLE_DATA;
-
     if (this.opcode > 0x07) {
       this.controlMessage(data);
     } else if (this.compressed) {
@@ -332,8 +324,6 @@ class Receiver {
     } else if (this.pushFragment(data)) {
       this.dataMessage();
     }
-
-    return true;
   }
 
   /**
@@ -399,6 +389,7 @@ class Receiver {
     if (this.opcode === 0x08) {
       if (data.length === 0) {
         this.onclose(1000, '', { masked: this.masked });
+        this.loop = false;
         this.cleanup(this.cleanupCallback);
       } else if (data.length === 1) {
         this.error(new Error('invalid payload length'), 1002);
@@ -418,6 +409,7 @@ class Receiver {
         }
 
         this.onclose(code, buf.toString(), { masked: this.masked });
+        this.loop = false;
         this.cleanup(this.cleanupCallback);
       }
 
@@ -442,6 +434,7 @@ class Receiver {
   error (err, code) {
     this.onerror(err, code);
     this.hadError = true;
+    this.loop = false;
     this.cleanup(this.cleanupCallback);
   }
 
@@ -497,7 +490,7 @@ class Receiver {
   cleanup (cb) {
     this.dead = true;
 
-    if (!this.hadError && this.state === INFLATING) {
+    if (!this.hadError && (this.loop || this.state === INFLATING)) {
       this.cleanupCallback = cb;
     } else {
       this.extensions = null;
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 5d8c3b020..1290388af 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -718,7 +718,7 @@ describe('Receiver', function () {
       p.add(frame);
       p.add(frame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, frame.length);
 
       p.cleanup(() => {
@@ -750,7 +750,7 @@ describe('Receiver', function () {
       p.add(textFrame);
       p.add(closeFrame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length);
 
       p.cleanup(() => {
@@ -782,7 +782,7 @@ describe('Receiver', function () {
       p.add(textFrame);
       p.add(invalidFrame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length);
 
       p.cleanup(() => {
@@ -817,7 +817,7 @@ describe('Receiver', function () {
       p.add(textFrame);
       p.add(incompleteFrame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, incompleteFrame.length);
 
       p.cleanup(() => {

From 8ead18d00cb1bc7f7e40ab3abc9a14810a17ce3f Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 11 Feb 2017 10:24:24 +0100
Subject: [PATCH 238/489] [perf] Write to the socket as soon as possible

---
 lib/PerMessageDeflate.js |  3 ++-
 lib/Sender.js            | 40 ++++++++++------------------------------
 test/WebSocket.test.js   |  4 +++-
 3 files changed, 15 insertions(+), 32 deletions(-)

diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index d1569546f..92a26525f 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -288,7 +288,8 @@ class PerMessageDeflate {
 
   compress (data, fin, callback) {
     if (!data || data.length === 0) {
-      return callback(null, EMPTY_BLOCK);
+      process.nextTick(callback, null, EMPTY_BLOCK);
+      return;
     }
 
     var endpoint = this._isServer ? 'server' : 'client';
diff --git a/lib/Sender.js b/lib/Sender.js
index b36d60138..08997de95 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -29,8 +29,8 @@ class Sender {
     this.firstFragment = true;
     this.compress = false;
 
-    this.processing = false;
     this.bufferedBytes = 0;
+    this.deflating = false;
     this.queue = [];
 
     this.onerror = null;
@@ -78,8 +78,6 @@ class Sender {
       fin: true,
       mask
     }, cb);
-
-    if (this.perMessageDeflate) this.continue();
   }
 
   /**
@@ -126,8 +124,6 @@ class Sender {
       readOnly,
       mask
     });
-
-    if (this.perMessageDeflate) this.continue();
   }
 
   /**
@@ -174,8 +170,6 @@ class Sender {
       readOnly,
       mask
     });
-
-    if (this.perMessageDeflate) this.continue();
   }
 
   /**
@@ -256,10 +250,10 @@ class Sender {
   dispatch (data, options, cb) {
     if (!options.compress) {
       this.frameAndSend(data, options, cb);
-      this.continue();
       return;
     }
 
+    this.deflating = true;
     this.perMessageDeflate.compress(data, options.fin, (err, buf) => {
       if (err) {
         if (cb) cb(err);
@@ -269,7 +263,8 @@ class Sender {
 
       options.readOnly = false;
       this.frameAndSend(buf, options, cb);
-      this.continue();
+      this.deflating = false;
+      this.dequeue();
     });
   }
 
@@ -349,32 +344,17 @@ class Sender {
   }
 
   /**
-   * Executes a queued send operation.
+   * Executes queued send operations.
    *
    * @private
    */
   dequeue () {
-    if (this.processing) return;
-
-    const params = this.queue.shift();
-    if (!params) return;
-
-    if (params[1]) this.bufferedBytes -= params[1].length;
-    this.processing = true;
-
-    params[0].apply(this, params.slice(1));
-  }
+    while (!this.deflating && this.queue.length) {
+      const params = this.queue.shift();
 
-  /**
-   * Signals the completion of a send operation.
-   *
-   * @private
-   */
-  continue () {
-    process.nextTick(() => {
-      this.processing = false;
-      this.dequeue();
-    });
+      if (params[1]) this.bufferedBytes -= params[1].length;
+      params[0].apply(this, params.slice(1));
+    }
   }
 
   /**
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 13b9d4bb4..f7ea03d90 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -143,7 +143,9 @@ describe('WebSocket', function () {
 
       it('takes into account the data in the sender queue', function (done) {
         const wss = new WebSocketServer({ port: ++port }, () => {
-          const ws = new WebSocket(`ws://localhost:${port}`);
+          const ws = new WebSocket(`ws://localhost:${port}`, {
+            perMessageDeflate: { threshold: 0 }
+          });
 
           ws.on('open', () => {
             ws.send('foo');

From 3b9f4ca282d736db79f9f903da3c03f961741ea6 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 11 Feb 2017 15:09:53 +0100
Subject: [PATCH 239/489] [perf] Enqueue send operations only when strictly
 necessary

---
 lib/Sender.js | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index 08997de95..ee520cdda 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -55,7 +55,7 @@ class Sender {
     buf.writeUInt16BE(code || 1000, 0, true);
     if (buf.length > 2) buf.write(data, 2);
 
-    if (this.perMessageDeflate) {
+    if (this.deflating) {
       this.enqueue([this.doClose, buf, mask, cb]);
     } else {
       this.doClose(buf, mask, cb);
@@ -101,7 +101,7 @@ class Sender {
       }
     }
 
-    if (this.perMessageDeflate) {
+    if (this.deflating) {
       this.enqueue([this.doPing, data, mask, readOnly]);
     } else {
       this.doPing(data, mask, readOnly);
@@ -147,7 +147,7 @@ class Sender {
       }
     }
 
-    if (this.perMessageDeflate) {
+    if (this.deflating) {
       this.enqueue([this.doPong, data, mask, readOnly]);
     } else {
       this.doPong(data, mask, readOnly);
@@ -214,14 +214,20 @@ class Sender {
     if (options.fin) this.firstFragment = true;
 
     if (this.perMessageDeflate) {
-      this.enqueue([this.dispatch, data, {
+      const opts = {
         compress: this.compress,
         mask: options.mask,
         fin: options.fin,
         readOnly,
         opcode,
         rsv1
-      }, cb]);
+      };
+
+      if (this.deflating) {
+        this.enqueue([this.dispatch, data, opts, cb]);
+      } else {
+        this.dispatch(data, opts, cb);
+      }
     } else {
       this.frameAndSend(data, {
         mask: options.mask,
@@ -366,7 +372,6 @@ class Sender {
   enqueue (params) {
     if (params[1]) this.bufferedBytes += params[1].length;
     this.queue.push(params);
-    this.dequeue();
   }
 }
 

From 060b275d978ff7d095a92863fc3a1f7bcea878ed Mon Sep 17 00:00:00 2001
From: Varun Malhotra 
Date: Wed, 8 Jul 2015 11:28:06 -0700
Subject: [PATCH 240/489] [feature] Allow path to be specified with UNIX domain
 sockets

This change allows URL path with params to be specified with
UNIX domain socket URLs.

An example URL with UNIX domain socket looks like:
  ws+unix:///absolule/path/to/uds_socket:/pathname?query_params

Note that ':' is the seprator between socket path and URL path.

Existing URLs like the following also work:
  ws+unix:///absolule/path/to/uds_socket

In the above case, path gets set to '/'.
---
 doc/ws.md                    | 18 ++++++++++++++++++
 lib/WebSocket.js             | 25 +++++++++++++++----------
 test/WebSocketServer.test.js | 16 ++++++++++++----
 3 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 39d1b9126..cdc6aee05 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -190,6 +190,24 @@ requested to the server).
 
 Create a new WebSocket instance.
 
+#### UNIX Domain Sockets
+
+`ws` supports making requests to UNIX domain sockets. To make one, use the
+following URL scheme:
+
+```
+ws+unix:///absolule/path/to/uds_socket:/pathname?search_params
+```
+
+Note that `:` is the separator between the socket path and the URL path. If
+the URL path is omitted
+
+```
+ws+unix:///absolule/path/to/uds_socket
+```
+
+it defaults to `/`.
+
 ### Event: 'close'
 
 - `code` {Number}
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index a655a134f..2bfd1653d 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -537,11 +537,12 @@ function initAsClient (address, protocols, options) {
   const serverUrl = url.parse(address);
   const isUnixSocket = serverUrl.protocol === 'ws+unix:';
 
-  if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url');
+  if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) {
+    throw new Error('invalid url');
+  }
 
   const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
   const key = crypto.randomBytes(16).toString('base64');
-  const port = serverUrl.port || (isSecure ? 443 : 80);
   const httpObj = isSecure ? https : http;
 
   //
@@ -559,8 +560,8 @@ function initAsClient (address, protocols, options) {
   }
 
   const requestOptions = {
+    port: serverUrl.port || (isSecure ? 443 : 80),
     host: serverUrl.hostname,
-    port,
     path: '/',
     headers: {
       'Sec-WebSocket-Version': options.protocolVersion,
@@ -585,16 +586,20 @@ function initAsClient (address, protocols, options) {
     }
   }
   if (options.host) requestOptions.headers.Host = options.host;
-  if (options.family) requestOptions.family = options.family;
+  if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
 
   if (options.localAddress) requestOptions.localAddress = options.localAddress;
-  if (isUnixSocket) requestOptions.socketPath = serverUrl.pathname;
-  if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
+  if (options.family) requestOptions.family = options.family;
 
-  //
-  // Make sure that path starts with `/`.
-  //
-  if (serverUrl.path) {
+  if (isUnixSocket) {
+    const parts = serverUrl.path.split(':');
+
+    requestOptions.socketPath = parts[0];
+    requestOptions.path = parts[1];
+  } else if (serverUrl.path) {
+    //
+    // Make sure that path starts with `/`.
+    //
     if (serverUrl.path.charAt(0) !== '/') {
       requestOptions.path = `/${serverUrl.path}`;
     } else {
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 3579dcf8f..72481c996 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -3,6 +3,7 @@
 'use strict';
 
 const assert = require('assert');
+const crypto = require('crypto');
 const https = require('https');
 const http = require('http');
 const net = require('net');
@@ -82,16 +83,23 @@ describe('WebSocketServer', function () {
       if (process.platform === 'win32') return done();
 
       const server = http.createServer();
-      const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`;
+      const sockPath = `/tmp/ws.${crypto.randomBytes(16).toString('hex')}.socket`;
 
       server.listen(sockPath, () => {
         const wss = new WebSocketServer({ server });
-        const ws = new WebSocket(`ws+unix://${sockPath}`);
 
         wss.on('connection', (ws) => {
-          wss.close();
-          server.close(done);
+          if (wss.clients.size === 1) {
+            assert.strictEqual(ws.upgradeReq.url, '/foo?bar=bar');
+          } else {
+            assert.strictEqual(ws.upgradeReq.url, '/');
+            wss.close();
+            server.close(done);
+          }
         });
+
+        const ws = new WebSocket(`ws+unix://${sockPath}:/foo?bar=bar`);
+        ws.on('open', () => new WebSocket(`ws+unix://${sockPath}`));
       });
     });
 

From 5bccfe59252923b85e7cffd56e3ddc1bd742f378 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 18 Feb 2017 08:14:24 +0100
Subject: [PATCH 241/489] [dist] 2.1.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 17d91b579..8f510e39f 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.0.3",
+  "version": "2.1.0",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From ca4a6226c311c30d02344d361dce5984ec9a2a07 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 20 Feb 2017 15:48:55 +0100
Subject: [PATCH 242/489] [minor] Avoid using deprecated APIs

---
 lib/Receiver.js              | 2 +-
 test/WebSocket.test.js       | 2 +-
 test/WebSocketServer.test.js | 5 +----
 3 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/lib/Receiver.js b/lib/Receiver.js
index 71da72c7e..f7dfcfda6 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -85,7 +85,7 @@ class Receiver {
       return dst;
     }
 
-    dst = new Buffer(bytes);
+    dst = Buffer.allocUnsafe(bytes);
 
     while (bytes > 0) {
       l = this.buffers[0].length;
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index f7ea03d90..b64c89283 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -194,7 +194,7 @@ describe('WebSocket', function () {
           assert.ok(req.headers.authorization);
           assert.strictEqual(
             req.headers.authorization,
-            `Basic ${new Buffer(auth).toString('base64')}`
+            `Basic ${Buffer.from(auth).toString('base64')}`
           );
 
           wss.close();
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 72481c996..afb3ded9c 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -126,10 +126,7 @@ describe('WebSocketServer', function () {
 
       const ws = new WebSocket(`ws://localhost:${port}/`);
 
-      ws.onopen = () => {
-        ws._socket.write(new Buffer([5]));
-        ws.send('');
-      };
+      ws.onopen = () => ws._socket.write(Buffer.from([0x85, 0x00]));
     });
   });
 

From bfa575541244405ed32094ee29c80ab0c92274df Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Mon, 20 Feb 2017 18:21:49 +0100
Subject: [PATCH 243/489] chore(package): update eslint to version 3.16.0
 (#1012)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8f510e39f..a3dca315a 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~2.0.0",
-    "eslint": "~3.15.0",
+    "eslint": "~3.16.0",
     "eslint-config-semistandard": "~7.0.0",
     "eslint-config-standard": "~6.2.1",
     "eslint-plugin-promise": "~3.4.0",

From e94a6ab386b2ec841a7e5279d8b425509a450bc1 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 09:36:24 +0100
Subject: [PATCH 244/489] [minor] Inline {BufferUtil,Validation}.fallback.js
 into parent modules

---
 lib/BufferUtil.fallback.js | 56 --------------------------------------
 lib/BufferUtil.js          | 49 ++++++++++++++++++++++++++++++++-
 lib/Validation.fallback.js |  9 ------
 lib/Validation.js          |  2 +-
 4 files changed, 49 insertions(+), 67 deletions(-)
 delete mode 100644 lib/BufferUtil.fallback.js
 delete mode 100644 lib/Validation.fallback.js

diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js
deleted file mode 100644
index 5fcde1c3f..000000000
--- a/lib/BufferUtil.fallback.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*!
- * ws: a node.js websocket client
- * Copyright(c) 2011 Einar Otto Stangvik 
- * MIT Licensed
- */
-
-'use strict';
-
-/**
- * Merges an array of buffers into a target buffer.
- *
- * @param {Buffer} target The target buffer
- * @param {Buffer[]} buffers The array of buffers to merge
- * @public
- */
-const merge = (target, buffers) => {
-  var offset = 0;
-  for (var i = 0; i < buffers.length; i++) {
-    const buf = buffers[i];
-    buf.copy(target, offset);
-    offset += buf.length;
-  }
-};
-
-/**
- * Masks a buffer using the given mask.
- *
- * @param {Buffer} source The buffer to mask
- * @param {Buffer} mask The mask to use
- * @param {Buffer} output The buffer where to store the result
- * @param {Number} offset The offset at which to start writing
- * @param {Number} length The number of bytes to mask.
- * @public
- */
-const mask = (source, mask, output, offset, length) => {
-  for (var i = 0; i < length; i++) {
-    output[offset + i] = source[i] ^ mask[i & 3];
-  }
-};
-
-/**
- * Unmasks a buffer using the given mask.
- *
- * @param {Buffer} buffer The buffer to unmask
- * @param {Buffer} mask The mask to use
- * @public
- */
-const unmask = (buffer, mask) => {
-  // Required until https://github.com/nodejs/node/issues/9006 is resolved.
-  const length = buffer.length;
-  for (var i = 0; i < length; i++) {
-    buffer[i] ^= mask[i & 3];
-  }
-};
-
-module.exports = { merge, mask, unmask };
diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index 7220fbc86..526654d42 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -11,5 +11,52 @@ try {
 
   module.exports = bufferUtil.BufferUtil || bufferUtil;
 } catch (e) {
-  module.exports = require('./BufferUtil.fallback');
+  /**
+   * Merges an array of buffers into a target buffer.
+   *
+   * @param {Buffer} target The target buffer
+   * @param {Buffer[]} buffers The array of buffers to merge
+   * @public
+   */
+  const merge = (target, buffers) => {
+    var offset = 0;
+    for (var i = 0; i < buffers.length; i++) {
+      const buf = buffers[i];
+      buf.copy(target, offset);
+      offset += buf.length;
+    }
+  };
+
+  /**
+   * Masks a buffer using the given mask.
+   *
+   * @param {Buffer} source The buffer to mask
+   * @param {Buffer} mask The mask to use
+   * @param {Buffer} output The buffer where to store the result
+   * @param {Number} offset The offset at which to start writing
+   * @param {Number} length The number of bytes to mask.
+   * @public
+   */
+  const mask = (source, mask, output, offset, length) => {
+    for (var i = 0; i < length; i++) {
+      output[offset + i] = source[i] ^ mask[i & 3];
+    }
+  };
+
+  /**
+   * Unmasks a buffer using the given mask.
+   *
+   * @param {Buffer} buffer The buffer to unmask
+   * @param {Buffer} mask The mask to use
+   * @public
+   */
+  const unmask = (buffer, mask) => {
+    // Required until https://github.com/nodejs/node/issues/9006 is resolved.
+    const length = buffer.length;
+    for (var i = 0; i < length; i++) {
+      buffer[i] ^= mask[i & 3];
+    }
+  };
+
+  module.exports = { merge, mask, unmask };
 }
diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js
deleted file mode 100644
index 60663149a..000000000
--- a/lib/Validation.fallback.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * ws: a node.js websocket client
- * Copyright(c) 2011 Einar Otto Stangvik 
- * MIT Licensed
- */
-
-'use strict';
-
-module.exports = () => true;
diff --git a/lib/Validation.js b/lib/Validation.js
index 7fa5f5084..f0380555c 100644
--- a/lib/Validation.js
+++ b/lib/Validation.js
@@ -13,5 +13,5 @@ try {
     ? isValidUTF8.Validation.isValidUTF8  // utf-8-validate@<3.0.0
     : isValidUTF8;
 } catch (e) {
-  module.exports = require('./Validation.fallback');
+  module.exports = () => true;
 }

From 8127ebc7c1eb86a1938685c5b8a70f8c4046bed0 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 10:03:07 +0100
Subject: [PATCH 245/489] [minor] Move shared constants into lib/Constants.js

---
 lib/Constants.js       | 5 +++++
 lib/Receiver.js        | 7 +++----
 lib/WebSocket.js       | 7 +++----
 lib/WebSocketServer.js | 5 ++---
 4 files changed, 13 insertions(+), 11 deletions(-)
 create mode 100644 lib/Constants.js

diff --git a/lib/Constants.js b/lib/Constants.js
new file mode 100644
index 000000000..732df7429
--- /dev/null
+++ b/lib/Constants.js
@@ -0,0 +1,5 @@
+'use strict';
+
+exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
+exports.EMPTY_BUFFER = Buffer.alloc(0);
+exports.NOOP = () => {};
diff --git a/lib/Receiver.js b/lib/Receiver.js
index f7dfcfda6..b43c36022 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -10,8 +10,7 @@ const PerMessageDeflate = require('./PerMessageDeflate');
 const isValidUTF8 = require('./Validation');
 const bufferUtil = require('./BufferUtil');
 const ErrorCodes = require('./ErrorCodes');
-
-const EMPTY_BUFFER = Buffer.alloc(0);
+const constants = require('./Constants');
 
 const GET_INFO = 0;
 const GET_PAYLOAD_LENGTH_16 = 1;
@@ -307,7 +306,7 @@ class Receiver {
    * @private
    */
   getData () {
-    var data = EMPTY_BUFFER;
+    var data = constants.EMPTY_BUFFER;
 
     if (this.payloadLength) {
       if (!this.hasBufferedBytes(this.payloadLength)) return;
@@ -357,7 +356,7 @@ class Receiver {
         ? Buffer.concat(this.fragments, this.messageLength)
         : this.fragments.length === 1
           ? this.fragments[0]
-          : EMPTY_BUFFER;
+          : constants.EMPTY_BUFFER;
 
       this.totalPayloadLength = 0;
       this.fragments.length = 0;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 2bfd1653d..22ef9d40c 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -16,13 +16,12 @@ const url = require('url');
 const PerMessageDeflate = require('./PerMessageDeflate');
 const EventTarget = require('./EventTarget');
 const Extensions = require('./Extensions');
+const constants = require('./Constants');
 const Receiver = require('./Receiver');
 const Sender = require('./Sender');
 
-const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
 const protocolVersion = 13;
-const noop = () => {};
 
 /**
  * Class representing a WebSocket.
@@ -230,7 +229,7 @@ class WebSocket extends EventEmitter {
     this.extensions = null;
 
     this.removeAllListeners();
-    this.on('error', noop); // Catch all errors after this.
+    this.on('error', constants.NOOP); // Catch all errors after this.
   }
 
   /**
@@ -662,7 +661,7 @@ function initAsClient (address, protocols, options) {
     this._req = null;
 
     const digest = crypto.createHash('sha1')
-      .update(key + GUID, 'binary')
+      .update(key + constants.GUID, 'binary')
       .digest('base64');
 
     if (res.headers['sec-websocket-accept'] !== digest) {
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index 1f812eb6e..1499a2cdc 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -14,10 +14,9 @@ const url = require('url');
 
 const PerMessageDeflate = require('./PerMessageDeflate');
 const Extensions = require('./Extensions');
+const constants = require('./Constants');
 const WebSocket = require('./WebSocket');
 
-const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
-
 /**
  * Class representing a WebSocket server.
  *
@@ -215,7 +214,7 @@ class WebSocketServer extends EventEmitter {
     if (!socket.readable || !socket.writable) return socket.destroy();
 
     const key = crypto.createHash('sha1')
-      .update(req.headers['sec-websocket-key'] + GUID, 'binary')
+      .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
       .digest('base64');
 
     const headers = [

From 5edb4601a71202cc9bf7bf0a173145cbe62a9a1c Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 11:08:21 +0100
Subject: [PATCH 246/489] [fix] Use a random masking key also for zero-length
 frames

---
 lib/Sender.js          | 21 ++++--------------
 lib/WebSocket.js       | 11 +++++-----
 test/Sender.test.js    | 48 ------------------------------------------
 test/WebSocket.test.js |  2 +-
 4 files changed, 10 insertions(+), 72 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index ee520cdda..2dd1ec96c 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -90,7 +90,7 @@ class Sender {
   ping (data, mask) {
     var readOnly = true;
 
-    if (data && !Buffer.isBuffer(data)) {
+    if (!Buffer.isBuffer(data)) {
       if (data instanceof ArrayBuffer) {
         data = Buffer.from(data);
       } else if (ArrayBuffer.isView(data)) {
@@ -136,7 +136,7 @@ class Sender {
   pong (data, mask) {
     var readOnly = true;
 
-    if (data && !Buffer.isBuffer(data)) {
+    if (!Buffer.isBuffer(data)) {
       if (data instanceof ArrayBuffer) {
         data = Buffer.from(data);
       } else if (ArrayBuffer.isView(data)) {
@@ -189,7 +189,7 @@ class Sender {
     var rsv1 = options.compress;
     var readOnly = true;
 
-    if (data && !Buffer.isBuffer(data)) {
+    if (!Buffer.isBuffer(data)) {
       if (data instanceof ArrayBuffer) {
         data = Buffer.from(data);
       } else if (ArrayBuffer.isView(data)) {
@@ -202,7 +202,7 @@ class Sender {
 
     if (this.firstFragment) {
       this.firstFragment = false;
-      if (rsv1 && data && this.perMessageDeflate) {
+      if (rsv1 && this.perMessageDeflate) {
         rsv1 = data.length >= this.perMessageDeflate.threshold;
       }
       this.compress = rsv1;
@@ -288,19 +288,6 @@ class Sender {
    * @private
    */
   frameAndSend (data, options, cb) {
-    if (!data) {
-      const bytes = [options.opcode, 0];
-
-      if (options.fin) bytes[0] |= 0x80;
-      if (options.mask) {
-        bytes[1] |= 0x80;
-        bytes.push(0, 0, 0, 0);
-      }
-
-      sendFramedData(this, Buffer.from(bytes), null, cb);
-      return;
-    }
-
     const mergeBuffers = data.length < 1024 || options.mask && options.readOnly;
     var dataOffset = options.mask ? 6 : 2;
     var payloadLength = data.length;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 22ef9d40c..c0800adfe 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -310,7 +310,7 @@ class WebSocket extends EventEmitter {
 
     if (typeof data === 'number') data = data.toString();
     if (mask === undefined) mask = !this._isServer;
-    this._sender.ping(data, mask);
+    this._sender.ping(data || constants.EMPTY_BUFFER, mask);
   }
 
   /**
@@ -329,7 +329,7 @@ class WebSocket extends EventEmitter {
 
     if (typeof data === 'number') data = data.toString();
     if (mask === undefined) mask = !this._isServer;
-    this._sender.pong(data, mask);
+    this._sender.pong(data || constants.EMPTY_BUFFER, mask);
   }
 
   /**
@@ -357,20 +357,19 @@ class WebSocket extends EventEmitter {
     }
 
     if (typeof data === 'number') data = data.toString();
-    else if (!data) data = '';
 
     const opts = Object.assign({
-      fin: true,
       binary: typeof data !== 'string',
       mask: !this._isServer,
-      compress: true
+      compress: true,
+      fin: true
     }, options);
 
     if (!this.extensions[PerMessageDeflate.extensionName]) {
       opts.compress = false;
     }
 
-    this._sender.send(data, opts, cb);
+    this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
   }
 
   /**
diff --git a/test/Sender.test.js b/test/Sender.test.js
index d6454b47a..da511051a 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -178,30 +178,6 @@ describe('Sender', function () {
       sender.send('123', { compress: true, fin: true });
     });
 
-    it('compresses null as first fragment', function (done) {
-      const fragments = [];
-      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
-      const sender = new Sender({
-        write: (data) => {
-          fragments.push(data);
-          if (fragments.length !== 2) return;
-
-          assert.strictEqual(fragments[0][0] & 0x40, 0x40);
-          assert.strictEqual(fragments[0].length, 3);
-          assert.strictEqual(fragments[1][0] & 0x40, 0x00);
-          assert.strictEqual(fragments[1].length, 8);
-          done();
-        }
-      }, {
-        'permessage-deflate': perMessageDeflate
-      });
-
-      perMessageDeflate.accept([{}]);
-
-      sender.send(null, { compress: true, fin: false });
-      sender.send('data', { compress: true, fin: true });
-    });
-
     it('compresses empty buffer as first fragment', function (done) {
       const fragments = [];
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
@@ -226,30 +202,6 @@ describe('Sender', function () {
       sender.send('data', { compress: true, fin: true });
     });
 
-    it('compresses null last fragment', function (done) {
-      const fragments = [];
-      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
-      const sender = new Sender({
-        write: (data) => {
-          fragments.push(data);
-          if (fragments.length !== 2) return;
-
-          assert.strictEqual(fragments[0][0] & 0x40, 0x40);
-          assert.strictEqual(fragments[0].length, 12);
-          assert.strictEqual(fragments[1][0] & 0x40, 0x00);
-          assert.strictEqual(fragments[1].length, 3);
-          done();
-        }
-      }, {
-        'permessage-deflate': perMessageDeflate
-      });
-
-      perMessageDeflate.accept([{}]);
-
-      sender.send('data', { compress: true, fin: false });
-      sender.send(null, { compress: true, fin: true });
-    });
-
     it('compresses empty buffer as last fragment', function (done) {
       const fragments = [];
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index b64c89283..8c336c9a3 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -811,7 +811,7 @@ describe('WebSocket', function () {
         ws.on('open', () => ws.send());
 
         srv.on('message', (message, flags) => {
-          assert.strictEqual(message, '');
+          assert.ok(message.equals(Buffer.alloc(0)));
           srv.close(done);
           ws.terminate();
         });

From eed0814853dbd4ac9b1ca3a29057121c8a0a703f Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 11:42:01 +0100
Subject: [PATCH 247/489] [minor] Remove no longer needed `if` statements

---
 lib/Sender.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index 2dd1ec96c..b3e6cd730 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -345,7 +345,7 @@ class Sender {
     while (!this.deflating && this.queue.length) {
       const params = this.queue.shift();
 
-      if (params[1]) this.bufferedBytes -= params[1].length;
+      this.bufferedBytes -= params[1].length;
       params[0].apply(this, params.slice(1));
     }
   }
@@ -357,7 +357,7 @@ class Sender {
    * @private
    */
   enqueue (params) {
-    if (params[1]) this.bufferedBytes += params[1].length;
+    this.bufferedBytes += params[1].length;
     this.queue.push(params);
   }
 }

From af8f00390c20bb0eaafae02be089eb268d893789 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sun, 26 Feb 2017 16:13:39 +0100
Subject: [PATCH 248/489] [feature] Make `Sender#frameAndSend()` a static
 method (#1016)

---
 bench/sender.benchmark.js |  24 +++--
 lib/Sender.js             | 191 ++++++++++++++++++--------------------
 test/Sender.test.js       |  35 ++-----
 3 files changed, 109 insertions(+), 141 deletions(-)

diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js
index a0f19a870..46132c190 100644
--- a/bench/sender.benchmark.js
+++ b/bench/sender.benchmark.js
@@ -33,19 +33,17 @@ const opts2 = {
 };
 
 const suite = new benchmark.Suite();
-var sender = new Sender();
-sender._socket = { write () {} };
-
-suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(data1, opts1));
-suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(data1, opts2));
-suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(data2, opts1));
-suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(data2, opts2));
-suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(data3, opts1));
-suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(data3, opts2));
-suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(data4, opts1));
-suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(data4, opts2));
-suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(data5, opts1));
-suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(data5, opts2));
+
+suite.add('frame, unmasked (64 B)', () => Sender.frame(data1, opts1));
+suite.add('frame, masked (64 B)', () => Sender.frame(data1, opts2));
+suite.add('frame, unmasked (16 KiB)', () => Sender.frame(data2, opts1));
+suite.add('frame, masked (16 KiB)', () => Sender.frame(data2, opts2));
+suite.add('frame, unmasked (64 KiB)', () => Sender.frame(data3, opts1));
+suite.add('frame, masked (64 KiB)', () => Sender.frame(data3, opts2));
+suite.add('frame, unmasked (200 KiB)', () => Sender.frame(data4, opts1));
+suite.add('frame, masked (200 KiB)', () => Sender.frame(data4, opts2));
+suite.add('frame, unmasked (1 MiB)', () => Sender.frame(data5, opts1));
+suite.add('frame, masked (1 MiB)', () => Sender.frame(data5, opts2));
 
 suite.on('cycle', (e) => console.log(e.target.toString()));
 
diff --git a/lib/Sender.js b/lib/Sender.js
index b3e6cd730..d36c7ba5a 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -36,6 +36,71 @@ class Sender {
     this.onerror = null;
   }
 
+  /**
+   * Frames a piece of data according to the HyBi WebSocket protocol.
+   *
+   * @param {Buffer} data The data to frame
+   * @param {Object} options Options object
+   * @param {Number} options.opcode The opcode
+   * @param {Boolean} options.readOnly Specifies whether `data` can be modified
+   * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
+   * @param {Boolean} options.mask Specifies whether or not to mask `data`
+   * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
+   * @return {Buffer[]} The framed data as a list of `Buffer` instances
+   * @public
+   */
+  static frame (data, options) {
+    const merge = data.length < 1024 || options.mask && options.readOnly;
+    var offset = options.mask ? 6 : 2;
+    var payloadLength = data.length;
+
+    if (data.length >= 65536) {
+      offset += 8;
+      payloadLength = 127;
+    } else if (data.length > 125) {
+      offset += 2;
+      payloadLength = 126;
+    }
+
+    const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
+
+    target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
+    if (options.rsv1) target[0] |= 0x40;
+
+    if (payloadLength === 126) {
+      target.writeUInt16BE(data.length, 2, true);
+    } else if (payloadLength === 127) {
+      target.writeUInt32BE(0, 2, true);
+      target.writeUInt32BE(data.length, 6, true);
+    }
+
+    if (!options.mask) {
+      target[1] = payloadLength;
+      if (merge) {
+        data.copy(target, offset);
+        return [target];
+      }
+
+      return [target, data];
+    }
+
+    const mask = crypto.randomBytes(4);
+
+    target[1] = payloadLength | 0x80;
+    target[offset - 4] = mask[0];
+    target[offset - 3] = mask[1];
+    target[offset - 2] = mask[2];
+    target[offset - 1] = mask[3];
+
+    if (merge) {
+      bufferUtil.mask(data, mask, target, offset, data.length);
+      return [target];
+    }
+
+    bufferUtil.mask(data, mask, data, 0, data.length);
+    return [target, data];
+  }
+
   /**
    * Sends a close message to the other peer.
    *
@@ -71,13 +136,13 @@ class Sender {
    * @private
    */
   doClose (data, mask, cb) {
-    this.frameAndSend(data, {
+    this.sendFrame(Sender.frame(data, {
       readOnly: false,
       opcode: 0x08,
       rsv1: false,
       fin: true,
       mask
-    }, cb);
+    }), cb);
   }
 
   /**
@@ -117,13 +182,13 @@ class Sender {
    * @private
    */
   doPing (data, mask, readOnly) {
-    this.frameAndSend(data, {
+    this.sendFrame(Sender.frame(data, {
       opcode: 0x09,
       rsv1: false,
       fin: true,
       readOnly,
       mask
-    });
+    }));
   }
 
   /**
@@ -163,13 +228,13 @@ class Sender {
    * @private
    */
   doPong (data, mask, readOnly) {
-    this.frameAndSend(data, {
+    this.sendFrame(Sender.frame(data, {
       opcode: 0x0a,
       rsv1: false,
       fin: true,
       readOnly,
       mask
-    });
+    }));
   }
 
   /**
@@ -229,13 +294,13 @@ class Sender {
         this.dispatch(data, opts, cb);
       }
     } else {
-      this.frameAndSend(data, {
+      this.sendFrame(Sender.frame(data, {
         mask: options.mask,
         fin: options.fin,
         rsv1: false,
         readOnly,
         opcode
-      }, cb);
+      }), cb);
     }
   }
 
@@ -255,7 +320,7 @@ class Sender {
    */
   dispatch (data, options, cb) {
     if (!options.compress) {
-      this.frameAndSend(data, options, cb);
+      this.sendFrame(Sender.frame(data, options), cb);
       return;
     }
 
@@ -268,74 +333,12 @@ class Sender {
       }
 
       options.readOnly = false;
-      this.frameAndSend(buf, options, cb);
+      this.sendFrame(Sender.frame(buf, options), cb);
       this.deflating = false;
       this.dequeue();
     });
   }
 
-  /**
-   * Frames and sends a piece of data according to the HyBi WebSocket protocol.
-   *
-   * @param {Buffer} data The data to send
-   * @param {Object} options Options object
-   * @param {Number} options.opcode The opcode
-   * @param {Boolean} options.readOnly Specifies whether `data` can be modified
-   * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
-   * @param {Boolean} options.mask Specifies whether or not to mask `data`
-   * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
-   * @param {Function} cb Callback
-   * @private
-   */
-  frameAndSend (data, options, cb) {
-    const mergeBuffers = data.length < 1024 || options.mask && options.readOnly;
-    var dataOffset = options.mask ? 6 : 2;
-    var payloadLength = data.length;
-
-    if (data.length >= 65536) {
-      dataOffset += 8;
-      payloadLength = 127;
-    } else if (data.length > 125) {
-      dataOffset += 2;
-      payloadLength = 126;
-    }
-
-    const outputBuffer = Buffer.allocUnsafe(
-      mergeBuffers ? data.length + dataOffset : dataOffset
-    );
-
-    outputBuffer[0] = options.fin ? options.opcode | 0x80 : options.opcode;
-    if (options.rsv1) outputBuffer[0] |= 0x40;
-
-    if (payloadLength === 126) {
-      outputBuffer.writeUInt16BE(data.length, 2, true);
-    } else if (payloadLength === 127) {
-      outputBuffer.writeUInt32BE(0, 2, true);
-      outputBuffer.writeUInt32BE(data.length, 6, true);
-    }
-
-    if (options.mask) {
-      const mask = getRandomMask();
-
-      outputBuffer[1] = payloadLength | 0x80;
-      outputBuffer[dataOffset - 4] = mask[0];
-      outputBuffer[dataOffset - 3] = mask[1];
-      outputBuffer[dataOffset - 2] = mask[2];
-      outputBuffer[dataOffset - 1] = mask[3];
-
-      if (mergeBuffers) {
-        bufferUtil.mask(data, mask, outputBuffer, dataOffset, data.length);
-      } else {
-        bufferUtil.mask(data, mask, data, 0, data.length);
-      }
-    } else {
-      outputBuffer[1] = payloadLength;
-      if (mergeBuffers) data.copy(outputBuffer, dataOffset);
-    }
-
-    sendFramedData(this, outputBuffer, mergeBuffers ? null : data, cb);
-  }
-
   /**
    * Executes queued send operations.
    *
@@ -360,6 +363,22 @@ class Sender {
     this.bufferedBytes += params[1].length;
     this.queue.push(params);
   }
+
+  /**
+   * Sends a frame.
+   *
+   * @param {Buffer[]} list The frame to send
+   * @param {Function} cb Callback
+   * @private
+   */
+  sendFrame (list, cb) {
+    if (list.length === 2) {
+      this._socket.write(list[0]);
+      this._socket.write(list[1], cb);
+    } else {
+      this._socket.write(list[0], cb);
+    }
+  }
 }
 
 module.exports = Sender;
@@ -380,31 +399,3 @@ function viewToBuffer (view) {
 
   return buf;
 }
-
-/**
- * Generates a random mask.
- *
- * @return {Buffer} The mask
- * @private
- */
-function getRandomMask () {
-  return crypto.randomBytes(4);
-}
-
-/**
- * Sends a frame.
- *
- * @param {Sender} sender Sender instance
- * @param {Buffer} outputBuffer The data to send
- * @param {Buffer} data Additional data to send if frame is split into two buffers
- * @param {Function} cb Callback
- * @private
- */
-function sendFramedData (sender, outputBuffer, data, cb) {
-  if (data) {
-    sender._socket.write(outputBuffer);
-    sender._socket.write(data, cb);
-  } else {
-    sender._socket.write(outputBuffer, cb);
-  }
-}
diff --git a/test/Sender.test.js b/test/Sender.test.js
index da511051a..7c60c1e8b 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -6,12 +6,11 @@ const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Sender = require('../lib/Sender');
 
 describe('Sender', function () {
-  describe('#frameAndSend', function () {
-    it('does not modify a masked binary buffer', function () {
-      const sender = new Sender({ write: () => {} });
+  describe('.frame', function () {
+    it('does not mutate the input buffer if data is `readOnly`', function () {
       const buf = Buffer.from([1, 2, 3, 4, 5]);
 
-      sender.frameAndSend(buf, {
+      Sender.frame(buf, {
         readOnly: true,
         rsv1: false,
         mask: true,
@@ -22,36 +21,16 @@ describe('Sender', function () {
       assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5])));
     });
 
-    it('does not modify a masked text buffer', function () {
-      const sender = new Sender({ write: () => {} });
-      const text = Buffer.from('hi there');
-
-      sender.frameAndSend(text, {
-        readOnly: true,
-        rsv1: false,
-        mask: true,
-        opcode: 1,
-        fin: true
-      });
-
-      assert.ok(text.equals(Buffer.from('hi there')));
-    });
-
-    it('sets RSV1 bit if compressed', function (done) {
-      const sender = new Sender({
-        write: (data) => {
-          assert.strictEqual(data[0] & 0x40, 0x40);
-          done();
-        }
-      });
-
-      sender.frameAndSend(Buffer.from('hi'), {
+    it('sets RSV1 bit if compressed', function () {
+      const list = Sender.frame(Buffer.from('hi'), {
         readOnly: false,
         mask: false,
         rsv1: true,
         opcode: 1,
         fin: true
       });
+
+      assert.strictEqual(list[0][0] & 0x40, 0x40);
     });
   });
 

From 30eccee554f1b653239abc8a9de93dd1a1c44179 Mon Sep 17 00:00:00 2001
From: Guy Margalit 
Date: Mon, 27 Feb 2017 10:06:51 +0200
Subject: [PATCH 249/489] [feature] Add "fragments" as possible value for
 `binaryType` (#1018)

---
 .gitignore             |  1 +
 doc/ws.md              |  5 ++-
 lib/Constants.js       |  2 ++
 lib/EventTarget.js     |  5 +--
 lib/Receiver.js        | 44 ++++++++++++++++++-----
 lib/WebSocket.js       | 19 ++++++----
 test/Receiver.test.js  | 82 ++++++++++++++++++++++++++++++++++++++++++
 test/WebSocket.test.js | 57 ++++++++++++++++++++++++++---
 8 files changed, 191 insertions(+), 24 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4b173c692..1e80e9875 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 node_modules/
 coverage/
 npm-debug.log
+.vscode/
diff --git a/doc/ws.md b/doc/ws.md
index cdc6aee05..038ba7651 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -277,7 +277,10 @@ Register an event listener emulating the `EventTarget` interface.
 - {String}
 
 A string indicating the type of binary data being transmitted by the connection.
-This should be either "nodebuffer" or "arraybuffer". Defaults to "nodebuffer".
+This should be either "nodebuffer" or "arraybuffer" or "fragments". Defaults to "nodebuffer".
+Type "fragments" will emit the array of fragments as received from the sender,
+without copyfull concatenation, which is useful for the performance of binary protocols 
+transfering large messages with multiple fragments.
 
 ### websocket.bufferedAmount
 
diff --git a/lib/Constants.js b/lib/Constants.js
index 732df7429..b1ab31fb2 100644
--- a/lib/Constants.js
+++ b/lib/Constants.js
@@ -3,3 +3,5 @@
 exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 exports.EMPTY_BUFFER = Buffer.alloc(0);
 exports.NOOP = () => {};
+
+exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
diff --git a/lib/EventTarget.js b/lib/EventTarget.js
index 9720e4a26..e30b1b3ed 100644
--- a/lib/EventTarget.js
+++ b/lib/EventTarget.js
@@ -28,7 +28,7 @@ class MessageEvent extends Event {
   /**
    * Create a new `MessageEvent`.
    *
-   * @param {(String|Buffer|ArrayBuffer)} data The received data
+   * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
    * @param {Boolean} isBinary Specifies if `data` is binary
    * @param {WebSocket} target A reference to the target to which the event was dispatched
    */
@@ -100,9 +100,6 @@ const EventTarget = {
     if (typeof listener !== 'function') return;
 
     function onMessage (data, flags) {
-      if (flags.binary && this.binaryType === 'arraybuffer') {
-        data = new Uint8Array(data).buffer;
-      }
       listener.call(this, new MessageEvent(data, !!flags.binary, this));
     }
 
diff --git a/lib/Receiver.js b/lib/Receiver.js
index b43c36022..11d9efbf9 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -28,10 +28,12 @@ class Receiver {
    *
    * @param {Object} extensions An object containing the negotiated extensions
    * @param {Number} maxPayload The maximum allowed message length
+   * @param {String} binaryType The type for binary data, see constants.BINARY_TYPES
    */
-  constructor (extensions, maxPayload) {
+  constructor (extensions, maxPayload, binaryType) {
     this.extensions = extensions || {};
     this.maxPayload = maxPayload | 0;
+    this.binaryType = binaryType || constants.BINARY_TYPES[0];
 
     this.bufferedBytes = 0;
     this.buffers = [];
@@ -352,20 +354,28 @@ class Receiver {
    */
   dataMessage () {
     if (this.fin) {
-      const buf = this.fragments.length > 1
-        ? Buffer.concat(this.fragments, this.messageLength)
-        : this.fragments.length === 1
-          ? this.fragments[0]
-          : constants.EMPTY_BUFFER;
-
+      const fragments = this.fragments;
+      const messageLength = this.messageLength;
       this.totalPayloadLength = 0;
-      this.fragments.length = 0;
+      this.fragments = [];
       this.messageLength = 0;
       this.fragmented = 0;
 
       if (this.opcode === 2) {
-        this.onmessage(buf, { masked: this.masked, binary: true });
+        var data;
+
+        if (this.binaryType === 'nodebuffer') {
+          data = bufferFromFragments(fragments, messageLength);
+        } else if (this.binaryType === 'arraybuffer') {
+          data = new Uint8Array(bufferFromFragments(fragments, messageLength)).buffer;
+        } else {
+          data = fragments;
+        }
+
+        this.onmessage(data, { masked: this.masked, binary: true });
       } else {
+        const buf = bufferFromFragments(fragments, messageLength);
+
         if (!isValidUTF8(buf)) {
           this.error(new Error('invalid utf8 sequence'), 1007);
           return;
@@ -509,4 +519,20 @@ class Receiver {
   }
 }
 
+/**
+ * Make a buffer from a list of fragments.
+ * Optimized for the common case of a single fragment in order to
+ * avoid copyfull concat and simply return the fragment buffer.
+ *
+ * @param {Buffer[]} fragments
+ * @param {Number} messageLength
+ * @return {Buffer}
+ * @private
+ */
+function bufferFromFragments (fragments, messageLength) {
+  if (fragments.length === 1) return fragments[0];
+  if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
+  return constants.EMPTY_BUFFER;
+}
+
 module.exports = Receiver;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index c0800adfe..642330ba6 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -53,7 +53,7 @@ class WebSocket extends EventEmitter {
     this.protocol = '';
 
     this._finalize = this.finalize.bind(this);
-    this._binaryType = 'nodebuffer';
+    this._binaryType = constants.BINARY_TYPES[0];
     this._finalizeCalled = false;
     this._closeMessage = null;
     this._closeTimer = null;
@@ -98,10 +98,17 @@ class WebSocket extends EventEmitter {
   }
 
   set binaryType (type) {
-    if (type === 'arraybuffer' || type === 'nodebuffer') {
-      this._binaryType = type;
-    } else {
-      throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"');
+    // silently ignore unsupported types
+    if (constants.BINARY_TYPES.indexOf(type) < 0) {
+      return;
+    }
+
+    this._binaryType = type;
+
+    // update the receiver if already created,
+    // if not then it will take the value once created
+    if (this._receiver) {
+      this._receiver.binaryType = type;
     }
   }
 
@@ -116,7 +123,7 @@ class WebSocket extends EventEmitter {
     socket.setTimeout(0);
     socket.setNoDelay();
 
-    this._receiver = new Receiver(this.extensions, this.maxPayload);
+    this._receiver = new Receiver(this.extensions, this.maxPayload, this.binaryType);
     this._sender = new Sender(socket, this.extensions);
     this._ultron = new Ultron(socket);
     this._socket = socket;
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 1290388af..6d4e07710 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -5,6 +5,7 @@ const crypto = require('crypto');
 
 const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Receiver = require('../lib/Receiver');
+const Sender = require('../lib/Sender');
 const util = require('./hybi-util');
 
 describe('Receiver', function () {
@@ -827,4 +828,85 @@ describe('Receiver', function () {
       });
     });
   });
+
+  it('can emit nodebuffer of fragmented binary message', function (done) {
+    const p = new Receiver();
+    const frags = [
+      crypto.randomBytes(7321),
+      crypto.randomBytes(137),
+      crypto.randomBytes(285787),
+      crypto.randomBytes(3)
+    ];
+
+    p.binaryType = 'nodebuffer';
+    p.onmessage = function (data) {
+      assert.ok(Buffer.isBuffer(data));
+      assert.ok(data.equals(Buffer.concat(frags)));
+      done();
+    };
+
+    addBinaryFragments(p, frags);
+  });
+
+  it('can emit arraybuffer of fragmented binary message', function (done) {
+    const p = new Receiver();
+    const frags = [
+      crypto.randomBytes(19221),
+      crypto.randomBytes(954),
+      crypto.randomBytes(623987)
+    ];
+
+    p.binaryType = 'arraybuffer';
+    p.onmessage = function (data) {
+      assert.ok(data instanceof ArrayBuffer);
+      assert.ok(Buffer.from(data).equals(Buffer.concat(frags)));
+      done();
+    };
+
+    addBinaryFragments(p, frags);
+  });
+
+  it('can emit fragments of fragmented binary message', function (done) {
+    const p = new Receiver();
+    const frags = [
+      crypto.randomBytes(17),
+      crypto.randomBytes(419872),
+      crypto.randomBytes(83),
+      crypto.randomBytes(9928),
+      crypto.randomBytes(1)
+    ];
+
+    p.binaryType = 'fragments';
+    p.onmessage = function (data) {
+      assert.ok(Array.isArray(data));
+      assert.ok(data.length === frags.length);
+      for (let i = 0; i < frags.length; ++i) {
+        assert.ok(Buffer.isBuffer(data[i]));
+        assert.ok(frags[i].equals(data[i]));
+      }
+      done();
+    };
+
+    addBinaryFragments(p, frags);
+  });
 });
+
+/**
+ * Adds a list of binary fragments to the receiver prefixing each one
+ * with info byte and length.
+ *
+ * @param {Receiver} receiver
+ * @param {Buffer[]} frags
+ * @private
+ */
+function addBinaryFragments (receiver, frags) {
+  for (let i = 0; i < frags.length; ++i) {
+    Sender.frame(frags[i], {
+      opcode: i === 0 ? 2 : 0,
+      fin: i + 1 === frags.length,
+      mask: false,
+      rsv1: false,
+      readOnly: true
+    }).forEach(buf => receiver.add(buf));
+  }
+}
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 8c336c9a3..b1bcb0fe4 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -1168,12 +1168,23 @@ describe('WebSocket', function () {
       assert.strictEqual(ws.onopen, listener);
     });
 
-    it('should throw an error when setting an invalid binary type', function () {
+    it('should ignore when setting an invalid binary type', function () {
       const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
 
-      assert.throws(() => {
-        ws.binaryType = 'foo';
-      }, /^SyntaxError: unsupported binaryType: must be either "nodebuffer" or "arraybuffer"$/);
+      ws.binaryType = 'nodebuffer';
+      assert.ok(ws.binaryType === 'nodebuffer');
+      ws.binaryType = 'foo';
+      assert.ok(ws.binaryType === 'nodebuffer');
+      ws.binaryType = 'arraybuffer';
+      assert.ok(ws.binaryType === 'arraybuffer');
+      ws.binaryType = '';
+      assert.ok(ws.binaryType === 'arraybuffer');
+      ws.binaryType = 'fragments';
+      assert.ok(ws.binaryType === 'fragments');
+      ws.binaryType = 'buffer';
+      assert.ok(ws.binaryType === 'fragments');
+      ws.binaryType = 'nodebuffer';
+      assert.ok(ws.binaryType === 'nodebuffer');
     });
 
     it('should work the same as the EventEmitter api', function (done) {
@@ -1420,6 +1431,44 @@ describe('WebSocket', function () {
         };
       });
     });
+
+    it('should allow to update binaryType after receiver created', function (done) {
+      server.createServer(++port, (srv) => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        function testType (binaryType, callback) {
+          const buf = Buffer.from(binaryType);
+          ws.binaryType = binaryType;
+          ws.onmessage = (messageEvent) => {
+            if (binaryType === 'nodebuffer') {
+              assert.ok(Buffer.isBuffer(messageEvent.data));
+              assert.ok(messageEvent.data.equals(buf));
+            } else if (binaryType === 'arraybuffer') {
+              assert.ok(messageEvent.data instanceof ArrayBuffer);
+              assert.ok(Buffer.from(messageEvent.data).equals(buf));
+            } else if (binaryType === 'fragments') {
+              assert.ok(Array.isArray(messageEvent.data));
+              assert.ok(messageEvent.data.length === 1);
+              assert.ok(Buffer.from(messageEvent.data[0]).equals(buf));
+            }
+            callback();
+          };
+          ws.send(buf);
+        }
+
+        ws.onopen =
+          () => testType('nodebuffer',
+            () => testType('arraybuffer',
+              () => testType('fragments',
+                () => {
+                  srv.close(done);
+                  ws.terminate();
+                }
+              )
+            )
+          );
+      });
+    });
   });
 
   describe('ssl', function () {

From 6a734ae17473e28a712661e54808908f8a0bc530 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 27 Feb 2017 10:41:50 +0100
Subject: [PATCH 250/489] [minor] Fix nits

---
 doc/ws.md              |  8 +++---
 lib/Constants.js       |  3 +-
 lib/Receiver.js        | 30 ++++++++++----------
 lib/WebSocket.js       | 16 ++++-------
 test/Receiver.test.js  | 63 +++++++++++++++++++++---------------------
 test/WebSocket.test.js | 47 +++++++++++++++----------------
 6 files changed, 80 insertions(+), 87 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 038ba7651..3cc6c0a9e 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -277,10 +277,10 @@ Register an event listener emulating the `EventTarget` interface.
 - {String}
 
 A string indicating the type of binary data being transmitted by the connection.
-This should be either "nodebuffer" or "arraybuffer" or "fragments". Defaults to "nodebuffer".
-Type "fragments" will emit the array of fragments as received from the sender,
-without copyfull concatenation, which is useful for the performance of binary protocols 
-transfering large messages with multiple fragments.
+This should be one of "nodebuffer", "arraybuffer" or "fragments". Defaults to
+"nodebuffer". Type "fragments" will emit the array of fragments as received from
+the sender, without copyfull concatenation, which is useful for the performance
+of binary protocols transfering large messages with multiple fragments.
 
 ### websocket.bufferedAmount
 
diff --git a/lib/Constants.js b/lib/Constants.js
index b1ab31fb2..ce7ed989f 100644
--- a/lib/Constants.js
+++ b/lib/Constants.js
@@ -1,7 +1,6 @@
 'use strict';
 
+exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
 exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 exports.EMPTY_BUFFER = Buffer.alloc(0);
 exports.NOOP = () => {};
-
-exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 11d9efbf9..906b1b0e9 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -28,12 +28,12 @@ class Receiver {
    *
    * @param {Object} extensions An object containing the negotiated extensions
    * @param {Number} maxPayload The maximum allowed message length
-   * @param {String} binaryType The type for binary data, see constants.BINARY_TYPES
+   * @param {String} binaryType The type for binary data
    */
   constructor (extensions, maxPayload, binaryType) {
+    this.binaryType = binaryType || constants.BINARY_TYPES[0];
     this.extensions = extensions || {};
     this.maxPayload = maxPayload | 0;
-    this.binaryType = binaryType || constants.BINARY_TYPES[0];
 
     this.bufferedBytes = 0;
     this.buffers = [];
@@ -354,27 +354,29 @@ class Receiver {
    */
   dataMessage () {
     if (this.fin) {
-      const fragments = this.fragments;
       const messageLength = this.messageLength;
+      const fragments = this.fragments;
+
       this.totalPayloadLength = 0;
-      this.fragments = [];
       this.messageLength = 0;
       this.fragmented = 0;
+      this.fragments = [];
 
       if (this.opcode === 2) {
         var data;
 
         if (this.binaryType === 'nodebuffer') {
-          data = bufferFromFragments(fragments, messageLength);
+          data = toBuffer(fragments, messageLength);
         } else if (this.binaryType === 'arraybuffer') {
-          data = new Uint8Array(bufferFromFragments(fragments, messageLength)).buffer;
+          const buf = toBuffer(fragments, messageLength);
+          data = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
         } else {
           data = fragments;
         }
 
         this.onmessage(data, { masked: this.masked, binary: true });
       } else {
-        const buf = bufferFromFragments(fragments, messageLength);
+        const buf = toBuffer(fragments, messageLength);
 
         if (!isValidUTF8(buf)) {
           this.error(new Error('invalid utf8 sequence'), 1007);
@@ -519,20 +521,18 @@ class Receiver {
   }
 }
 
+module.exports = Receiver;
+
 /**
- * Make a buffer from a list of fragments.
- * Optimized for the common case of a single fragment in order to
- * avoid copyfull concat and simply return the fragment buffer.
+ * Makes a buffer from a list of fragments.
  *
- * @param {Buffer[]} fragments
- * @param {Number} messageLength
+ * @param {Buffer[]} fragments The list of fragments composing the message
+ * @param {Number} messageLength The length of the message
  * @return {Buffer}
  * @private
  */
-function bufferFromFragments (fragments, messageLength) {
+function toBuffer (fragments, messageLength) {
   if (fragments.length === 1) return fragments[0];
   if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
   return constants.EMPTY_BUFFER;
 }
-
-module.exports = Receiver;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 642330ba6..24bd1bff7 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -52,8 +52,8 @@ class WebSocket extends EventEmitter {
     this.extensions = {};
     this.protocol = '';
 
-    this._finalize = this.finalize.bind(this);
     this._binaryType = constants.BINARY_TYPES[0];
+    this._finalize = this.finalize.bind(this);
     this._finalizeCalled = false;
     this._closeMessage = null;
     this._closeTimer = null;
@@ -98,18 +98,14 @@ class WebSocket extends EventEmitter {
   }
 
   set binaryType (type) {
-    // silently ignore unsupported types
-    if (constants.BINARY_TYPES.indexOf(type) < 0) {
-      return;
-    }
+    if (constants.BINARY_TYPES.indexOf(type) < 0) return;
 
     this._binaryType = type;
 
-    // update the receiver if already created,
-    // if not then it will take the value once created
-    if (this._receiver) {
-      this._receiver.binaryType = type;
-    }
+    //
+    // Allow to change `binaryType` on the fly.
+    //
+    if (this._receiver) this._receiver.binaryType = type;
   }
 
   /**
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 6d4e07710..54948a2c8 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -839,13 +839,21 @@ describe('Receiver', function () {
     ];
 
     p.binaryType = 'nodebuffer';
-    p.onmessage = function (data) {
+    p.onmessage = (data) => {
       assert.ok(Buffer.isBuffer(data));
       assert.ok(data.equals(Buffer.concat(frags)));
       done();
     };
 
-    addBinaryFragments(p, frags);
+    frags.forEach((frag, i) => {
+      Sender.frame(frag, {
+        fin: i === frags.length - 1,
+        opcode: i === 0 ? 2 : 0,
+        readOnly: true,
+        mask: false,
+        rsv1: false
+      }).forEach((buf) => p.add(buf));
+    });
   });
 
   it('can emit arraybuffer of fragmented binary message', function (done) {
@@ -857,13 +865,21 @@ describe('Receiver', function () {
     ];
 
     p.binaryType = 'arraybuffer';
-    p.onmessage = function (data) {
+    p.onmessage = (data) => {
       assert.ok(data instanceof ArrayBuffer);
       assert.ok(Buffer.from(data).equals(Buffer.concat(frags)));
       done();
     };
 
-    addBinaryFragments(p, frags);
+    frags.forEach((frag, i) => {
+      Sender.frame(frag, {
+        fin: i === frags.length - 1,
+        opcode: i === 0 ? 2 : 0,
+        readOnly: true,
+        mask: false,
+        rsv1: false
+      }).forEach((buf) => p.add(buf));
+    });
   });
 
   it('can emit fragments of fragmented binary message', function (done) {
@@ -877,36 +893,19 @@ describe('Receiver', function () {
     ];
 
     p.binaryType = 'fragments';
-    p.onmessage = function (data) {
-      assert.ok(Array.isArray(data));
-      assert.ok(data.length === frags.length);
-      for (let i = 0; i < frags.length; ++i) {
-        assert.ok(Buffer.isBuffer(data[i]));
-        assert.ok(frags[i].equals(data[i]));
-      }
+    p.onmessage = (data) => {
+      assert.deepStrictEqual(data, frags);
       done();
     };
 
-    addBinaryFragments(p, frags);
+    frags.forEach((frag, i) => {
+      Sender.frame(frag, {
+        fin: i === frags.length - 1,
+        opcode: i === 0 ? 2 : 0,
+        readOnly: true,
+        mask: false,
+        rsv1: false
+      }).forEach((buf) => p.add(buf));
+    });
   });
 });
-
-/**
- * Adds a list of binary fragments to the receiver prefixing each one
- * with info byte and length.
- *
- * @param {Receiver} receiver
- * @param {Buffer[]} frags
- * @private
- */
-function addBinaryFragments (receiver, frags) {
-  for (let i = 0; i < frags.length; ++i) {
-    Sender.frame(frags[i], {
-      opcode: i === 0 ? 2 : 0,
-      fin: i + 1 === frags.length,
-      mask: false,
-      rsv1: false,
-      readOnly: true
-    }).forEach(buf => receiver.add(buf));
-  }
-}
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index b1bcb0fe4..867629d82 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -1172,19 +1172,19 @@ describe('WebSocket', function () {
       const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
 
       ws.binaryType = 'nodebuffer';
-      assert.ok(ws.binaryType === 'nodebuffer');
+      assert.strictEqual(ws.binaryType, 'nodebuffer');
       ws.binaryType = 'foo';
-      assert.ok(ws.binaryType === 'nodebuffer');
+      assert.strictEqual(ws.binaryType, 'nodebuffer');
       ws.binaryType = 'arraybuffer';
-      assert.ok(ws.binaryType === 'arraybuffer');
+      assert.strictEqual(ws.binaryType, 'arraybuffer');
       ws.binaryType = '';
-      assert.ok(ws.binaryType === 'arraybuffer');
+      assert.strictEqual(ws.binaryType, 'arraybuffer');
       ws.binaryType = 'fragments';
-      assert.ok(ws.binaryType === 'fragments');
+      assert.strictEqual(ws.binaryType, 'fragments');
       ws.binaryType = 'buffer';
-      assert.ok(ws.binaryType === 'fragments');
+      assert.strictEqual(ws.binaryType, 'fragments');
       ws.binaryType = 'nodebuffer';
-      assert.ok(ws.binaryType === 'nodebuffer');
+      assert.strictEqual(ws.binaryType, 'nodebuffer');
     });
 
     it('should work the same as the EventEmitter api', function (done) {
@@ -1432,13 +1432,14 @@ describe('WebSocket', function () {
       });
     });
 
-    it('should allow to update binaryType after receiver created', function (done) {
+    it('should allow to update binaryType on the fly', function (done) {
       server.createServer(++port, (srv) => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        function testType (binaryType, callback) {
+        function testType (binaryType, next) {
           const buf = Buffer.from(binaryType);
           ws.binaryType = binaryType;
+
           ws.onmessage = (messageEvent) => {
             if (binaryType === 'nodebuffer') {
               assert.ok(Buffer.isBuffer(messageEvent.data));
@@ -1447,26 +1448,24 @@ describe('WebSocket', function () {
               assert.ok(messageEvent.data instanceof ArrayBuffer);
               assert.ok(Buffer.from(messageEvent.data).equals(buf));
             } else if (binaryType === 'fragments') {
-              assert.ok(Array.isArray(messageEvent.data));
-              assert.ok(messageEvent.data.length === 1);
-              assert.ok(Buffer.from(messageEvent.data[0]).equals(buf));
+              assert.deepStrictEqual(messageEvent.data, [buf]);
             }
-            callback();
+            next();
           };
+
           ws.send(buf);
         }
 
-        ws.onopen =
-          () => testType('nodebuffer',
-            () => testType('arraybuffer',
-              () => testType('fragments',
-                () => {
-                  srv.close(done);
-                  ws.terminate();
-                }
-              )
-            )
-          );
+        ws.onopen = () => {
+          testType('nodebuffer', () => {
+            testType('arraybuffer', () => {
+              testType('fragments', () => {
+                srv.close(done);
+                ws.terminate();
+              });
+            });
+          });
+        };
       });
     });
   });

From 625e9bf76acbe11bf2be13951c8fcca7733477ac Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 27 Feb 2017 14:51:23 +0100
Subject: [PATCH 251/489] [perf] Optimize for cases where buffer is not a
 subarray

---
 lib/Receiver.js | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/lib/Receiver.js b/lib/Receiver.js
index 906b1b0e9..5f74fe246 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -368,8 +368,7 @@ class Receiver {
         if (this.binaryType === 'nodebuffer') {
           data = toBuffer(fragments, messageLength);
         } else if (this.binaryType === 'arraybuffer') {
-          const buf = toBuffer(fragments, messageLength);
-          data = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
+          data = toArrayBuffer(toBuffer(fragments, messageLength));
         } else {
           data = fragments;
         }
@@ -536,3 +535,17 @@ function toBuffer (fragments, messageLength) {
   if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
   return constants.EMPTY_BUFFER;
 }
+
+/**
+ * Converts a buffer to an `ArrayBuffer`.
+ *
+ * @param {Buffer} The buffer to convert
+ * @return {ArrayBuffer} Converted buffer
+ */
+function toArrayBuffer (buf) {
+  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
+    return buf.buffer;
+  }
+
+  return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
+}

From ae42166ec5966e4924d5995c84514d58d206a7ea Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 28 Feb 2017 10:17:19 +0100
Subject: [PATCH 252/489] [dist] 2.2.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a3dca315a..6a4b08967 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.1.0",
+  "version": "2.2.0",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 703adf85ec732d1ae8fb9bea9f4cf02436942916 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 1 Mar 2017 07:38:05 +0100
Subject: [PATCH 253/489] chore(package): update eslint-plugin-promise to
 version 3.5.0 (#1025)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6a4b08967..9abfbdad7 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
     "eslint": "~3.16.0",
     "eslint-config-semistandard": "~7.0.0",
     "eslint-config-standard": "~6.2.1",
-    "eslint-plugin-promise": "~3.4.0",
+    "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.0.1",
     "istanbul": "~0.4.5",
     "mocha": "~3.2.0",

From f9b2bdfb7f82c7f49b48261ab6479ee40eb7e5fc Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 1 Mar 2017 07:48:48 +0100
Subject: [PATCH 254/489] [lint] Comply with the new standard rules

---
 bench/speed.js           | 2 +-
 lib/PerMessageDeflate.js | 2 +-
 lib/Receiver.js          | 2 +-
 lib/Sender.js            | 2 +-
 lib/WebSocketServer.js   | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/bench/speed.js b/bench/speed.js
index 40c3f30cf..55047d5d8 100644
--- a/bench/speed.js
+++ b/bench/speed.js
@@ -69,7 +69,7 @@ if (cluster.isMaster) {
       if (++roundtrip !== roundtrips) return ws.send(data, { binary: useBinary });
 
       var elapsed = process.hrtime(time);
-      elapsed = elapsed[0] * 1e9 + elapsed[1];
+      elapsed = (elapsed[0] * 1e9) + elapsed[1];
 
       console.log(
         '%d roundtrips of %s %s data:\t%ss\t%s',
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 92a26525f..218bcdeb0 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -261,7 +261,7 @@ class PerMessageDeflate {
       this._inflate.writeInProgress = false;
 
       if (
-        fin && this.params[`${endpoint}_no_context_takeover`] ||
+        (fin && this.params[`${endpoint}_no_context_takeover`]) ||
         this._inflate.pendingClose
       ) {
         this._inflate.close();
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 5f74fe246..670e72e78 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -272,7 +272,7 @@ class Receiver {
       return;
     }
 
-    this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true);
+    this.payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true);
     this.haveLength();
   }
 
diff --git a/lib/Sender.js b/lib/Sender.js
index d36c7ba5a..0df22dbb8 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -50,7 +50,7 @@ class Sender {
    * @public
    */
   static frame (data, options) {
-    const merge = data.length < 1024 || options.mask && options.readOnly;
+    const merge = data.length < 1024 || (options.mask && options.readOnly);
     var offset = options.mask ? 6 : 2;
     var payloadLength = data.length;
 
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index 1499a2cdc..5170375ba 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -153,7 +153,7 @@ class WebSocketServer extends EventEmitter {
 
     if (
       req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' ||
-      !req.headers['sec-websocket-key'] || version !== 8 && version !== 13 ||
+      !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
       !this.shouldHandle(req)
     ) {
       return abortConnection(socket, 400);

From 2aec9a4abe6f62116d09ab01a873ca486b39d2ac Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 1 Mar 2017 08:56:42 +0100
Subject: [PATCH 255/489] [lint] Remove eslint-config-semistandard dev
 dependency

---
 .eslintrc.yaml | 7 ++++++-
 package.json   | 1 -
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index 5b53411e0..2524511f9 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -1,3 +1,8 @@
-extends: semistandard
+extends: standard
 env:
   mocha: true
+rules:
+  no-extra-semi: error
+  semi:
+    - error
+    - always
diff --git a/package.json b/package.json
index 9abfbdad7..95483471c 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~2.0.0",
     "eslint": "~3.16.0",
-    "eslint-config-semistandard": "~7.0.0",
     "eslint-config-standard": "~6.2.1",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.0.1",

From 944b788c2b01ac2ea874f48fa4b143be37deeeb4 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 1 Mar 2017 09:16:01 +0100
Subject: [PATCH 256/489] chore(package): update eslint-config-standard to
 version 7.0.0 (#1024)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 95483471c..c47da7ccb 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~2.0.0",
     "eslint": "~3.16.0",
-    "eslint-config-standard": "~6.2.1",
+    "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.0.1",
     "istanbul": "~0.4.5",

From a70c6d0a2b0f65583f79379835cd6d952a51262d Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Thu, 2 Mar 2017 09:05:14 +0100
Subject: [PATCH 257/489] chore(package): update eslint-plugin-standard to
 version 2.1.0 (#1027)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c47da7ccb..1aed2bd8d 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
     "eslint": "~3.16.0",
     "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",
-    "eslint-plugin-standard": "~2.0.1",
+    "eslint-plugin-standard": "~2.1.0",
     "istanbul": "~0.4.5",
     "mocha": "~3.2.0",
     "utf-8-validate": "~3.0.0"

From 4ae630af7409ba830a043dcf7eff24f655923aa6 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 2 Mar 2017 15:43:30 +0100
Subject: [PATCH 258/489] [perf] Avoid using `Buffer.concat()` (#1026)

---
 lib/BufferUtil.js        | 41 ++++++++++++++++-------------
 lib/PerMessageDeflate.js | 56 +++++++++++++++++++++++-----------------
 lib/Receiver.js          |  2 +-
 3 files changed, 56 insertions(+), 43 deletions(-)

diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index 526654d42..bb1004672 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -6,27 +6,32 @@
  * MIT Licensed
  */
 
+/**
+ * Merges an array of buffers into a new buffer.
+ *
+ * @param {Buffer[]} list The array of buffers to concat
+ * @param {Number} totalLength The total length of buffers in the list
+ * @return {Buffer} The resulting buffer
+ * @public
+ */
+const concat = (list, totalLength) => {
+  const target = Buffer.allocUnsafe(totalLength);
+  var offset = 0;
+
+  for (var i = 0; i < list.length; i++) {
+    const buf = list[i];
+    buf.copy(target, offset);
+    offset += buf.length;
+  }
+
+  return target;
+};
+
 try {
   const bufferUtil = require('bufferutil');
 
-  module.exports = bufferUtil.BufferUtil || bufferUtil;
+  module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
 } catch (e) {
-  /**
-   * Merges an array of buffers into a target buffer.
-   *
-   * @param {Buffer} target The target buffer
-   * @param {Buffer[]} buffers The array of buffers to merge
-   * @public
-   */
-  const merge = (target, buffers) => {
-    var offset = 0;
-    for (var i = 0; i < buffers.length; i++) {
-      const buf = buffers[i];
-      buf.copy(target, offset);
-      offset += buf.length;
-    }
-  };
-
   /**
    * Masks a buffer using the given mask.
    *
@@ -58,5 +63,5 @@ try {
     }
   };
 
-  module.exports = { merge, mask, unmask };
+  module.exports = { concat, mask, unmask };
 }
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 218bcdeb0..68e2ed384 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -2,6 +2,8 @@
 
 const zlib = require('zlib');
 
+const bufferUtil = require('./BufferUtil');
+
 const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
 const DEFAULT_WINDOW_BITS = 15;
 const DEFAULT_MEM_LEVEL = 8;
@@ -9,7 +11,7 @@ const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
 const EMPTY_BLOCK = Buffer.from([0x00]);
 
 /**
- * Per-message Compression Extensions implementation
+ * Per-message Deflate implementation.
  */
 class PerMessageDeflate {
   constructor (options, isServer, maxPayload) {
@@ -22,12 +24,15 @@ class PerMessageDeflate {
     this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold;
   }
 
+  static get extensionName () {
+    return 'permessage-deflate';
+  }
+
   /**
    * Create extension parameters offer
    *
-   * @api public
+   * @public
    */
-
   offer () {
     var params = {};
     if (this._options.serverNoContextTakeover) {
@@ -50,7 +55,7 @@ class PerMessageDeflate {
   /**
    * Accept extension offer
    *
-   * @api public
+   * @public
    */
   accept (paramsList) {
     paramsList = this.normalizeParams(paramsList);
@@ -69,7 +74,7 @@ class PerMessageDeflate {
   /**
    * Releases all resources used by the extension
    *
-   * @api public
+   * @public
    */
   cleanup () {
     if (this._inflate) {
@@ -93,9 +98,8 @@ class PerMessageDeflate {
   /**
    * Accept extension offer from client
    *
-   * @api private
+   * @private
    */
-
   acceptAsServer (paramsList) {
     var accepted = {};
     var result = paramsList.some((params) => {
@@ -147,9 +151,8 @@ class PerMessageDeflate {
   /**
    * Accept extension response from server
    *
-   * @api privaye
+   * @private
    */
-
   acceptAsClient (paramsList) {
     var params = paramsList[0];
     if (this._options.clientNoContextTakeover != null) {
@@ -172,9 +175,8 @@ class PerMessageDeflate {
   /**
    * Normalize extensions parameters
    *
-   * @api private
+   * @private
    */
-
   normalizeParams (paramsList) {
     return paramsList.map((params) => {
       Object.keys(params).forEach((key) => {
@@ -276,26 +278,25 @@ class PerMessageDeflate {
     this._inflate.flush(() => {
       cleanup();
       if (err) callback(err);
-      else callback(null, Buffer.concat(buffers, totalLength));
+      else callback(null, bufferUtil.concat(buffers, totalLength));
     });
   }
 
   /**
    * Compress message
    *
-   * @api public
+   * @public
    */
-
   compress (data, fin, callback) {
     if (!data || data.length === 0) {
       process.nextTick(callback, null, EMPTY_BLOCK);
       return;
     }
 
-    var endpoint = this._isServer ? 'server' : 'client';
+    const endpoint = this._isServer ? 'server' : 'client';
 
     if (!this._deflate) {
-      var maxWindowBits = this.params[endpoint + '_max_window_bits'];
+      const maxWindowBits = this.params[`${endpoint}_max_window_bits`];
       this._deflate = zlib.createDeflateRaw({
         flush: zlib.Z_SYNC_FLUSH,
         windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS,
@@ -304,19 +305,30 @@ class PerMessageDeflate {
     }
     this._deflate.writeInProgress = true;
 
+    var totalLength = 0;
     const buffers = [];
 
-    const onData = (data) => buffers.push(data);
+    const onData = (data) => {
+      totalLength += data.length;
+      buffers.push(data);
+    };
+
     const onError = (err) => {
       cleanup();
       callback(err);
     };
+
     const cleanup = () => {
       if (!this._deflate) return;
+
       this._deflate.removeListener('error', onError);
       this._deflate.removeListener('data', onData);
       this._deflate.writeInProgress = false;
-      if ((fin && this.params[endpoint + '_no_context_takeover']) || this._deflate.pendingClose) {
+
+      if (
+        (fin && this.params[`${endpoint}_no_context_takeover`]) ||
+        this._deflate.pendingClose
+      ) {
         this._deflate.close();
         this._deflate = null;
       }
@@ -326,15 +338,11 @@ class PerMessageDeflate {
     this._deflate.write(data);
     this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
       cleanup();
-      var data = Buffer.concat(buffers);
-      if (fin) {
-        data = data.slice(0, data.length - 4);
-      }
+      var data = bufferUtil.concat(buffers, totalLength);
+      if (fin) data = data.slice(0, data.length - 4);
       callback(null, data);
     });
   }
 }
 
-PerMessageDeflate.extensionName = 'permessage-deflate';
-
 module.exports = PerMessageDeflate;
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 670e72e78..54cb409b3 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -532,7 +532,7 @@ module.exports = Receiver;
  */
 function toBuffer (fragments, messageLength) {
   if (fragments.length === 1) return fragments[0];
-  if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
+  if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
   return constants.EMPTY_BUFFER;
 }
 

From e13ef4a603db913e02c9375e3b1393f9f76ef8ff Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Fri, 3 Mar 2017 12:34:22 +0100
Subject: [PATCH 259/489] chore(package): update bufferutil to version 3.0.0
 (#1029)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1aed2bd8d..97d8be634 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
   },
   "devDependencies": {
     "benchmark": "~2.1.2",
-    "bufferutil": "~2.0.0",
+    "bufferutil": "~3.0.0",
     "eslint": "~3.16.0",
     "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",

From 4a605b9853a36ce53332f3cad87127b47d4d1030 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 4 Mar 2017 07:20:44 +0100
Subject: [PATCH 260/489] chore(package): update eslint to version 3.17.0
 (#1030)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 97d8be634..de6bc088b 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
-    "eslint": "~3.16.0",
+    "eslint": "~3.17.0",
     "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.1.0",

From e057bfc8274e4b64fc6752cfddcfc634ebddd87e Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 8 Mar 2017 14:31:54 +0100
Subject: [PATCH 261/489] [example] Avoid using deprecated APIs

---
 examples/fileapi/server.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js
index 380ce152d..4202818a0 100644
--- a/examples/fileapi/server.js
+++ b/examples/fileapi/server.js
@@ -34,8 +34,8 @@ function makePathForFile (filePath, prefix, cb) {
     if (error) return cb(error);
     if (pieces.length === 0) return cb(null, incrementalPath);
     incrementalPath += '/' + pieces.shift();
-    fs.exists(incrementalPath, function (exists) {
-      if (!exists) fs.mkdir(incrementalPath, step);
+    fs.access(incrementalPath, function (err) {
+      if (err) fs.mkdir(incrementalPath, step);
       else process.nextTick(step);
     });
   }

From 05970f6a11fd153dd89559ed95a54fd8d1375c48 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 8 Mar 2017 14:42:46 +0100
Subject: [PATCH 262/489] [pkg] Add missing dependencies for
 eslint-config-standard@8

---
 package.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/package.json b/package.json
index de6bc088b..b2057b9be 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,8 @@
     "bufferutil": "~3.0.0",
     "eslint": "~3.17.0",
     "eslint-config-standard": "~7.0.0",
+    "eslint-plugin-import": "~2.2.0",
+    "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.1.0",
     "istanbul": "~0.4.5",

From fecc4f5b5b96bd98318d5c962eac6f0d9d3d37fc Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 8 Mar 2017 15:00:59 +0100
Subject: [PATCH 263/489] chore(package): update eslint-config-standard to
 version 8.0.0-beta.1 (#1035)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b2057b9be..551322340 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.17.0",
-    "eslint-config-standard": "~7.0.0",
+    "eslint-config-standard": "~8.0.0-beta.1",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From d29f30682e3330609c27d35053785373530aaa26 Mon Sep 17 00:00:00 2001
From: Dorian 
Date: Fri, 10 Mar 2017 00:27:42 -0800
Subject: [PATCH 264/489] [minor] Improve error message for unsupported
 protocol version (#1036)

---
 lib/WebSocket.js       | 10 +++++++---
 test/WebSocket.test.js |  2 +-
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 24bd1bff7..f08a96bd7 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -21,7 +21,8 @@ const Receiver = require('./Receiver');
 const Sender = require('./Sender');
 
 const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
-const protocolVersion = 13;
+const protocolVersions = [8, 13];
+const protocolVersion = protocolVersions[1];
 
 /**
  * Class representing a WebSocket.
@@ -527,8 +528,11 @@ function initAsClient (address, protocols, options) {
     ca: null
   }, options);
 
-  if (options.protocolVersion !== 8 && options.protocolVersion !== 13) {
-    throw new Error('unsupported protocol version');
+  if (protocolVersions.indexOf(options.protocolVersion) === -1) {
+    throw new Error(
+      `unsupported protocol version: ${options.protocolVersion} ` +
+      `(supported versions: ${protocolVersions.join(', ')})`
+    );
   }
 
   this.protocolVersion = options.protocolVersion;
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 867629d82..ff01c07be 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -51,7 +51,7 @@ describe('WebSocket', function () {
 
       assert.throws(
         () => new WebSocket('ws://localhost', options),
-        /^Error: unsupported protocol version$/
+        /^Error: unsupported protocol version: 1000 \(supported versions: 8, 13\)$/
       );
     });
 

From 72170d455c38637ef5702c5d8270e72c55313181 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Fri, 10 Mar 2017 09:34:06 +0100
Subject: [PATCH 265/489] [codestyle] Fix nits

---
 lib/WebSocket.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index f08a96bd7..0f8a2d3ae 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -20,9 +20,8 @@ const constants = require('./Constants');
 const Receiver = require('./Receiver');
 const Sender = require('./Sender');
 
-const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
 const protocolVersions = [8, 13];
-const protocolVersion = protocolVersions[1];
+const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
 
 /**
  * Class representing a WebSocket.
@@ -505,15 +504,15 @@ function initAsServerClient (req, socket, head, options) {
  */
 function initAsClient (address, protocols, options) {
   options = Object.assign({
+    protocolVersion: protocolVersions[1],
     protocol: protocols.join(','),
     perMessageDeflate: true,
     localAddress: null,
-    protocolVersion,
     headers: null,
+    family: null,
     origin: null,
     agent: null,
     host: null,
-    family: null,
 
     //
     // SSL options.

From 738e07f337fd601ba8ab0c0afc50f79296920fc8 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 11 Mar 2017 15:06:19 +0100
Subject: [PATCH 266/489] [fix] Make `WebSocket#terminate()` destroy the socket
 (#1033)

---
 doc/ws.md        |  4 ++--
 lib/WebSocket.js | 19 ++++---------------
 2 files changed, 6 insertions(+), 17 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 3cc6c0a9e..8893edf47 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -406,11 +406,11 @@ Resume the socket
 - `callback` {Function} An optional callback which is invoked when `data` is
   written out.
 
-Sends `data` through the connection.
+Send `data` through the connection.
 
 ### websocket.terminate()
 
-Send a FIN packet to the other peer.
+Forcibly close the connection.
 
 ### websocket.upgradeReq
 
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 0f8a2d3ae..c4ed744d5 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -276,7 +276,7 @@ class WebSocket extends EventEmitter {
     }
 
     if (this.readyState === WebSocket.CLOSING) {
-      if (this._closeCode) this.terminate();
+      if (this._closeCode && this._socket) this._socket.end();
       return;
     }
 
@@ -284,9 +284,8 @@ class WebSocket extends EventEmitter {
     this._sender.close(code, data, !this._isServer, (err) => {
       if (err) this.emit('error', err);
 
-      if (this._closeCode) {
-        this.terminate();
-      } else {
+      if (this._socket) {
+        if (this._closeCode) this._socket.end();
         //
         // Ensure that the connection is cleaned up even when the closing
         // handshake fails.
@@ -391,17 +390,7 @@ class WebSocket extends EventEmitter {
       return;
     }
 
-    if (this._socket) {
-      this.readyState = WebSocket.CLOSING;
-      this._socket.end();
-
-      //
-      // Add a timeout to ensure that the connection is completely cleaned up
-      // within 30 seconds, even if the other peer does not send a FIN packet.
-      //
-      clearTimeout(this._closeTimer);
-      this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
-    }
+    this.finalize(true);
   }
 }
 

From 08eb82725afce71f495f82e5f24d3e375a1670d2 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 13 Mar 2017 07:56:41 +0100
Subject: [PATCH 267/489] [dist] 2.2.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 551322340..03370cf1e 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.2.0",
+  "version": "2.2.1",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 1b3810ed95d2d2572314b3c361e97eaab73c41ef Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 14 Mar 2017 10:53:46 +0100
Subject: [PATCH 268/489] [minor] Add missing JSDoc comments

---
 lib/Extensions.js        |  64 ++++++++++++------------
 lib/PerMessageDeflate.js | 103 ++++++++++++++++++++++++++-------------
 lib/WebSocket.js         |   2 +-
 3 files changed, 100 insertions(+), 69 deletions(-)

diff --git a/lib/Extensions.js b/lib/Extensions.js
index 036c71382..a91910eb2 100644
--- a/lib/Extensions.js
+++ b/lib/Extensions.js
@@ -1,31 +1,28 @@
 'use strict';
 
 /**
- * Module exports.
+ * Parse the `Sec-WebSocket-Extensions` header into an object.
+ *
+ * @param {String} value field value of the header
+ * @return {Object} The parsed object
+ * @public
  */
-
-exports.parse = parse;
-exports.format = format;
-
-/**
- * Parse extensions header value
- */
-
-function parse (value) {
+const parse = (value) => {
   value = value || '';
 
-  var extensions = {};
+  const extensions = {};
 
-  value.split(',').forEach(function (v) {
-    var params = v.split(';');
-    var token = params.shift().trim();
-    var paramsList = extensions[token] = extensions[token] || [];
-    var parsedParams = {};
+  value.split(',').forEach((v) => {
+    const params = v.split(';');
+    const token = params.shift().trim();
+    const paramsList = extensions[token] = extensions[token] || [];
+    const parsedParams = {};
 
-    params.forEach(function (param) {
-      var parts = param.trim().split('=');
-      var key = parts[0];
+    params.forEach((param) => {
+      const parts = param.trim().split('=');
+      const key = parts[0];
       var value = parts[1];
+
       if (value === undefined) {
         value = true;
       } else {
@@ -44,26 +41,27 @@ function parse (value) {
   });
 
   return extensions;
-}
+};
 
 /**
- * Format extensions header value
+ * Serialize a parsed `Sec-WebSocket-Extensions` header to a string.
+ *
+ * @param {Object} value The object to format
+ * @return {String} A string representing the given value
+ * @public
  */
-
-function format (value) {
-  return Object.keys(value).map(function (token) {
+const format = (value) => {
+  return Object.keys(value).map((token) => {
     var paramsList = value[token];
-    if (!Array.isArray(paramsList)) {
-      paramsList = [paramsList];
-    }
-    return paramsList.map(function (params) {
-      return [token].concat(Object.keys(params).map(function (k) {
+    if (!Array.isArray(paramsList)) paramsList = [paramsList];
+    return paramsList.map((params) => {
+      return [token].concat(Object.keys(params).map((k) => {
         var p = params[k];
         if (!Array.isArray(p)) p = [p];
-        return p.map(function (v) {
-          return v === true ? k : k + '=' + v;
-        }).join('; ');
+        return p.map((v) => v === true ? k : `${k}=${v}`).join('; ');
       })).join('; ');
     }).join(', ');
   }).join(', ');
-}
+};
+
+module.exports = { format, parse };
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 68e2ed384..192d530ef 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -29,12 +29,14 @@ class PerMessageDeflate {
   }
 
   /**
-   * Create extension parameters offer
+   * Create extension parameters offer.
    *
+   * @return {Object} Extension parameters
    * @public
    */
   offer () {
-    var params = {};
+    const params = {};
+
     if (this._options.serverNoContextTakeover) {
       params.server_no_context_takeover = true;
     }
@@ -49,12 +51,15 @@ class PerMessageDeflate {
     } else if (this._options.clientMaxWindowBits == null) {
       params.client_max_window_bits = true;
     }
+
     return params;
   }
 
   /**
-   * Accept extension offer
+   * Accept extension offer.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Object} Accepted configuration
    * @public
    */
   accept (paramsList) {
@@ -72,7 +77,7 @@ class PerMessageDeflate {
   }
 
   /**
-   * Releases all resources used by the extension
+   * Releases all resources used by the extension.
    *
    * @public
    */
@@ -96,36 +101,45 @@ class PerMessageDeflate {
   }
 
   /**
-   * Accept extension offer from client
+   * Accept extension offer from client.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Object} Accepted configuration
    * @private
    */
   acceptAsServer (paramsList) {
-    var accepted = {};
-    var result = paramsList.some((params) => {
-      accepted = {};
-      if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) {
-        return;
-      }
-      if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) {
-        return;
-      }
-      if (typeof this._options.serverMaxWindowBits === 'number' &&
-          typeof params.server_max_window_bits === 'number' &&
-          this._options.serverMaxWindowBits > params.server_max_window_bits) {
-        return;
-      }
-      if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) {
+    const accepted = {};
+    const result = paramsList.some((params) => {
+      if ((
+        this._options.serverNoContextTakeover === false &&
+        params.server_no_context_takeover
+      ) || (
+        this._options.serverMaxWindowBits === false &&
+        params.server_max_window_bits
+      ) || (
+        typeof this._options.serverMaxWindowBits === 'number' &&
+        typeof params.server_max_window_bits === 'number' &&
+        this._options.serverMaxWindowBits > params.server_max_window_bits
+      ) || (
+        typeof this._options.clientMaxWindowBits === 'number' &&
+        !params.client_max_window_bits
+      )) {
         return;
       }
 
-      if (this._options.serverNoContextTakeover || params.server_no_context_takeover) {
+      if (
+        this._options.serverNoContextTakeover ||
+        params.server_no_context_takeover
+      ) {
         accepted.server_no_context_takeover = true;
       }
       if (this._options.clientNoContextTakeover) {
         accepted.client_no_context_takeover = true;
       }
-      if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) {
+      if (
+        this._options.clientNoContextTakeover !== false &&
+        params.client_no_context_takeover
+      ) {
         accepted.client_no_context_takeover = true;
       }
       if (typeof this._options.serverMaxWindowBits === 'number') {
@@ -135,46 +149,62 @@ class PerMessageDeflate {
       }
       if (typeof this._options.clientMaxWindowBits === 'number') {
         accepted.client_max_window_bits = this._options.clientMaxWindowBits;
-      } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') {
+      } else if (
+        this._options.clientMaxWindowBits !== false &&
+        typeof params.client_max_window_bits === 'number'
+      ) {
         accepted.client_max_window_bits = params.client_max_window_bits;
       }
       return true;
     });
 
-    if (!result) {
-      throw new Error(`Doesn't support the offered configuration`);
-    }
+    if (!result) throw new Error(`Doesn't support the offered configuration`);
 
     return accepted;
   }
 
   /**
-   * Accept extension response from server
+   * Accept extension response from server.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Object} Accepted configuration
    * @private
    */
   acceptAsClient (paramsList) {
-    var params = paramsList[0];
+    const params = paramsList[0];
+
     if (this._options.clientNoContextTakeover != null) {
-      if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) {
+      if (
+        this._options.clientNoContextTakeover === false &&
+        params.client_no_context_takeover
+      ) {
         throw new Error('Invalid value for "client_no_context_takeover"');
       }
     }
     if (this._options.clientMaxWindowBits != null) {
-      if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) {
+      if (
+        this._options.clientMaxWindowBits === false &&
+        params.client_max_window_bits
+      ) {
         throw new Error('Invalid value for "client_max_window_bits"');
       }
-      if (typeof this._options.clientMaxWindowBits === 'number' &&
-          (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) {
+      if (
+        typeof this._options.clientMaxWindowBits === 'number' && (
+        !params.client_max_window_bits ||
+        params.client_max_window_bits > this._options.clientMaxWindowBits
+      )) {
         throw new Error('Invalid value for "client_max_window_bits"');
       }
     }
+
     return params;
   }
 
   /**
-   * Normalize extensions parameters
+   * Normalize extensions parameters.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Array} Normalized extensions parameters
    * @private
    */
   normalizeParams (paramsList) {
@@ -182,7 +212,7 @@ class PerMessageDeflate {
       Object.keys(params).forEach((key) => {
         var value = params[key];
         if (value.length > 1) {
-          throw new Error('Multiple extension parameters for ' + key);
+          throw new Error(`Multiple extension parameters for ${key}`);
         }
 
         value = value[0];
@@ -283,8 +313,11 @@ class PerMessageDeflate {
   }
 
   /**
-   * Compress message
+   * Compress data.
    *
+   * @param {Buffer} data Data to compress
+   * @param {Boolean} fin Specifies whether or not this is the last fragment
+   * @param {Function} callback Callback
    * @public
    */
   compress (data, fin, callback) {
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index c4ed744d5..a66c12814 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -375,7 +375,7 @@ class WebSocket extends EventEmitter {
   }
 
   /**
-   * Half-close the socket sending a FIN packet.
+   * Forcibly close the connection.
    *
    * @public
    */

From 3e6c8d3b0d93fef8efceb75ca9b7512a581a4067 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 16 Mar 2017 20:54:05 +0100
Subject: [PATCH 269/489] [test] Fix origin header in integration tests

---
 test/WebSocket.integration.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js
index 90d5adcfb..08810cfc9 100644
--- a/test/WebSocket.integration.js
+++ b/test/WebSocket.integration.js
@@ -7,7 +7,7 @@ const WebSocket = require('..');
 describe('WebSocket', function () {
   it('communicates successfully with echo service (ws)', function (done) {
     const ws = new WebSocket('ws://echo.websocket.org/', {
-      origin: 'ws://echo.websocket.org',
+      origin: 'http://www.websocket.org',
       protocolVersion: 13
     });
     const str = Date.now().toString();
@@ -28,7 +28,7 @@ describe('WebSocket', function () {
 
   it('communicates successfully with echo service (wss)', function (done) {
     const ws = new WebSocket('wss://echo.websocket.org/', {
-      origin: 'wss://echo.websocket.org',
+      origin: 'https://www.websocket.org',
       protocolVersion: 13
     });
     const str = Date.now().toString();

From 2ace70d913a7dc0fc741147deb559dda914d1498 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 18 Mar 2017 07:32:10 +0100
Subject: [PATCH 270/489] chore(package): update eslint to version 3.18.0
 (#1048)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 03370cf1e..bc0bf88a5 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
-    "eslint": "~3.17.0",
+    "eslint": "~3.18.0",
     "eslint-config-standard": "~8.0.0-beta.1",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",

From 35119fdeeb3310e7ad8b4d34520591fa7acb53a8 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Mar 2017 15:28:27 +0100
Subject: [PATCH 271/489] [test] Delete test/testserver.js

---
 test/WebSocket.test.js | 665 ++++++++++++++++++++++-------------------
 test/testserver.js     | 153 ----------
 2 files changed, 354 insertions(+), 464 deletions(-)
 delete mode 100644 test/testserver.js

diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index ff01c07be..fd065d67e 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -8,7 +8,7 @@ const https = require('https');
 const http = require('http');
 const fs = require('fs');
 
-const server = require('./testserver');
+const constants = require('../lib/Constants');
 const WebSocket = require('..');
 
 const WebSocketServer = WebSocket.Server;
@@ -181,43 +181,40 @@ describe('WebSocket', function () {
     });
 
     describe('Custom headers', function () {
+      const server = http.createServer();
+
+      beforeEach((done) => server.listen(++port, done));
+      afterEach((done) => server.close(done));
+
       it('request has an authorization header', function (done) {
-        const server = http.createServer();
         const wss = new WebSocketServer({ server });
         const auth = 'test:testpass';
 
-        server.listen(++port, () => {
-          const ws = new WebSocket(`ws://${auth}@localhost:${port}`);
-        });
-
-        server.on('upgrade', (req, socket, head) => {
+        server.once('upgrade', (req, socket, head) => {
           assert.ok(req.headers.authorization);
           assert.strictEqual(
             req.headers.authorization,
             `Basic ${Buffer.from(auth).toString('base64')}`
           );
 
-          wss.close();
-          server.close(done);
+          wss.close(done);
         });
+
+        const ws = new WebSocket(`ws://${auth}@localhost:${port}`);
       });
 
       it('accepts custom headers', function (done) {
-        const server = http.createServer();
         const wss = new WebSocketServer({ server });
 
-        server.on('upgrade', (req, socket, head) => {
+        server.once('upgrade', (req, socket, head) => {
           assert.ok(req.headers.cookie);
           assert.strictEqual(req.headers.cookie, 'foo=bar');
 
-          wss.close();
-          server.close(done);
+          wss.close(done);
         });
 
-        server.listen(++port, () => {
-          const ws = new WebSocket(`ws://localhost:${port}`, {
-            headers: { 'Cookie': 'foo=bar' }
-          });
+        const ws = new WebSocket(`ws://localhost:${port}`, {
+          headers: { 'Cookie': 'foo=bar' }
         });
       });
     });
@@ -232,7 +229,7 @@ describe('WebSocket', function () {
       });
 
       it('set to open once connection is established', function (done) {
-        server.createServer(++port, (srv) => {
+        const wss = new WebSocketServer({ port: ++port }, () => {
           const ws = new WebSocket(`ws://localhost:${port}`);
 
           ws.on('open', () => {
@@ -240,17 +237,17 @@ describe('WebSocket', function () {
             ws.close();
           });
 
-          ws.on('close', () => srv.close(done));
+          ws.on('close', () => wss.close(done));
         });
       });
 
       it('set to closed once connection is closed', function (done) {
-        server.createServer(++port, (srv) => {
+        const wss = new WebSocketServer({ port: ++port }, () => {
           const ws = new WebSocket(`ws://localhost:${port}`);
 
           ws.on('close', () => {
             assert.strictEqual(ws.readyState, WebSocket.CLOSED);
-            srv.close(done);
+            wss.close(done);
           });
 
           ws.on('open', () => ws.close(1001));
@@ -258,12 +255,12 @@ describe('WebSocket', function () {
       });
 
       it('set to closed once connection is terminated', function (done) {
-        server.createServer(++port, (srv) => {
+        const wss = new WebSocketServer({ port: ++port }, () => {
           const ws = new WebSocket(`ws://localhost:${port}`);
 
           ws.on('close', () => {
             assert.strictEqual(ws.readyState, WebSocket.CLOSED);
-            srv.close(done);
+            wss.close(done);
           });
 
           ws.on('open', () => ws.terminate());
@@ -325,66 +322,130 @@ describe('WebSocket', function () {
   });
 
   describe('connection establishing', function () {
+    const server = http.createServer();
+
+    beforeEach((done) => server.listen(++port, done));
+    afterEach((done) => server.close(done));
+
     it('invalid server key is denied', function (done) {
-      server.createServer(++port, server.handlers.invalidKey, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.on('end', () => socket.end());
+        socket.write(
+          'HTTP/1.1 101 Switching Protocols\r\n' +
+          'Upgrade: websocket\r\n' +
+          'Connection: Upgrade\r\n' +
+          'Sec-WebSocket-Accept: CxYS6+NgJSBG74mdgLvGscRvpns=\r\n' +
+          '\r\n'
+        );
+      });
+
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => srv.close(done));
+      ws.on('error', (err) => {
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.message, 'invalid server key');
+        done();
       });
     });
 
     it('close event is raised when server closes connection', function (done) {
-      server.createServer(++port, server.handlers.closeAfterConnect, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        const key = crypto.createHash('sha1')
+          .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
+          .digest('base64');
+
+        socket.end(
+          'HTTP/1.1 101 Switching Protocols\r\n' +
+          'Upgrade: websocket\r\n' +
+          'Connection: Upgrade\r\n' +
+          `Sec-WebSocket-Accept: ${key}\r\n` +
+          '\r\n'
+        );
+      });
+
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('close', () => srv.close(done));
+      ws.on('close', (code, reason) => {
+        assert.strictEqual(code, 1006);
+        assert.strictEqual(reason, '');
+        done();
       });
     });
 
     it('error is emitted if server aborts connection', function (done) {
-      server.createServer(++port, server.handlers.return401, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.end(
+          `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
+          'Connection: close\r\n' +
+          'Content-type: text/html\r\n' +
+          `Content-Length: ${http.STATUS_CODES[401].length}\r\n` +
+          '\r\n'
+        );
+      });
 
-        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
-        ws.on('error', () => srv.close(done));
+      const ws = new WebSocket(`ws://localhost:${port}`);
+
+      ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+      ws.on('error', (err) => {
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.message, 'unexpected server response (401)');
+        done();
       });
     });
 
     it('unexpected response can be read when sent by server', function (done) {
-      server.createServer(++port, server.handlers.return401, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.end(
+          `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
+          'Connection: close\r\n' +
+          'Content-type: text/html\r\n' +
+          `Content-Length: ${http.STATUS_CODES[401].length}\r\n` +
+          '\r\n' +
+          'foo'
+        );
+      });
 
-        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
-        ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
-        ws.on('unexpected-response', (req, res) => {
-          assert.strictEqual(res.statusCode, 401);
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-          let data = '';
+      ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+      ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
+      ws.on('unexpected-response', (req, res) => {
+        assert.strictEqual(res.statusCode, 401);
 
-          res.on('data', (v) => {
-            data += v;
-          });
+        let data = '';
 
-          res.on('end', () => {
-            assert.strictEqual(data, 'Not allowed!');
-            srv.close(done);
-          });
+        res.on('data', (v) => {
+          data += v;
+        });
+
+        res.on('end', () => {
+          assert.strictEqual(data, 'foo');
+          done();
         });
       });
     });
 
     it('request can be aborted when unexpected response is sent by server', function (done) {
-      server.createServer(++port, server.handlers.return401, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.end(
+          `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
+          'Connection: close\r\n' +
+          'Content-type: text/html\r\n' +
+          `Content-Length: ${http.STATUS_CODES[401].length}\r\n` +
+          '\r\n' +
+          'foo'
+        );
+      });
 
-        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
-        ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
-        ws.on('unexpected-response', (req, res) => {
-          assert.strictEqual(res.statusCode, 401);
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-          res.on('end', () => srv.close(done));
-          req.abort();
-        });
+      ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+      ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
+      ws.on('unexpected-response', (req, res) => {
+        assert.strictEqual(res.statusCode, 401);
+
+        res.on('end', done);
+        req.abort();
       });
     });
   });
@@ -463,142 +524,114 @@ describe('WebSocket', function () {
   });
 
   describe('#ping', function () {
-    it('before connect should fail', function (done) {
-      server.createServer(++port, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+    it('before connect should fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
+      });
 
-        ws.on('error', () => {});
+      assert.throws(() => ws.ping(), /^Error: not opened$/);
+    });
 
-        try {
-          ws.ping();
-        } catch (e) {
-          srv.close(done);
-          ws.terminate();
-        }
+    it('before connect can silently fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
       });
+
+      assert.doesNotThrow(() => ws.ping('', true, true));
     });
 
-    it('before connect can silently fail', function (done) {
-      server.createServer(++port, (srv) => {
+    it('without message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => {});
-        ws.ping('', true, true);
+        ws.on('open', () => ws.ping());
+      });
 
-        srv.close(done);
-        ws.terminate();
+      wss.on('connection', (ws) => {
+        ws.on('ping', () => wss.close(done));
       });
     });
 
-    it('without message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, function (srv) {
-        srv.on('ping', () => {
-          srv.close(done);
-          ws.terminate();
-        });
-
+    it('with message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('open', () => ws.ping());
+        ws.on('open', () => {
+          ws.ping('hi', true);
+          ws.ping('hi');
+        });
       });
-    });
 
-    it('with message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('ping', (message) => {
+      wss.on('connection', (ws) => {
+        let pings = 0;
+        ws.on('ping', (message) => {
           assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
+          if (++pings === 2) wss.close(done);
         });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.ping('hi'));
       });
     });
 
     it('can send numbers as ping payload', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('ping', (message) => {
-          assert.strictEqual(message.toString(), '0');
-          srv.close(done);
-          ws.terminate();
-        });
-
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.ping(0));
       });
-    });
 
-    it('with encoded message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('ping', (message, flags) => {
-          assert.ok(flags.masked);
-          assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
+      wss.on('connection', (ws) => {
+        ws.on('ping', (message) => {
+          assert.strictEqual(message.toString(), '0');
+          wss.close(done);
         });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.ping('hi', true));
       });
     });
   });
 
   describe('#pong', function () {
-    it('before connect should fail', (done) => {
-      server.createServer(++port, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+    it('before connect should fail', () => {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
+      });
 
-        ws.on('error', () => {});
+      assert.throws(() => ws.pong(), /^Error: not opened$/);
+    });
 
-        try {
-          ws.pong();
-        } catch (e) {
-          srv.close(done);
-          ws.terminate();
-        }
+    it('before connect can silently fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
       });
+
+      assert.doesNotThrow(() => ws.pong('', true, true));
     });
 
-    it('before connect can silently fail', function (done) {
-      server.createServer(++port, (srv) => {
+    it('without message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => {});
-        ws.pong('', true, true);
+        ws.on('open', () => ws.pong());
+      });
 
-        srv.close(done);
-        ws.terminate();
+      wss.on('connection', (ws) => {
+        ws.on('pong', () => wss.close(done));
       });
     });
 
-    it('without message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('pong', () => {
-          srv.close(done);
-          ws.terminate();
-        });
-
+    it('with message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('open', () => ws.pong());
+        ws.on('open', () => {
+          ws.pong('hi', true);
+          ws.pong('hi');
+        });
       });
-    });
 
-    it('with message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('pong', (message) => {
+      wss.on('connection', (ws) => {
+        let pongs = 0;
+        ws.on('pong', (message) => {
           assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
+          if (++pongs === 2) wss.close(done);
         });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.pong('hi'));
       });
     });
 
@@ -616,55 +649,46 @@ describe('WebSocket', function () {
         });
       });
     });
-
-    it('with encoded message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('pong', (message, flags) => {
-          assert.ok(flags.masked);
-          assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
-        });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.pong('hi', true));
-      });
-    });
   });
 
   describe('#send', function () {
-    it('very long binary data can be sent and received (with echoing server)', (done) => {
-      server.createServer(++port, (srv) => {
+    it('very long binary data can be sent and received', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const array = new Float32Array(5 * 1024 * 1024);
 
-        for (let i = 0; i < array.length; ++i) {
+        for (let i = 0; i < array.length; i++) {
           array[i] = i / 5;
         }
 
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('open', () => ws.send(array, { binary: true }));
-        ws.on('message', (message, flags) => {
+        ws.on('open', () => ws.send(array, { compress: false }));
+        ws.on('message', (msg, flags) => {
           assert.ok(flags.binary);
-          assert.ok(message.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          assert.ok(msg.equals(Buffer.from(array.buffer)));
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg, { compress: false }));
+      });
     });
 
     it('can send and receive text data', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send('hi'));
         ws.on('message', (message, flags) => {
           assert.strictEqual(message, 'hi');
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('does not override the `fin` option', function (done) {
@@ -701,7 +725,7 @@ describe('WebSocket', function () {
     });
 
     it('can send binary data as an array', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const array = new Float32Array(6);
 
         for (let i = 0; i < array.length; ++i) {
@@ -718,14 +742,17 @@ describe('WebSocket', function () {
         ws.on('message', (message, flags) => {
           assert.ok(flags.binary);
           assert.ok(message.equals(buf));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('can send binary data as a buffer', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const buf = Buffer.from('foobar');
         const ws = new WebSocket(`ws://localhost:${port}`);
 
@@ -733,14 +760,17 @@ describe('WebSocket', function () {
         ws.on('message', (message, flags) => {
           assert.ok(flags.binary);
           assert.ok(message.equals(buf));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('ArrayBuffer is auto-detected without binary flag', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const array = new Float32Array(5);
 
         for (let i = 0; i < array.length; ++i) {
@@ -753,14 +783,17 @@ describe('WebSocket', function () {
         ws.onmessage = (event) => {
           assert.ok(event.binary);
           assert.ok(event.data.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('Buffer is auto-detected without binary flag', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const buf = Buffer.from('foobar');
         const ws = new WebSocket(`ws://localhost:${port}`);
 
@@ -769,136 +802,135 @@ describe('WebSocket', function () {
         ws.onmessage = (event) => {
           assert.ok(event.binary);
           assert.ok(event.data.equals(buf));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         };
       });
-    });
-
-    it('before connect should fail', function (done) {
-      server.createServer(++port, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => {});
-
-        try {
-          ws.send('hi');
-        } catch (e) {
-          srv.close(done);
-          ws.terminate();
-        }
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
       });
     });
 
-    it('before connect should pass error through callback, if present', function (done) {
-      const wss = new WebSocketServer({ port: ++port }, () => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+    it('before connect should fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
+      });
 
-        ws.send('hi', (err) => {
-          assert.ok(err instanceof Error);
-          assert.strictEqual(err.message, 'not opened');
-          ws.on('close', () => wss.close(done));
-        });
+      assert.throws(() => ws.send('hi'), /^Error: not opened$/);
+    });
+
+    it('before connect should pass error through callback, if present', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
       });
 
-      wss.on('connection', (ws) => ws.close());
+      ws.send('hi', (err) => {
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.message, 'not opened');
+      });
     });
 
     it('without data should be successful', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send());
+      });
 
-        srv.on('message', (message, flags) => {
+      wss.on('connection', (ws) => {
+        ws.on('message', (message) => {
           assert.ok(message.equals(Buffer.alloc(0)));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
     });
 
     it('calls optional callback when flushed', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => {
-          ws.send('hi', () => {
-            srv.close(done);
-            ws.terminate();
+          ws.send('hi', (err) => {
+            assert.ifError(err);
+            wss.close(done);
           });
         });
       });
     });
 
     it('with unmasked message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send('hi', { mask: false }));
+      });
 
-        srv.on('message', (message, flags) => {
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
           assert.strictEqual(message, 'hi');
-          srv.close(done);
-          ws.terminate();
+          assert.ok(!flags.masked);
+          wss.close(done);
         });
       });
     });
 
     it('with masked message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send('hi', { mask: true }));
+      });
 
-        srv.on('message', (message, flags) => {
-          assert.ok(flags.masked);
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
           assert.strictEqual(message, 'hi');
-          srv.close(done);
-          ws.terminate();
+          assert.ok(flags.masked);
+          wss.close(done);
         });
       });
     });
 
     it('with unmasked binary message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Float32Array(5);
+      const array = new Float32Array(5);
 
-        for (let i = 0; i < array.length; ++i) {
-          array[i] = i / 2;
-        }
+      for (let i = 0; i < array.length; ++i) {
+        array[i] = i / 2;
+      }
 
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send(array, { mask: false, binary: true }));
+      });
 
-        srv.on('message', (message, flags) => {
-          assert.ok(flags.binary);
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
           assert.ok(message.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          assert.ok(flags.binary);
+          wss.close(done);
         });
       });
     });
 
     it('with masked binary message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Float32Array(5);
+      const array = new Float32Array(5);
 
-        for (let i = 0; i < array.length; ++i) {
-          array[i] = i / 2;
-        }
+      for (let i = 0; i < array.length; ++i) {
+        array[i] = i / 2;
+      }
 
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send(array, { mask: true, binary: true }));
+      });
 
-        srv.on('message', (message, flags) => {
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
+          assert.ok(message.equals(Buffer.from(array.buffer)));
           assert.ok(flags.binary);
           assert.ok(flags.masked);
-          assert.ok(message.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
     });
@@ -949,79 +981,82 @@ describe('WebSocket', function () {
     });
 
     it('throws an error if the first argument is invalid (1/2)', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => {
-          try {
-            ws.close('error');
-          } catch (e) {
-            srv.close(done);
-            ws.terminate();
-          }
+          assert.throws(
+            () => ws.close('error'),
+            /^Error: first argument must be a valid error code number$/
+          );
+
+          wss.close(done);
         });
       });
     });
 
     it('throws an error if the first argument is invalid (2/2)', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => {
-          try {
-            ws.close(1004);
-          } catch (e) {
-            srv.close(done);
-            ws.terminate();
-          }
+          assert.throws(
+            () => ws.close(1004),
+            /^Error: first argument must be a valid error code number$/
+          );
+
+          wss.close(done);
         });
       });
     });
 
     it('works when close reason is not specified', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.close(1000));
+      });
 
-        srv.on('close', (code, message) => {
+      wss.on('connection', (ws) => {
+        ws.on('close', (code, message) => {
           assert.strictEqual(message, '');
-          srv.close(done);
-          ws.terminate();
+          assert.strictEqual(code, 1000);
+          wss.close(done);
         });
       });
     });
 
     it('works when close reason is specified', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.close(1000, 'some reason'));
+      });
 
-        srv.on('close', (code, message, flags) => {
-          assert.ok(flags.masked);
+      wss.on('connection', (ws) => {
+        ws.on('close', (code, message) => {
           assert.strictEqual(message, 'some reason');
-          srv.close(done);
-          ws.terminate();
+          assert.strictEqual(code, 1000);
+          wss.close(done);
         });
       });
     });
 
     it('ends connection to the server', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({
+        clientTracking: false,
+        port: ++port
+      }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
-        let connectedOnce = false;
 
         ws.on('open', () => {
-          connectedOnce = true;
+          ws.on('close', (code, reason) => {
+            assert.strictEqual(reason, 'some reason');
+            assert.strictEqual(code, 1000);
+            wss.close(done);
+          });
           ws.close(1000, 'some reason');
         });
-
-        ws.on('close', () => {
-          assert.ok(connectedOnce);
-          srv.close(done);
-          ws.terminate();
-        });
       });
     });
 
@@ -1188,7 +1223,10 @@ describe('WebSocket', function () {
     });
 
     it('should work the same as the EventEmitter api', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({
+        clientTracking: false,
+        port: ++port
+      }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
         let message = 0;
         let close = 0;
@@ -1209,10 +1247,13 @@ describe('WebSocket', function () {
           assert.strictEqual(message, 1);
           assert.strictEqual(open, 1);
           assert.strictEqual(close, 1);
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('doesn\'t return event listeners added with `on`', function () {
@@ -1287,16 +1328,19 @@ describe('WebSocket', function () {
     });
 
     it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.addEventListener('open', () => ws.send('hi'));
         ws.addEventListener('message', (messageEvent) => {
           assert.strictEqual(messageEvent.data, 'hi');
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('should receive valid CloseEvent when server closes with code 1000', function (done) {
@@ -1385,70 +1429,68 @@ describe('WebSocket', function () {
     });
 
     it('should pass binary data as a node.js Buffer by default', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Uint8Array(4096);
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.onopen = () => ws.send(array, { binary: true });
-        ws.onmessage = (messageEvent) => {
-          assert.ok(messageEvent.binary);
-          assert.strictEqual(ws.binaryType, 'nodebuffer');
-          assert.ok(messageEvent.data instanceof Buffer);
-          srv.close(done);
-          ws.terminate();
+        ws.onmessage = (evt) => {
+          assert.ok(Buffer.isBuffer(evt.data));
+          assert.ok(evt.binary);
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => ws.send(new Uint8Array(4096)));
     });
 
     it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Uint8Array(4096);
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.binaryType = 'arraybuffer';
 
-        ws.onopen = () => ws.send(array, { binary: true });
-        ws.onmessage = (messageEvent) => {
-          assert.ok(messageEvent.binary);
-          assert.ok(messageEvent.data instanceof ArrayBuffer);
-          srv.close(done);
-          ws.terminate();
+        ws.onmessage = (evt) => {
+          assert.ok(evt.data instanceof ArrayBuffer);
+          assert.ok(evt.binary);
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => ws.send(new Uint8Array(4096)));
     });
 
     it('should ignore binaryType for text messages', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
+
         ws.binaryType = 'arraybuffer';
 
-        ws.onopen = () => ws.send('foobar');
-        ws.onmessage = (messageEvent) => {
-          assert.ok(!messageEvent.binary);
-          assert.strictEqual(typeof messageEvent.data, 'string');
-          srv.close(done);
-          ws.terminate();
+        ws.onmessage = (evt) => {
+          assert.strictEqual(evt.data, 'foo');
+          assert.ok(!evt.binary);
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => ws.send('foo'));
     });
 
     it('should allow to update binaryType on the fly', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         function testType (binaryType, next) {
           const buf = Buffer.from(binaryType);
           ws.binaryType = binaryType;
 
-          ws.onmessage = (messageEvent) => {
+          ws.onmessage = (evt) => {
             if (binaryType === 'nodebuffer') {
-              assert.ok(Buffer.isBuffer(messageEvent.data));
-              assert.ok(messageEvent.data.equals(buf));
+              assert.ok(Buffer.isBuffer(evt.data));
+              assert.ok(evt.data.equals(buf));
             } else if (binaryType === 'arraybuffer') {
-              assert.ok(messageEvent.data instanceof ArrayBuffer);
-              assert.ok(Buffer.from(messageEvent.data).equals(buf));
+              assert.ok(evt.data instanceof ArrayBuffer);
+              assert.ok(Buffer.from(evt.data).equals(buf));
             } else if (binaryType === 'fragments') {
-              assert.deepStrictEqual(messageEvent.data, [buf]);
+              assert.deepStrictEqual(evt.data, [buf]);
             }
             next();
           };
@@ -1459,14 +1501,15 @@ describe('WebSocket', function () {
         ws.onopen = () => {
           testType('nodebuffer', () => {
             testType('arraybuffer', () => {
-              testType('fragments', () => {
-                srv.close(done);
-                ws.terminate();
-              });
+              testType('fragments', () => wss.close(done));
             });
           });
         };
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
   });
 
diff --git a/test/testserver.js b/test/testserver.js
deleted file mode 100644
index 8214650e8..000000000
--- a/test/testserver.js
+++ /dev/null
@@ -1,153 +0,0 @@
-'use strict';
-
-const EventEmitter = require('events');
-const crypto = require('crypto');
-const http = require('http');
-
-const Receiver = require('../lib/Receiver');
-const Sender = require('../lib/Sender');
-
-module.exports = {
-  handlers: {
-    closeAfterConnect: closeAfterConnectHandler,
-    invalidKey: invalidRequestHandler,
-    return401: return401,
-    valid: validServer
-  },
-  createServer: (port, handler, cb) => {
-    if (handler && !cb) {
-      cb = handler;
-      handler = null;
-    }
-
-    const webServer = http.createServer();
-    const srv = new Server(webServer);
-
-    webServer.on('upgrade', (req, socket) => {
-      webServer._socket = socket;
-      (handler || validServer)(srv, req, socket);
-    });
-
-    webServer.listen(port, '127.0.0.1', () => cb(srv));
-  }
-};
-
-function validServer (server, req, socket) {
-  if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') {
-    throw new Error('invalid headers');
-  }
-
-  if (!req.headers['sec-websocket-key']) {
-    throw new Error('websocket key is missing');
-  }
-
-  // calc key
-  const key = crypto.createHash('sha1')
-    .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary')
-    .digest('base64');
-
-  socket.setTimeout(0);
-  socket.setNoDelay(true);
-
-  socket.write(
-    'HTTP/1.1 101 Switching Protocols\r\n' +
-    'Upgrade: websocket\r\n' +
-    'Connection: Upgrade\r\n' +
-    `Sec-WebSocket-Accept:${key}\r\n` +
-    '\r\n'
-  );
-
-  const sender = new Sender(socket);
-  const receiver = new Receiver();
-
-  receiver.onping = (message, flags) => server.emit('ping', message, flags);
-  receiver.onpong = (message, flags) => server.emit('pong', message, flags);
-  receiver.onmessage = (message, flags) => {
-    server.emit('message', message, flags);
-    sender.send(message, { binary: flags.binary, fin: true });
-  };
-  receiver.onclose = (code, message, flags) => {
-    sender.close(code, message, false, () => {
-      server.emit('close', code, message, flags);
-      socket.end();
-    });
-  };
-
-  socket.on('data', (data) => receiver.add(data));
-  socket.on('end', () => socket.end());
-}
-
-function invalidRequestHandler (server, req, socket) {
-  if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') {
-    throw new Error('invalid headers');
-  }
-
-  if (!req.headers['sec-websocket-key']) {
-    throw new Error('websocket key is missing');
-  }
-
-  // calc key
-  const key = crypto.createHash('sha1')
-    .update(`${req.headers['sec-websocket-key']}bogus`, 'latin1')
-    .digest('base64');
-
-  socket.write(
-    'HTTP/1.1 101 Switching Protocols\r\n' +
-    'Upgrade: websocket\r\n' +
-    'Connection: Upgrade\r\n' +
-    `Sec-WebSocket-Accept:${key}\r\n` +
-    '\r\n'
-  );
-  socket.end();
-}
-
-function closeAfterConnectHandler (server, req, socket) {
-  if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') {
-    throw new Error('invalid headers');
-  }
-
-  if (!req.headers['sec-websocket-key']) {
-    throw new Error('websocket key is missing');
-  }
-
-  // calc key
-  const key = crypto.createHash('sha1')
-    .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1')
-    .digest('base64');
-
-  socket.write(
-    'HTTP/1.1 101 Switching Protocols\r\n' +
-    'Upgrade: websocket\r\n' +
-    'Connection: Upgrade\r\n' +
-    `Sec-WebSocket-Accept:${key}\r\n` +
-    '\r\n'
-  );
-  socket.end();
-}
-
-function return401 (server, req, socket) {
-  socket.write(
-    `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
-    'Connection: close\r\n' +
-    'Content-type: text/html\r\n' +
-    'Content-Length: 12\r\n' +
-    '\r\n' +
-    'Not allowed!'
-  );
-  socket.end();
-}
-
-/**
- * Server object, which will do the actual emitting
- */
-class Server extends EventEmitter {
-  constructor (webServer) {
-    super();
-    this.webServer = webServer;
-  }
-
-  close (cb) {
-    this.webServer.close(cb);
-    if (this._socket) this._socket.end();
-  }
-}

From 286d513c1239bc2f1c9d23d997de1c954d3c8615 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Mar 2017 21:43:25 +0100
Subject: [PATCH 272/489] [dist] 2.2.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bc0bf88a5..81cdebf81 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.2.1",
+  "version": "2.2.2",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 52d78421f29762add44c35528ab7de0da7673758 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 28 Mar 2017 07:55:32 +0200
Subject: [PATCH 273/489] [test] Use nyc for coverage

---
 .gitignore   | 3 ++-
 .travis.yml  | 6 ++----
 package.json | 8 +++-----
 3 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/.gitignore b/.gitignore
index 1e80e9875..1f0951bf2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 node_modules/
+.nyc_output/
 coverage/
-npm-debug.log
 .vscode/
+npm-debug.log
diff --git a/.travis.yml b/.travis.yml
index 3a58aefe7..4627538a3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,5 @@ node_js:
   - "7"
   - "6"
   - "4"
-script:
-  - "npm run test-travis"
-after_script:
-  - "npm install coveralls@2 && cat coverage/lcov.info | coveralls"
+after_success:
+  - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls"
diff --git a/package.json b/package.json
index 81cdebf81..b7f8f7313 100644
--- a/package.json
+++ b/package.json
@@ -18,10 +18,8 @@
     "url": "git://github.com/websockets/ws.git"
   },
   "scripts": {
-    "test-travis": "npm run lint && istanbul cover _mocha --report lcovonly -- test/*.test.js",
-    "coverage": "istanbul cover _mocha --report html -- test/*.test.js",
-    "integration": "npm run lint && mocha test/*.integration.js",
-    "test": "npm run lint && mocha test/*.test.js",
+    "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js",
+    "integration": "eslint . && mocha test/*.integration.js",
     "lint": "eslint ."
   },
   "dependencies": {
@@ -36,8 +34,8 @@
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.1.0",
-    "istanbul": "~0.4.5",
     "mocha": "~3.2.0",
+    "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"
   }
 }

From 105374c94e2e08807f58ada9fe00fe0f4e0a2415 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 28 Mar 2017 10:37:06 +0200
Subject: [PATCH 274/489] [test] Increase code coverage

---
 lib/BufferUtil.js      |  2 +-
 lib/Validation.js      |  2 +-
 test/Sender.test.js    | 88 +++++++++++++++++++++++++-----------------
 test/WebSocket.test.js | 30 ++++++++++++++
 4 files changed, 84 insertions(+), 38 deletions(-)

diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index bb1004672..b7e6d0ce4 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -31,7 +31,7 @@ try {
   const bufferUtil = require('bufferutil');
 
   module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
-} catch (e) {
+} catch (e) /* istanbul ignore next */ {
   /**
    * Masks a buffer using the given mask.
    *
diff --git a/lib/Validation.js b/lib/Validation.js
index f0380555c..fcb170f31 100644
--- a/lib/Validation.js
+++ b/lib/Validation.js
@@ -12,6 +12,6 @@ try {
   module.exports = typeof isValidUTF8 === 'object'
     ? isValidUTF8.Validation.isValidUTF8  // utf-8-validate@<3.0.0
     : isValidUTF8;
-} catch (e) {
+} catch (e) /* istanbul ignore next */ {
   module.exports = () => true;
 }
diff --git a/test/Sender.test.js b/test/Sender.test.js
index 7c60c1e8b..e30d34332 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -34,42 +34,6 @@ describe('Sender', function () {
     });
   });
 
-  describe('#ping', function () {
-    it('works with multiple types of data', function (done) {
-      let count = 0;
-      const sender = new Sender({
-        write: (data) => {
-          assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69])));
-          if (++count === 3) done();
-        }
-      });
-
-      const array = new Uint8Array([0x68, 0x69]);
-
-      sender.ping(array.buffer, false);
-      sender.ping(array, false);
-      sender.ping('hi', false);
-    });
-  });
-
-  describe('#pong', function () {
-    it('works with multiple types of data', function (done) {
-      let count = 0;
-      const sender = new Sender({
-        write: (data) => {
-          assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69])));
-          if (++count === 3) done();
-        }
-      });
-
-      const array = new Uint8Array([0x68, 0x69]);
-
-      sender.pong(array.buffer, false);
-      sender.pong(array, false);
-      sender.pong('hi', false);
-    });
-  });
-
   describe('#send', function () {
     it('compresses data if compress option is enabled', function (done) {
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
@@ -228,6 +192,58 @@ describe('Sender', function () {
     });
   });
 
+  describe('#ping', function () {
+    it('works with multiple types of data', function (done) {
+      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
+      let count = 0;
+      const sender = new Sender({
+        write: (data) => {
+          if (++count === 1) return;
+
+          assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69])));
+          if (count === 4) done();
+        }
+      }, {
+        'permessage-deflate': perMessageDeflate
+      });
+
+      perMessageDeflate.accept([{}]);
+
+      const array = new Uint8Array([0x68, 0x69]);
+
+      sender.send('foo', { compress: true, fin: true });
+      sender.ping(array.buffer, false);
+      sender.ping(array, false);
+      sender.ping('hi', false);
+    });
+  });
+
+  describe('#pong', function () {
+    it('works with multiple types of data', function (done) {
+      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
+      let count = 0;
+      const sender = new Sender({
+        write: (data) => {
+          if (++count === 1) return;
+
+          assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69])));
+          if (count === 4) done();
+        }
+      }, {
+        'permessage-deflate': perMessageDeflate
+      });
+
+      perMessageDeflate.accept([{}]);
+
+      const array = new Uint8Array([0x68, 0x69]);
+
+      sender.send('foo', { compress: true, fin: true });
+      sender.pong(array.buffer, false);
+      sender.pong(array, false);
+      sender.pong('hi', false);
+    });
+  });
+
   describe('#close', function () {
     it('should consume all data before closing', function (done) {
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index fd065d67e..a78fcea69 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -1129,6 +1129,21 @@ describe('WebSocket', function () {
         });
       });
     });
+
+    it('does nothing if the connection is already CLOSED', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('close', (code) => {
+          assert.strictEqual(code, 1000);
+          assert.strictEqual(ws.readyState, WebSocket.CLOSED);
+          ws.close();
+          wss.close(done);
+        });
+      });
+
+      wss.on('connection', (ws) => ws.close());
+    });
   });
 
   describe('#terminate', function () {
@@ -1174,6 +1189,21 @@ describe('WebSocket', function () {
         ws.on('close', () => done());
       });
     });
+
+    it('does nothing if the connection is already CLOSED', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('close', (code) => {
+          assert.strictEqual(code, 1006);
+          assert.strictEqual(ws.readyState, WebSocket.CLOSED);
+          ws.terminate();
+          wss.close(done);
+        });
+      });
+
+      wss.on('connection', (ws) => ws.terminate());
+    });
   });
 
   describe('WHATWG API emulation', function () {

From c50a6b36a4a0be67416f9c9803964546fd9cd270 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 1 Apr 2017 07:51:24 +0200
Subject: [PATCH 275/489] chore(package): update eslint to version 3.19.0
 (#1057)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b7f8f7313..eee0fa3bf 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
-    "eslint": "~3.18.0",
+    "eslint": "~3.19.0",
     "eslint-config-standard": "~8.0.0-beta.1",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",

From 459809362a584693812277587f7e4212030bd294 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sun, 2 Apr 2017 19:54:56 +0200
Subject: [PATCH 276/489] [minor] Use safe-buffer (#1059)

---
 .travis.yml                    | 1 +
 appveyor.yml                   | 1 +
 bench/parser.benchmark.js      | 6 +++++-
 bench/speed.js                 | 4 +++-
 lib/BufferUtil.js              | 8 ++++++--
 lib/Constants.js               | 4 ++++
 lib/PerMessageDeflate.js       | 7 +++++--
 lib/Receiver.js                | 4 ++++
 lib/Sender.js                  | 3 +++
 lib/WebSocketServer.js         | 3 +++
 package.json                   | 1 +
 test/PerMessageDeflate.test.js | 3 +++
 test/Receiver.test.js          | 3 +++
 test/Sender.test.js            | 3 +++
 test/Validation.test.js        | 3 +++
 test/WebSocket.test.js         | 2 ++
 test/WebSocketServer.test.js   | 2 ++
 test/hybi-util.js              | 4 ++++
 18 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 4627538a3..81cf3a9a2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,5 +5,6 @@ node_js:
   - "7"
   - "6"
   - "4"
+  - "4.1.0"
 after_success:
   - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls"
diff --git a/appveyor.yml b/appveyor.yml
index 954f55608..d893ad905 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,6 +3,7 @@ environment:
     - nodejs_version: "7"
     - nodejs_version: "6"
     - nodejs_version: "4"
+    - nodejs_version: "4.1.0"
 platform:
   - x86
   - x64
diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js
index c28e82583..5e6259cb4 100644
--- a/bench/parser.benchmark.js
+++ b/bench/parser.benchmark.js
@@ -6,11 +6,15 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const benchmark = require('benchmark');
 const crypto = require('crypto');
 
 const util = require('../test/hybi-util');
-const Receiver = require('../').Receiver;
+const WebSocket = require('..');
+
+const Receiver = WebSocket.Receiver;
+const Buffer = safeBuffer.Buffer;
 
 //
 // Override the `cleanup` method to make the "close message" test work as
diff --git a/bench/speed.js b/bench/speed.js
index 55047d5d8..b22e66883 100644
--- a/bench/speed.js
+++ b/bench/speed.js
@@ -1,9 +1,11 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const cluster = require('cluster');
 
-const WebSocket = require('../');
+const WebSocket = require('..');
 
+const Buffer = safeBuffer.Buffer;
 const port = 8181;
 
 if (cluster.isMaster) {
diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index b7e6d0ce4..6a35e8f43 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -1,11 +1,15 @@
-'use strict';
-
 /*!
  * ws: a node.js websocket client
  * Copyright(c) 2011 Einar Otto Stangvik 
  * MIT Licensed
  */
 
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
 /**
  * Merges an array of buffers into a new buffer.
  *
diff --git a/lib/Constants.js b/lib/Constants.js
index ce7ed989f..390441462 100644
--- a/lib/Constants.js
+++ b/lib/Constants.js
@@ -1,5 +1,9 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
 exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
 exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 exports.EMPTY_BUFFER = Buffer.alloc(0);
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 192d530ef..c1a1d3c70 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -1,14 +1,17 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const zlib = require('zlib');
 
 const bufferUtil = require('./BufferUtil');
 
+const Buffer = safeBuffer.Buffer;
+
 const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
-const DEFAULT_WINDOW_BITS = 15;
-const DEFAULT_MEM_LEVEL = 8;
 const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
 const EMPTY_BLOCK = Buffer.from([0x00]);
+const DEFAULT_WINDOW_BITS = 15;
+const DEFAULT_MEM_LEVEL = 8;
 
 /**
  * Per-message Deflate implementation.
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 54cb409b3..6c1a10e2c 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -6,12 +6,16 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
+
 const PerMessageDeflate = require('./PerMessageDeflate');
 const isValidUTF8 = require('./Validation');
 const bufferUtil = require('./BufferUtil');
 const ErrorCodes = require('./ErrorCodes');
 const constants = require('./Constants');
 
+const Buffer = safeBuffer.Buffer;
+
 const GET_INFO = 0;
 const GET_PAYLOAD_LENGTH_16 = 1;
 const GET_PAYLOAD_LENGTH_64 = 2;
diff --git a/lib/Sender.js b/lib/Sender.js
index 0df22dbb8..b33bfd4d8 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -6,12 +6,15 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const crypto = require('crypto');
 
 const PerMessageDeflate = require('./PerMessageDeflate');
 const bufferUtil = require('./BufferUtil');
 const ErrorCodes = require('./ErrorCodes');
 
+const Buffer = safeBuffer.Buffer;
+
 /**
  * HyBi Sender implementation.
  */
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index 5170375ba..e78efc1aa 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -6,6 +6,7 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const EventEmitter = require('events');
 const crypto = require('crypto');
 const Ultron = require('ultron');
@@ -17,6 +18,8 @@ const Extensions = require('./Extensions');
 const constants = require('./Constants');
 const WebSocket = require('./WebSocket');
 
+const Buffer = safeBuffer.Buffer;
+
 /**
  * Class representing a WebSocket server.
  *
diff --git a/package.json b/package.json
index eee0fa3bf..444b22b1b 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
     "lint": "eslint ."
   },
   "dependencies": {
+    "safe-buffer": "~5.0.1",
     "ultron": "~1.1.0"
   },
   "devDependencies": {
diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js
index 3da9de4bc..dbaf67e4c 100644
--- a/test/PerMessageDeflate.test.js
+++ b/test/PerMessageDeflate.test.js
@@ -1,10 +1,13 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 
 const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Extensions = require('../lib/Extensions');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('PerMessageDeflate', function () {
   describe('#offer', function () {
     it('should create default params', function () {
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 54948a2c8..6caed81a5 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -1,5 +1,6 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 const crypto = require('crypto');
 
@@ -8,6 +9,8 @@ const Receiver = require('../lib/Receiver');
 const Sender = require('../lib/Sender');
 const util = require('./hybi-util');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('Receiver', function () {
   it('can parse unmasked text message', function (done) {
     const p = new Receiver();
diff --git a/test/Sender.test.js b/test/Sender.test.js
index e30d34332..385011476 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -1,10 +1,13 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 
 const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Sender = require('../lib/Sender');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('Sender', function () {
   describe('.frame', function () {
     it('does not mutate the input buffer if data is `readOnly`', function () {
diff --git a/test/Validation.test.js b/test/Validation.test.js
index d13cf460a..88dc37aa5 100644
--- a/test/Validation.test.js
+++ b/test/Validation.test.js
@@ -1,9 +1,12 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 
 const isValidUTF8 = require('../lib/Validation');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('Validation', function () {
   describe('isValidUTF8', function () {
     it('should return true for a valid utf8 string', function () {
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index a78fcea69..2f629232f 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -2,6 +2,7 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 const crypto = require('crypto');
 const https = require('https');
@@ -11,6 +12,7 @@ const fs = require('fs');
 const constants = require('../lib/Constants');
 const WebSocket = require('..');
 
+const Buffer = safeBuffer.Buffer;
 const WebSocketServer = WebSocket.Server;
 let port = 20000;
 
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index afb3ded9c..3930592bf 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -2,6 +2,7 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 const crypto = require('crypto');
 const https = require('https');
@@ -11,6 +12,7 @@ const fs = require('fs');
 
 const WebSocket = require('..');
 
+const Buffer = safeBuffer.Buffer;
 const WebSocketServer = WebSocket.Server;
 let port = 8000;
 
diff --git a/test/hybi-util.js b/test/hybi-util.js
index cdff71f5a..3f0196b92 100644
--- a/test/hybi-util.js
+++ b/test/hybi-util.js
@@ -6,6 +6,10 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
 /**
  * Performs hybi07+ type masking.
  */

From 20bd7c7c1a88c181008e3ce822c2035d9eec9165 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sun, 2 Apr 2017 22:12:12 +0200
Subject: [PATCH 277/489] [fix] Don't reassign the `options` argument when
 `protocols` is `null`

---
 lib/WebSocket.js       | 11 ++++++-----
 test/WebSocket.test.js | 25 ++++++++++++++++---------
 2 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index a66c12814..21a9f105e 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -39,14 +39,15 @@ class WebSocket extends EventEmitter {
   constructor (address, protocols, options) {
     super();
 
-    if (typeof protocols === 'object' && !Array.isArray(protocols)) {
+    if (!protocols) {
+      protocols = [];
+    } else if (typeof protocols === 'string') {
+      protocols = [protocols];
+    } else if (!Array.isArray(protocols)) {
       options = protocols;
-      protocols = null;
+      protocols = [];
     }
 
-    if (typeof protocols === 'string') protocols = [protocols];
-    if (!Array.isArray(protocols)) protocols = [];
-
     this.readyState = WebSocket.CONNECTING;
     this.bytesReceived = 0;
     this.extensions = {};
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 2f629232f..72c4343d2 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -31,7 +31,7 @@ describe('WebSocket', function () {
   });
 
   describe('options', function () {
-    it('should accept an `agent` option', function (done) {
+    it('accepts an `agent` option', function (done) {
       const agent = new CustomAgent();
 
       agent.addRequest = () => {
@@ -41,11 +41,18 @@ describe('WebSocket', function () {
       const ws = new WebSocket('ws://localhost', { agent });
     });
 
-    // GH-227
-    it('should accept the `options` object as the 3rd argument', function () {
-      const ws = new WebSocket('ws://localhost', [], {
-        agent: new CustomAgent()
-      });
+    it('accepts the `options` object as the 3rd argument', function () {
+      const agent = new CustomAgent();
+      let count = 0;
+      let ws;
+
+      agent.addRequest = (req) => count++;
+
+      ws = new WebSocket('ws://localhost', undefined, { agent });
+      ws = new WebSocket('ws://localhost', null, { agent });
+      ws = new WebSocket('ws://localhost', [], { agent });
+
+      assert.strictEqual(count, 3);
     });
 
     it('throws an error when using an invalid `protocolVersion`', function () {
@@ -57,7 +64,7 @@ describe('WebSocket', function () {
       );
     });
 
-    it('should accept the localAddress option', function (done) {
+    it('accepts the localAddress option', function (done) {
       //
       // Skip this test on macOS as by default all loopback addresses other
       // than 127.0.0.1 are disabled.
@@ -76,14 +83,14 @@ describe('WebSocket', function () {
       });
     });
 
-    it('should accept the localAddress option whether it was wrong interface', function () {
+    it('accepts the localAddress option whether it was wrong interface', function () {
       assert.throws(
         () => new WebSocket(`ws://localhost:${port}`, { localAddress: '123.456.789.428' }),
         /must be a valid IP: 123.456.789.428/
       );
     });
 
-    it('should accept the family option', function (done) {
+    it('accepts the family option', function (done) {
       const wss = new WebSocketServer({ host: '::1', port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 });
       });

From 212c7aab04a5f23d89111c1722371211efa2dd89 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 3 Apr 2017 11:58:56 +0200
Subject: [PATCH 278/489] [dist] 2.2.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 444b22b1b..88c7a9d14 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.2.2",
+  "version": "2.2.3",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 0d7cd2b5bde5bdb0e16ae5d4155255cfa779625f Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 5 Apr 2017 07:43:14 +0200
Subject: [PATCH 279/489] chore(package): update eslint-plugin-standard to
 version 2.2.0 (#1064)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 88c7a9d14..8546a3963 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
-    "eslint-plugin-standard": "~2.1.0",
+    "eslint-plugin-standard": "~2.2.0",
     "mocha": "~3.2.0",
     "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"

From a4cc55d1c707e1515bfdba6da357231443454b39 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 5 Apr 2017 07:59:54 +0200
Subject: [PATCH 280/489] chore(package): update eslint-config-standard to
 version 10.0.0 (#1065)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8546a3963..4b94ab1c1 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.19.0",
-    "eslint-config-standard": "~8.0.0-beta.1",
+    "eslint-config-standard": "~10.0.0",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From 617aeacaf96f2ac31f437354734ce53fc9cdc73c Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 6 Apr 2017 10:14:11 +0200
Subject: [PATCH 281/489] [minor] Make all hooks have access to the upgrade
 request

Closes #787
Fixes #783
Fixes #683
Fixes #525
---
 doc/ws.md                    |  4 +++-
 lib/WebSocketServer.js       |  6 +++---
 test/WebSocketServer.test.js | 33 ++++++++++++++++++++++++++-------
 3 files changed, 32 insertions(+), 11 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 8893edf47..af3b8556d 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -51,10 +51,11 @@ if `verifyClient` is provided with two arguments then those are:
 
 
 If `handleProtocols` is not set then the handshake is automatically accepted,
-otherwise the function takes a single argument:
+otherwise the function takes two arguments:
 
 - `protocols` {Array} The list of WebSocket subprotocols indicated by the
   client in the `Sec-WebSocket-Protocol` header.
+- `request` {http.IncomingMessage} The client HTTP GET request.
 
 If returned value is `false` then the handshake is rejected with the HTTP 401
 status code, otherwise the returned value sets the value of the
@@ -100,6 +101,7 @@ Emitted when an error occurs on the underlying server.
 ### Event: 'headers'
 
 - `headers` {Array}
+- `request` {http.IncomingMessage}
 
 Emitted before the response headers are written to the socket as part of the
 handshake. This allows you to inspect/modify the headers before they are sent.
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index e78efc1aa..bd3ef24e3 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -168,7 +168,7 @@ class WebSocketServer extends EventEmitter {
     // Optionally call external protocol selection handler.
     //
     if (this.options.handleProtocols) {
-      protocol = this.options.handleProtocols(protocol);
+      protocol = this.options.handleProtocols(protocol, req);
       if (protocol === false) return abortConnection(socket, 401);
     } else {
       protocol = protocol[0];
@@ -252,11 +252,11 @@ class WebSocketServer extends EventEmitter {
     //
     // Allow external modification/inspection of handshake headers.
     //
-    this.emit('headers', headers);
+    this.emit('headers', headers, req);
 
     socket.write(headers.concat('', '').join('\r\n'));
 
-    const client = new WebSocket([req, socket, head], {
+    const client = new WebSocket([req, socket, head], null, {
       maxPayload: this.options.maxPayload,
       protocolVersion: version,
       extensions,
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 3930592bf..6092e3283 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -801,7 +801,7 @@ describe('WebSocketServer', function () {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
-        ws.on('open', (client) => {
+        ws.on('open', () => {
           assert.strictEqual(ws.protocol, 'prot1');
           wss.close();
           done();
@@ -810,16 +810,17 @@ describe('WebSocketServer', function () {
     });
 
     it('selects the last protocol via protocol handler', function (done) {
-      const wss = new WebSocketServer({
-        handleProtocols: (ps) => ps[ps.length - 1],
-        port: ++port
-      }, () => {
+      const handleProtocols = (protocols, request) => {
+        assert.ok(request instanceof http.IncomingMessage);
+        assert.strictEqual(request.url, '/');
+        return protocols.pop();
+      };
+      const wss = new WebSocketServer({ handleProtocols, port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
         ws.on('open', () => {
           assert.strictEqual(ws.protocol, 'prot2');
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
     });
@@ -903,6 +904,24 @@ describe('WebSocketServer', function () {
         done();
       });
     });
+
+    it('emits the `headers` event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        wss.on('headers', (headers, request) => {
+          assert.deepStrictEqual(headers.slice(0, 3), [
+            'HTTP/1.1 101 Switching Protocols',
+            'Upgrade: websocket',
+            'Connection: Upgrade'
+          ]);
+          assert.ok(request instanceof http.IncomingMessage);
+          assert.strictEqual(request.url, '/');
+
+          wss.on('connection', () => wss.close(done));
+        });
+      });
+    });
   });
 
   describe('messaging', function () {

From 13cff408419fef5a2ccc882ba32adfbcced5a771 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Thu, 6 Apr 2017 20:58:53 +0200
Subject: [PATCH 282/489] chore(package): update eslint-config-standard to
 version 10.1.0 (#1072)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4b94ab1c1..d010bd667 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.19.0",
-    "eslint-config-standard": "~10.0.0",
+    "eslint-config-standard": "~10.1.0",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From 26bf7754ecd9364f55f1d4e89a3ea09bd06f4d02 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 6 Apr 2017 20:59:38 +0200
Subject: [PATCH 283/489] chore(package): update eslint-plugin-standard to
 version 3.0.0 (#1074)

Closes #1073

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d010bd667..8c085612f 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
-    "eslint-plugin-standard": "~2.2.0",
+    "eslint-plugin-standard": "~3.0.0",
     "mocha": "~3.2.0",
     "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"

From c4202c67427c485d0f6a4c4c0afb3bdd6f7b538c Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Fri, 7 Apr 2017 07:32:54 +0200
Subject: [PATCH 284/489] chore(package): update eslint-config-standard to
 version 10.2.0 (#1075)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8c085612f..32e05a969 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.19.0",
-    "eslint-config-standard": "~10.1.0",
+    "eslint-config-standard": "~10.2.0",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From 2efbdbc7ab3f0deb2ad8c891c969635486ae6824 Mon Sep 17 00:00:00 2001
From: Daniel Gonzalez Reina 
Date: Tue, 11 Apr 2017 16:59:01 +0200
Subject: [PATCH 285/489] Added a note to tell possible confused users that
 they should be using the native WebSocket in the browser

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 714f1a8d1..dc4863364 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,8 @@ and server implementation.
 Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/
 for the full reports.
 
+**Note for using `ws` in the browser**: This is a node.js library, and for the browser you should use the [native WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications). The client in the docs are a reference to a back end with the role of a client in the WebSocket communication.
+
 ## Protocol support
 
 * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)

From 2bacadca97f9dbdf67a802e216f34b966f41e0a2 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 13 Apr 2017 09:58:57 +0200
Subject: [PATCH 286/489] [doc] Update optional modules section

---
 README.md | 29 ++++++++++++++---------------
 1 file changed, 14 insertions(+), 15 deletions(-)

diff --git a/README.md b/README.md
index dc4863364..f4ab1894c 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,10 @@ and server implementation.
 Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/
 for the full reports.
 
-**Note for using `ws` in the browser**: This is a node.js library, and for the browser you should use the [native WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications). The client in the docs are a reference to a back end with the role of a client in the WebSocket communication.
+**Note**: This module does not work in the browser. The client in the docs is a
+reference to a back end with the role of a client in the WebSocket
+communication. Browser clients must use the native
+[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object.
 
 ## Protocol support
 
@@ -24,22 +27,18 @@ for the full reports.
 npm install --save ws
 ```
 
-### Opt-in for performance
+### Opt-in for performance and spec compliance
 
 There are 2 optional modules that can be installed along side with the `ws`
-module. These modules are binary addons which improve certain operations, but as
-they are binary addons they require compilation which can fail if no c++
-compiler is installed on the host system.
-
-- `npm install --save bufferutil`: Improves internal buffer operations which
-  allows for faster processing of masked WebSocket frames and general buffer
-  operations.
-- `npm install --save utf-8-validate`: The specification requires validation of
-  invalid UTF-8 chars, some of these validations could not be done in JavaScript
-  hence the need for a binary addon. In most cases you will already be
-  validating the input that you receive for security purposes leading to double
-  validation. But if you want to be 100% spec-conforming and have fast
-  validation of UTF-8 then this module is a must.
+module. These modules are binary addons which improve certain operations.
+Prebuilt binaries are available for the most popular platforms so you don't
+necessarily need to have a C++ compiler installed on your machine.
+
+- `npm install --save-optional bufferutil`: Allows to efficiently perform
+  operations such as masking and unmasking the data payload of the WebSocket
+  frames.
+- `npm install --save-optional utf-8-validate`: Allows to efficiently check
+  if a message contains valid UTF-8 as required by the spec.
 
 ## API Docs
 

From e182e96d2db4dadf347fd1bd04628a8b70b56ce8 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Fri, 14 Apr 2017 07:51:10 +0200
Subject: [PATCH 287/489] [doc] Fix typo in README.md

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index f4ab1894c..1ca0bdb91 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,9 @@ for Node.js-like docs for the ws classes.
 
 ## WebSocket compression
 
-`ws` supports the [permessage-deflate extension][permessage-deflate] extension
-which enables the client and server to negotiate a compression algorithm and
-its parameters, and then selectively apply it to the data payloads of each
+`ws` supports the [permessage-deflate extension][permessage-deflate] which
+enables the client and server to negotiate a compression algorithm and its
+parameters, and then selectively apply it to the data payloads of each
 WebSocket message.
 
 The extension is enabled by default but adds a significant overhead in terms of

From 3dbb58a82e118707fb08dc12c8c4e3f5c67f1005 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 18 Apr 2017 13:59:32 +0200
Subject: [PATCH 288/489] [perf] Prevent `Sender.frame()` from being
 deoptimized

---
 lib/Sender.js | 47 +++++++++++++++++++++++------------------------
 1 file changed, 23 insertions(+), 24 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index b33bfd4d8..79e68a5a3 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -140,11 +140,11 @@ class Sender {
    */
   doClose (data, mask, cb) {
     this.sendFrame(Sender.frame(data, {
-      readOnly: false,
-      opcode: 0x08,
-      rsv1: false,
       fin: true,
-      mask
+      rsv1: false,
+      opcode: 0x08,
+      mask,
+      readOnly: false
     }), cb);
   }
 
@@ -186,11 +186,11 @@ class Sender {
    */
   doPing (data, mask, readOnly) {
     this.sendFrame(Sender.frame(data, {
-      opcode: 0x09,
-      rsv1: false,
       fin: true,
-      readOnly,
-      mask
+      rsv1: false,
+      opcode: 0x09,
+      mask,
+      readOnly
     }));
   }
 
@@ -232,11 +232,11 @@ class Sender {
    */
   doPong (data, mask, readOnly) {
     this.sendFrame(Sender.frame(data, {
-      opcode: 0x0a,
-      rsv1: false,
       fin: true,
-      readOnly,
-      mask
+      rsv1: false,
+      opcode: 0x0a,
+      mask,
+      readOnly
     }));
   }
 
@@ -283,26 +283,25 @@ class Sender {
 
     if (this.perMessageDeflate) {
       const opts = {
-        compress: this.compress,
-        mask: options.mask,
         fin: options.fin,
-        readOnly,
+        rsv1,
         opcode,
-        rsv1
+        mask: options.mask,
+        readOnly
       };
 
       if (this.deflating) {
-        this.enqueue([this.dispatch, data, opts, cb]);
+        this.enqueue([this.dispatch, data, this.compress, opts, cb]);
       } else {
-        this.dispatch(data, opts, cb);
+        this.dispatch(data, this.compress, opts, cb);
       }
     } else {
       this.sendFrame(Sender.frame(data, {
-        mask: options.mask,
         fin: options.fin,
         rsv1: false,
-        readOnly,
-        opcode
+        opcode,
+        mask: options.mask,
+        readOnly
       }), cb);
     }
   }
@@ -311,18 +310,18 @@ class Sender {
    * Dispatches a data message.
    *
    * @param {Buffer} data The message to send
+   * @param {Boolean} compress Specifies whether or not to compress `data`
    * @param {Object} options Options object
    * @param {Number} options.opcode The opcode
    * @param {Boolean} options.readOnly Specifies whether `data` can be modified
    * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
-   * @param {Boolean} options.compress Specifies whether or not to compress `data`
    * @param {Boolean} options.mask Specifies whether or not to mask `data`
    * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
    * @param {Function} cb Callback
    * @private
    */
-  dispatch (data, options, cb) {
-    if (!options.compress) {
+  dispatch (data, compress, options, cb) {
+    if (!compress) {
       this.sendFrame(Sender.frame(data, options), cb);
       return;
     }

From b5b095474a4a25aad89b5cff97295c4c64c499b2 Mon Sep 17 00:00:00 2001
From: Jason Walton 
Date: Thu, 20 Apr 2017 09:37:38 -0400
Subject: [PATCH 289/489] [feature] Add headers event to WebSocket (#1082)

---
 doc/ws.md              |  9 +++++++++
 lib/WebSocket.js       |  2 ++
 test/WebSocket.test.js | 10 ++++++++++
 3 files changed, 21 insertions(+)

diff --git a/doc/ws.md b/doc/ws.md
index af3b8556d..cdb1f03e5 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -226,6 +226,15 @@ human-readable string explaining why the connection has been closed.
 Emitted when an error occurs. Errors from the underlying `net.Socket` are
 forwarded here.
 
+### Event: 'headers'
+
+- `headers` {Object}
+- `response` {http.IncomingMessage}
+
+Emitted when response headers are received from the server as part of the
+handshake.  This allows you to read headers from the server, for example
+'set-cookie' headers.
+
 ### Event: 'message'
 
 - `data` {String|Buffer}
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 21a9f105e..b835cdfe1 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -655,6 +655,8 @@ function initAsClient (address, protocols, options) {
   this._req.on('upgrade', (res, socket, head) => {
     this._req = null;
 
+    this.emit('headers', res.headers, res);
+
     const digest = crypto.createHash('sha1')
       .update(key + constants.GUID, 'binary')
       .digest('base64');
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 72c4343d2..348e112d7 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -328,6 +328,16 @@ describe('WebSocket', function () {
 
       wss.on('connection', (client) => client.pong());
     });
+
+    it('emits a headers event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+        ws.on('headers', (headers, res) => {
+          assert.strictEqual(headers, res.headers);
+          wss.close(done);
+        });
+      });
+    });
   });
 
   describe('connection establishing', function () {

From 2d66ec92f2dc58036725b8ff77ae13b884e48040 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 15:52:51 +0200
Subject: [PATCH 290/489] [pkg] Remove .npmignore in favor of `files`
 package.json field

---
 .npmignore   |  7 -------
 package.json | 19 +++++++++++--------
 2 files changed, 11 insertions(+), 15 deletions(-)
 delete mode 100644 .npmignore

diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 7c56d8c06..000000000
--- a/.npmignore
+++ /dev/null
@@ -1,7 +0,0 @@
-coverage/
-examples/
-bench/
-test/
-doc/
-appveyor.yml
-.*
diff --git a/package.json b/package.json
index 32e05a969..16f8b268d 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,7 @@
 {
-  "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
-  "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "version": "2.2.3",
-  "license": "MIT",
-  "main": "index.js",
+  "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "keywords": [
     "HyBi",
     "Push",
@@ -13,10 +10,16 @@
     "WebSockets",
     "real-time"
   ],
-  "repository": {
-    "type": "git",
-    "url": "git://github.com/websockets/ws.git"
-  },
+  "homepage": "https://github.com/websockets/ws",
+  "bugs": "https://github.com/websockets/ws/issues",
+  "repository": "websockets/ws",
+  "author": "Einar Otto Stangvik  (http://2x.io)",
+  "license": "MIT",
+  "main": "index.js",
+  "files": [
+    "index.js",
+    "lib"
+  ],
   "scripts": {
     "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js",
     "integration": "eslint . && mocha test/*.integration.js",

From 309d77f27ac2a505261c9d17dcfcf1a7ad0b1cae Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 15:54:52 +0200
Subject: [PATCH 291/489] [dist] 2.3.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 16f8b268d..41892ed9e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ws",
-  "version": "2.2.3",
+  "version": "2.3.0",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "keywords": [
     "HyBi",

From 3633d6c5226c2fa47fcf1bcfca7047b749816cfc Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 18:47:59 +0200
Subject: [PATCH 292/489] [fix] Make `WebSocket#close()` work when called from
 a headers listener

---
 lib/WebSocket.js       | 10 ++++++++--
 test/WebSocket.test.js | 28 ++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index b835cdfe1..41868d832 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -653,10 +653,16 @@ function initAsClient (address, protocols, options) {
   });
 
   this._req.on('upgrade', (res, socket, head) => {
-    this._req = null;
-
     this.emit('headers', res.headers, res);
 
+    //
+    // The user may have closed the connection from a listener of the `headers`
+    // event.
+    //
+    if (this.readyState !== WebSocket.CONNECTING) return;
+
+    this._req = null;
+
     const digest = crypto.createHash('sha1')
       .update(key + constants.GUID, 'binary')
       .digest('base64');
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 348e112d7..78d5ebaa3 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -999,6 +999,20 @@ describe('WebSocket', function () {
       });
     });
 
+    it('can be called from a listener of the headers event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+        ws.on('error', (err) => {
+          assert.ok(err instanceof Error);
+          assert.strictEqual(err.message, 'closed before the connection is established');
+          ws.on('close', () => wss.close(done));
+        });
+        ws.on('headers', () => ws.close());
+      });
+    });
+
     it('throws an error if the first argument is invalid (1/2)', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
@@ -1209,6 +1223,20 @@ describe('WebSocket', function () {
       });
     });
 
+    it('can be called from a listener of the headers event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+        ws.on('error', (err) => {
+          assert.ok(err instanceof Error);
+          assert.strictEqual(err.message, 'closed before the connection is established');
+          ws.on('close', () => wss.close(done));
+        });
+        ws.on('headers', () => ws.terminate());
+      });
+    });
+
     it('does nothing if the connection is already CLOSED', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);

From 732aaf06b76700f104eeff2740e1896be4e88199 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 19:40:11 +0200
Subject: [PATCH 293/489] [dist] 2.3.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 41892ed9e..6a95c7375 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ws",
-  "version": "2.3.0",
+  "version": "2.3.1",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "keywords": [
     "HyBi",

From 6ade76ddd1637da28a91dd3b7f69e93daef655f8 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Mon, 24 Apr 2017 15:27:06 +0200
Subject: [PATCH 294/489] chore(package): update mocha to version 3.3.0 (#1086)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6a95c7375..2dc589f55 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~3.0.0",
-    "mocha": "~3.2.0",
+    "mocha": "~3.3.0",
     "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"
   }

From d75ab9dc91e98673a9fcfdf585d5a0c8cfc48e99 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 29 Apr 2017 09:51:42 +0200
Subject: [PATCH 295/489] chore(package): update nyc to version 10.3.0 (#1091)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2dc589f55..79d2938d9 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~3.0.0",
     "mocha": "~3.3.0",
-    "nyc": "~10.2.0",
+    "nyc": "~10.3.0",
     "utf-8-validate": "~3.0.0"
   }
 }

From cce4d0f096169c4cd99592e2895da310fec19e19 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 3 May 2017 10:34:18 +0200
Subject: [PATCH 296/489] [test] Add callback to `wss.close()`

---
 test/WebSocket.test.js       | 24 +++------
 test/WebSocketServer.test.js | 99 +++++++++++-------------------------
 2 files changed, 35 insertions(+), 88 deletions(-)

diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 78d5ebaa3..e93bc6a83 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -108,8 +108,7 @@ describe('WebSocket', function () {
         const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false });
         ws.on('message', () => {
           assert.strictEqual(ws.bytesReceived, 8);
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
       wss.on('connection', (ws) => ws.send('foobar'));
@@ -308,10 +307,7 @@ describe('WebSocket', function () {
     it('emits a ping event', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
-        ws.on('ping', function () {
-          wss.close();
-          done();
-        });
+        ws.on('ping', () => wss.close(done));
       });
 
       wss.on('connection', (client) => client.ping());
@@ -320,10 +316,7 @@ describe('WebSocket', function () {
     it('emits a pong event', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
-        ws.on('pong', () => {
-          wss.close();
-          done();
-        });
+        ws.on('pong', () => wss.close(done));
       });
 
       wss.on('connection', (client) => client.pong());
@@ -515,8 +508,7 @@ describe('WebSocket', function () {
         let paused = true;
         serverClient.on('message', () => {
           assert.ok(!paused);
-          wss.close();
-          done();
+          wss.close(done);
         });
         serverClient.pause();
 
@@ -1427,9 +1419,7 @@ describe('WebSocket', function () {
         ws.addEventListener('close', (closeEvent) => {
           assert.ok(closeEvent.wasClean);
           assert.strictEqual(closeEvent.code, 1000);
-
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
 
@@ -1444,9 +1434,7 @@ describe('WebSocket', function () {
           assert.ok(!closeEvent.wasClean);
           assert.strictEqual(closeEvent.code, 1001);
           assert.strictEqual(closeEvent.reason, 'some daft reason');
-
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
 
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 6092e3283..801db7d6a 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -264,8 +264,7 @@ describe('WebSocketServer', function () {
     it('exposes options passed to constructor', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         assert.strictEqual(wss.options.port, port);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
   });
@@ -279,8 +278,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.maxPayload, maxPayload);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -292,8 +290,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client._receiver.maxPayload, maxPayload);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -309,8 +306,7 @@ describe('WebSocketServer', function () {
           client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload,
           maxPayload
         );
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
   });
@@ -363,8 +359,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -386,8 +381,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -409,8 +403,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -435,8 +428,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -462,8 +454,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -493,8 +484,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 401);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -525,10 +515,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('verifyClient gets client origin', function (done) {
@@ -555,8 +542,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.ok(verifyClientCalled);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -590,8 +576,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.ok(verifyClientCalled);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -666,8 +651,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 401);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -697,8 +681,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 404);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -729,10 +712,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) {
@@ -791,8 +771,7 @@ describe('WebSocketServer', function () {
       wss.on('connection', (ws) => {
         ws.on('message', (data) => {
           assert.strictEqual(data, 'Hello');
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
     });
@@ -803,8 +782,7 @@ describe('WebSocketServer', function () {
 
         ws.on('open', () => {
           assert.strictEqual(ws.protocol, 'prot1');
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
     });
@@ -833,10 +811,7 @@ describe('WebSocketServer', function () {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
         ws.on('open', () => done(new Error('connection must not be established')));
-        ws.on('error', () => {
-          wss.close();
-          done();
-        });
+        ws.on('error', () => wss.close(done));
       });
     });
 
@@ -848,10 +823,7 @@ describe('WebSocketServer', function () {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
         ws.on('open', () => done(new Error('connection must not be established')));
-        ws.on('error', () => {
-          wss.close();
-          done();
-        });
+        ws.on('error', () => wss.close(done));
       });
     });
 
@@ -874,8 +846,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 401);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -899,10 +870,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('emits the `headers` event', function (done) {
@@ -942,8 +910,7 @@ describe('WebSocketServer', function () {
       wss.on('connection', (client) => {
         client.on('message', (message) => {
           assert.strictEqual(message, data);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         client.send(data);
@@ -959,8 +926,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.protocol, 'hi');
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -971,8 +937,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.protocolVersion, 8);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -983,8 +948,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.upgradeReq.httpVersion, '1.1');
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
   });
@@ -1007,10 +971,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('does not accept connections with not defined extension parameter', function (done) {
@@ -1029,8 +990,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -1057,8 +1017,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();

From 2e92c7426cbd0eee0c6dd978986b9b33e813fdbb Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 9 May 2017 10:35:41 +0200
Subject: [PATCH 297/489] [doc] Add FAQ and TOC

---
 README.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 87 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 1ca0bdb91..e43891614 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,26 @@ reference to a back end with the role of a client in the WebSocket
 communication. Browser clients must use the native
 [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object.
 
+## Table of Contents
+
+* [Protocol support](#protocol-support)
+* [Installing](#installing)
+  + [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance)
+* [API docs](#api-docs)
+* [WebSocket compression](#websocket-compression)
+* [Usage examples](#usage-examples)
+  + [Sending and receiving text data](#sending-and-receiving-text-data)
+  + [Sending binary data](#sending-binary-data)
+  + [Server example](#server-example)
+  + [Broadcast example](#broadcast-example)
+  + [ExpressJS example](#expressjs-example)
+  + [echo.websocket.org demo](#echowebsocketorg-demo)
+  + [Other examples](#other-examples)
+* [Error handling best practices](#error-handling-best-practices)
+* [FAQ](#faq)
+* [Changelog](#changelog)
+* [License](#license)
+
 ## Protocol support
 
 * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
@@ -40,10 +60,9 @@ necessarily need to have a C++ compiler installed on your machine.
 - `npm install --save-optional utf-8-validate`: Allows to efficiently check
   if a message contains valid UTF-8 as required by the spec.
 
-## API Docs
+## API docs
 
-See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md)
-for Node.js-like docs for the ws classes.
+See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes.
 
 ## WebSocket compression
 
@@ -248,13 +267,76 @@ try { ws.send('something'); }
 catch (e) { /* handle error */ }
 ```
 
+## FAQ
+
+
+How to get the IP address of the client? +The remote IP address can be obtained from the raw socket. + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); + +wss.on('connection', function connection(ws) { + const ip = ws.upgradeReq.connection.remoteAddress; +}); +``` + +When the server runs behing a proxy like NGINX, the de-facto standard is to use +the `X-Forwarded-For` header. + +```js +wss.on('connection', function connection(ws) { + const ip = ws.upgradeReq['x-forwarded-for']; +}); +``` +
+ +
+How to detect and close broken connections? +Sometimes the link between the server and the client can be interrupted in a +way that keeps both the server and the client unware of the broken state of the +connection (e.g. when pulling the cord). + +In these cases ping messages can be used as a means to verify that the remote +endpoint is still responsive. + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); + +function heartbeat() { + this.isAlive = true; +} + +wss.on('connection', function connection(ws) { + ws.isAlive = true; + ws.on('pong', heartbeat); +}); + +const interval = setInterval(function ping() { + wss.clients.forEach(function each(ws) { + if (ws.isAlive === false) return ws.terminate(); + + ws.isAlive = false; + ws.ping('', false, true); + }); +}, 30000); +``` + +Pong messages are automatically sent in reponse to ping messages as required +by the spec. +
+ ## Changelog -We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) -for changelog entries. +We're using the GitHub [releases][changelog] for changelog entries. ## License [MIT](LICENSE) [permessage-deflate]: https://tools.ietf.org/html/rfc7692 +[changelog]: https://github.com/websockets/ws/releases From 9d0efbb274518e40845ee778fc87236cf191777b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 9 May 2017 10:48:22 +0200 Subject: [PATCH 298/489] [doc] Fix typo in code snippet --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e43891614..25dac5f6c 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ the `X-Forwarded-For` header. ```js wss.on('connection', function connection(ws) { - const ip = ws.upgradeReq['x-forwarded-for']; + const ip = ws.upgradeReq.headers['x-forwarded-for']; }); ``` From abd27fe449e514c048846a43b8d51efa4317b74a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 9 May 2017 11:19:51 +0200 Subject: [PATCH 299/489] [doc] Tweak markdown formatting --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 25dac5f6c..cf3ccfbe6 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ communication. Browser clients must use the native + [Other examples](#other-examples) * [Error handling best practices](#error-handling-best-practices) * [FAQ](#faq) + + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) + + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) * [Changelog](#changelog) * [License](#license) @@ -269,8 +271,8 @@ catch (e) { /* handle error */ } ## FAQ -
-How to get the IP address of the client? +### How to get the IP address of the client? + The remote IP address can be obtained from the raw socket. ```js @@ -291,10 +293,9 @@ wss.on('connection', function connection(ws) { const ip = ws.upgradeReq.headers['x-forwarded-for']; }); ``` -
-
-How to detect and close broken connections? +### How to detect and close broken connections? + Sometimes the link between the server and the client can be interrupted in a way that keeps both the server and the client unware of the broken state of the connection (e.g. when pulling the cord). @@ -328,7 +329,6 @@ const interval = setInterval(function ping() { Pong messages are automatically sent in reponse to ping messages as required by the spec. -
## Changelog From 1849ac26ee852df3804e8b4093ab316ed787a7f5 Mon Sep 17 00:00:00 2001 From: Gibson Fahnestock Date: Sun, 14 May 2017 05:46:47 +0100 Subject: [PATCH 300/489] [test] Skip test if 127.0.0.2 unavailable (#1106) --- test/WebSocket.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index e93bc6a83..7f93f981a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -65,16 +65,16 @@ describe('WebSocket', function () { }); it('accepts the localAddress option', function (done) { - // - // Skip this test on macOS as by default all loopback addresses other - // than 127.0.0.1 are disabled. - // - if (process.platform === 'darwin') return done(); - const wss = new WebSocketServer({ host: '127.0.0.1', port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { localAddress: '127.0.0.2' }); + + ws.on('error', (err) => { + // Skip this test on machines where 127.0.0.2 is disabled. + if (err.code === 'EADDRNOTAVAIL') return done(); + throw err; + }); }); wss.on('connection', (ws) => { From 839eac8bc76674313f343e402f567d40abe21138 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 15 May 2017 07:24:14 +0200 Subject: [PATCH 301/489] chore(package): update mocha to version 3.4.1 (#1108) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79d2938d9..bd59aaacf 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-node": "~4.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~3.3.0", + "mocha": "~3.4.1", "nyc": "~10.3.0", "utf-8-validate": "~3.0.0" } From 68f12aa0cbb4facc64a87bb0bb2ee80e99acecbd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 10 May 2017 07:44:07 +0200 Subject: [PATCH 302/489] [major] Remove unnecessary events (#1100) --- lib/WebSocketServer.js | 1 - test/WebSocketServer.test.js | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index bd3ef24e3..b3d82df67 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -85,7 +85,6 @@ class WebSocketServer extends EventEmitter { this._ultron.on('error', (err) => this.emit('error', err)); this._ultron.on('upgrade', (req, socket, head) => { this.handleUpgrade(req, socket, head, (client) => { - this.emit(`connection${req.url}`, client); this.emit('connection', client); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 801db7d6a..de081a5bb 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -105,20 +105,6 @@ describe('WebSocketServer', function () { }); }); - it('emits path specific connection event', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws://localhost:${port}/endpointName`); - - wss.on('connection/endpointName', (ws) => { - wss.close(); - server.close(done); - }); - }); - }); - it('will not crash when it receives an unhandled opcode', function (done) { const wss = new WebSocketServer({ port: ++port }); From c15118f0636d81497db9ad34663efc98b9ddc9c3 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 11 May 2017 14:42:51 +0200 Subject: [PATCH 303/489] [major] Remove the `flags` argument (#1101) The `flags` object specifies whether the received data is binary or not and whether it was masked or not. This object is actually redundant and therefore removed in this commit as the spec requires client-sent frames to be masked and server-sent frame to be unmasked. The `typeof` operator can be used to check whether the received data is binary or not. --- README.md | 9 ++++----- bench/speed.js | 2 +- doc/ws.md | 9 --------- examples/fileapi/server.js | 4 ++-- lib/EventTarget.js | 8 +++----- lib/Receiver.js | 14 ++++++-------- lib/WebSocket.js | 8 ++++---- test/WebSocket.test.js | 36 +++++++++++------------------------- test/WebSocketServer.test.js | 2 +- 9 files changed, 32 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index cf3ccfbe6..66176150e 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,8 @@ ws.on('open', function open() { ws.send('something'); }); -ws.on('message', function incoming(data, flags) { - // flags.binary will be set if a binary data is received. - // flags.masked will be set if the data was masked. +ws.on('message', function incoming(data) { + console.log(data); }); ``` @@ -232,8 +231,8 @@ ws.on('close', function close() { console.log('disconnected'); }); -ws.on('message', function incoming(data, flags) { - console.log(`Roundtrip time: ${Date.now() - data} ms`, flags); +ws.on('message', function incoming(data) { + console.log(`Roundtrip time: ${Date.now() - data} ms`); setTimeout(function timeout() { ws.send(Date.now()); diff --git a/bench/speed.js b/bench/speed.js index b22e66883..cae60a2bf 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -17,7 +17,7 @@ if (cluster.isMaster) { }, () => cluster.fork()); wss.on('connection', (ws) => { - ws.on('message', (data, flags) => ws.send(data, { binary: flags.binary || false })); + ws.on('message', (data) => ws.send(data)); }); cluster.on('exit', () => wss.close()); diff --git a/doc/ws.md b/doc/ws.md index cdb1f03e5..4a7db0fc6 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -238,9 +238,6 @@ handshake. This allows you to read headers from the server, for example ### Event: 'message' - `data` {String|Buffer} -- `flags` {Object} - - `binary` {Boolean} Specifies if `data` is binary. - - `masked` {Boolean} Specifies if `data` was masked. Emitted when a message is received from the server. @@ -251,18 +248,12 @@ Emitted when the connection is established. ### Event: 'ping' - `data` {Buffer} -- `flags` {Object} - - `binary` {Boolean} Specifies if `data` is binary. - - `masked` {Boolean} Specifies if `data` was masked. Emitted when a ping is received from the server. ### Event: 'pong' - `data` {Buffer} -- `flags` {Object} - - `binary` {Boolean} Specifies if `data` is binary. - - `masked` {Boolean} Specifies if `data` was masked. Emitted when a pong is received from the server. diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index 4202818a0..54e6128f8 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -60,8 +60,8 @@ wss.on('connection', function (ws) { var filesReceived = 0; var currentFile = null; - ws.on('message', function (data, flags) { - if (!flags.binary) { + ws.on('message', function (data) { + if (typeof data === 'string') { currentFile = JSON.parse(data); // note: a real-world app would want to sanity check the data } else { diff --git a/lib/EventTarget.js b/lib/EventTarget.js index e30b1b3ed..8f9a943a8 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -29,13 +29,11 @@ class MessageEvent extends Event { * Create a new `MessageEvent`. * * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data - * @param {Boolean} isBinary Specifies if `data` is binary * @param {WebSocket} target A reference to the target to which the event was dispatched */ - constructor (data, isBinary, target) { + constructor (data, target) { super('message', target); - this.binary = isBinary; // non-standard. this.data = data; } } @@ -99,8 +97,8 @@ const EventTarget = { addEventListener (method, listener) { if (typeof listener !== 'function') return; - function onMessage (data, flags) { - listener.call(this, new MessageEvent(data, !!flags.binary, this)); + function onMessage (data) { + listener.call(this, new MessageEvent(data, this)); } function onClose (code, message) { diff --git a/lib/Receiver.js b/lib/Receiver.js index 6c1a10e2c..7054ca469 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -377,7 +377,7 @@ class Receiver { data = fragments; } - this.onmessage(data, { masked: this.masked, binary: true }); + this.onmessage(data); } else { const buf = toBuffer(fragments, messageLength); @@ -386,7 +386,7 @@ class Receiver { return; } - this.onmessage(buf.toString(), { masked: this.masked }); + this.onmessage(buf.toString()); } } @@ -402,7 +402,7 @@ class Receiver { controlMessage (data) { if (this.opcode === 0x08) { if (data.length === 0) { - this.onclose(1000, '', { masked: this.masked }); + this.onclose(1000, ''); this.loop = false; this.cleanup(this.cleanupCallback); } else if (data.length === 1) { @@ -422,7 +422,7 @@ class Receiver { return; } - this.onclose(code, buf.toString(), { masked: this.masked }); + this.onclose(code, buf.toString()); this.loop = false; this.cleanup(this.cleanupCallback); } @@ -430,10 +430,8 @@ class Receiver { return; } - const flags = { masked: this.masked, binary: true }; - - if (this.opcode === 0x09) this.onping(data, flags); - else this.onpong(data, flags); + if (this.opcode === 0x09) this.onping(data); + else this.onpong(data); this.state = GET_INFO; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 41868d832..1c82fa438 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -143,12 +143,12 @@ class WebSocket extends EventEmitter { }); // receiver event handlers - this._receiver.onmessage = (data, flags) => this.emit('message', data, flags); - this._receiver.onping = (data, flags) => { + this._receiver.onmessage = (data) => this.emit('message', data); + this._receiver.onping = (data) => { this.pong(data, !this._isServer, true); - this.emit('ping', data, flags); + this.emit('ping', data); }; - this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); + this._receiver.onpong = (data) => this.emit('pong', data); this._receiver.onclose = (code, reason) => { this._closeMessage = reason; this._closeCode = code; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 7f93f981a..8da4dec94 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -674,8 +674,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { compress: false })); - ws.on('message', (msg, flags) => { - assert.ok(flags.binary); + ws.on('message', (msg) => { assert.ok(msg.equals(Buffer.from(array.buffer))); wss.close(done); }); @@ -691,7 +690,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi')); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); wss.close(done); }); @@ -750,8 +749,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(partial, { binary: true })); - ws.on('message', (message, flags) => { - assert.ok(flags.binary); + ws.on('message', (message) => { assert.ok(message.equals(buf)); wss.close(done); }); @@ -768,8 +766,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(buf, { binary: true })); - ws.on('message', (message, flags) => { - assert.ok(flags.binary); + ws.on('message', (message) => { assert.ok(message.equals(buf)); wss.close(done); }); @@ -792,7 +789,6 @@ describe('WebSocket', function () { ws.on('open', () => ws.send(array.buffer)); ws.onmessage = (event) => { - assert.ok(event.binary); assert.ok(event.data.equals(Buffer.from(array.buffer))); wss.close(done); }; @@ -811,7 +807,6 @@ describe('WebSocket', function () { ws.on('open', () => ws.send(buf)); ws.onmessage = (event) => { - assert.ok(event.binary); assert.ok(event.data.equals(buf)); wss.close(done); }; @@ -877,9 +872,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - assert.ok(!flags.masked); wss.close(done); }); }); @@ -893,9 +887,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - assert.ok(flags.masked); wss.close(done); }); }); @@ -915,9 +908,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - assert.ok(flags.binary); wss.close(done); }); }); @@ -937,10 +929,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - assert.ok(flags.binary); - assert.ok(flags.masked); wss.close(done); }); }); @@ -1493,13 +1483,12 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.send('hi')); }); - it('should pass binary data as a node.js Buffer by default', function (done) { + it('should pass binary data as a Node.js Buffer by default', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.onmessage = (evt) => { assert.ok(Buffer.isBuffer(evt.data)); - assert.ok(evt.binary); wss.close(done); }; }); @@ -1515,7 +1504,6 @@ describe('WebSocket', function () { ws.onmessage = (evt) => { assert.ok(evt.data instanceof ArrayBuffer); - assert.ok(evt.binary); wss.close(done); }; }); @@ -1531,7 +1519,6 @@ describe('WebSocket', function () { ws.onmessage = (evt) => { assert.strictEqual(evt.data, 'foo'); - assert.ok(!evt.binary); wss.close(done); }; }); @@ -1655,7 +1642,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ server }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'foobar'); server.close(done); wss.close(); @@ -1691,8 +1678,7 @@ describe('WebSocket', function () { }); ws.on('open', () => ws.send(buf)); - ws.on('message', (message, flags) => { - assert.strictEqual(flags.binary, true); + ws.on('message', (message) => { assert.ok(buf.equals(message)); server.close(done); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index de081a5bb..03cb1fb39 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -890,7 +890,7 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('message', (message, flags) => ws.send(message)); + ws.on('message', (message) => ws.send(message)); }); wss.on('connection', (client) => { From d2c848120f4eb97913ce266df69debbb4c8eefe8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 11 May 2017 14:43:06 +0200 Subject: [PATCH 304/489] [major] Remove the `upgradeReq` property (#1104) The `http.IncomingMessage` object, instead of being attached to the `WebSocket` object, is passes as the second argument to the `connection` event. --- README.md | 14 +++++++------- doc/ws.md | 12 ++++-------- examples/express-session-parse/index.js | 6 ++---- lib/WebSocket.js | 5 ++--- lib/WebSocketServer.js | 4 ++-- test/WebSocket.test.js | 8 ++++---- test/WebSocketServer.test.js | 17 +++-------------- 7 files changed, 24 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 66176150e..bbe86e7d3 100644 --- a/README.md +++ b/README.md @@ -196,10 +196,10 @@ app.use(function (req, res) { const server = http.createServer(app); const wss = new WebSocket.Server({ server }); -wss.on('connection', function connection(ws) { - const location = url.parse(ws.upgradeReq.url, true); +wss.on('connection', function connection(ws, req) { + const location = url.parse(req.url, true); // You might use location.query.access_token to authenticate or share sessions - // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) + // or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312) ws.on('message', function incoming(message) { console.log('received: %s', message); @@ -279,8 +279,8 @@ const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); -wss.on('connection', function connection(ws) { - const ip = ws.upgradeReq.connection.remoteAddress; +wss.on('connection', function connection(ws, req) { + const ip = req.connection.remoteAddress; }); ``` @@ -288,8 +288,8 @@ When the server runs behing a proxy like NGINX, the de-facto standard is to use the `X-Forwarded-For` header. ```js -wss.on('connection', function connection(ws) { - const ip = ws.upgradeReq.headers['x-forwarded-for']; +wss.on('connection', function connection(ws, req) { + const ip = req.headers['x-forwarded-for']; }); ``` diff --git a/doc/ws.md b/doc/ws.md index 4a7db0fc6..d8ca9d111 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -89,8 +89,11 @@ provided. ### Event: 'connection' - `socket` {WebSocket} +- `request` {http.IncomingMessage} -Emitted when the handshake is complete. `socket` is an instance of `WebSocket`. +Emitted when the handshake is complete. `request` is the http GET request sent +by the client. Useful for parsing authority headers, cookie headers, and other +information. ### Event: 'error' @@ -414,13 +417,6 @@ Send `data` through the connection. Forcibly close the connection. -### websocket.upgradeReq - -- {http.IncomingMessage} - -The http GET request sent by the client. Useful for parsing authority headers, -cookie headers, and other information. This is only available for server clients. - ### websocket.url - {String} diff --git a/examples/express-session-parse/index.js b/examples/express-session-parse/index.js index bac2d8e97..5250b2cb6 100644 --- a/examples/express-session-parse/index.js +++ b/examples/express-session-parse/index.js @@ -63,14 +63,12 @@ const wss = new WebSocket.Server({ server }); -wss.on('connection', (ws) => { +wss.on('connection', (ws, req) => { ws.on('message', (message) => { - const session = ws.upgradeReq.session; - // // Here we can now use session parameters. // - console.log(`WS message ${message} from user ${session.userId}`); + console.log(`WS message ${message} from user ${req.session.userId}`); }); }); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 1c82fa438..38e6e4eb5 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -65,7 +65,7 @@ class WebSocket extends EventEmitter { this._ultron = null; if (Array.isArray(address)) { - initAsServerClient.call(this, address[0], address[1], address[2], options); + initAsServerClient.call(this, address[0], address[1], options); } else { initAsClient.call(this, address, protocols, options); } @@ -455,13 +455,12 @@ module.exports = WebSocket; * @param {String} options.protocol The chosen subprotocol * @private */ -function initAsServerClient (req, socket, head, options) { +function initAsServerClient (socket, head, options) { this.protocolVersion = options.protocolVersion; this.extensions = options.extensions; this.maxPayload = options.maxPayload; this.protocol = options.protocol; - this.upgradeReq = req; this._isServer = true; this.setSocket(socket, head); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index b3d82df67..a76e96cbb 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -85,7 +85,7 @@ class WebSocketServer extends EventEmitter { this._ultron.on('error', (err) => this.emit('error', err)); this._ultron.on('upgrade', (req, socket, head) => { this.handleUpgrade(req, socket, head, (client) => { - this.emit('connection', client); + this.emit('connection', client, req); }); }); } @@ -255,7 +255,7 @@ class WebSocketServer extends EventEmitter { socket.write(headers.concat('', '').join('\r\n')); - const client = new WebSocket([req, socket, head], null, { + const client = new WebSocket([socket, head], null, { maxPayload: this.options.maxPayload, protocolVersion: version, extensions, diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 8da4dec94..d42f7431a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -77,8 +77,8 @@ describe('WebSocket', function () { }); }); - wss.on('connection', (ws) => { - assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '127.0.0.2'); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); wss.close(done); }); }); @@ -95,8 +95,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); - wss.on('connection', (ws) => { - assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '::1'); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '::1'); wss.close(done); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 03cb1fb39..4a71823ae 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -90,11 +90,11 @@ describe('WebSocketServer', function () { server.listen(sockPath, () => { const wss = new WebSocketServer({ server }); - wss.on('connection', (ws) => { + wss.on('connection', (ws, req) => { if (wss.clients.size === 1) { - assert.strictEqual(ws.upgradeReq.url, '/foo?bar=bar'); + assert.strictEqual(req.url, '/foo?bar=bar'); } else { - assert.strictEqual(ws.upgradeReq.url, '/'); + assert.strictEqual(req.url, '/'); wss.close(); server.close(done); } @@ -926,17 +926,6 @@ describe('WebSocketServer', function () { wss.close(done); }); }); - - it('upgradeReq is the original request object', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client.upgradeReq.httpVersion, '1.1'); - wss.close(done); - }); - }); }); describe('permessage-deflate', function () { From 439dbce25d7ef446321495bea77dbcd7646d9d81 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 14 May 2017 06:56:25 +0200 Subject: [PATCH 305/489] [major] Prefix all private properties (#1105) --- lib/PerMessageDeflate.js | 9 +- lib/Receiver.js | 234 +++++++++++++++++------------------ lib/Sender.js | 58 +++++---- lib/WebSocket.js | 8 +- lib/WebSocketServer.js | 1 - test/Receiver.test.js | 38 +++--- test/WebSocketServer.test.js | 8 +- 7 files changed, 181 insertions(+), 175 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c1a1d3c70..17bbf75ed 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -18,13 +18,16 @@ const DEFAULT_MEM_LEVEL = 8; */ class PerMessageDeflate { constructor (options, isServer, maxPayload) { + this._maxPayload = maxPayload | 0; this._options = options || {}; + this._threshold = this._options.threshold !== undefined + ? this._options.threshold + : 1024; this._isServer = !!isServer; - this._inflate = null; this._deflate = null; + this._inflate = null; + this.params = null; - this._maxPayload = maxPayload || 0; - this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold; } static get extensionName () { diff --git a/lib/Receiver.js b/lib/Receiver.js index 7054ca469..91196706c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -35,29 +35,29 @@ class Receiver { * @param {String} binaryType The type for binary data */ constructor (extensions, maxPayload, binaryType) { - this.binaryType = binaryType || constants.BINARY_TYPES[0]; - this.extensions = extensions || {}; - this.maxPayload = maxPayload | 0; - - this.bufferedBytes = 0; - this.buffers = []; - - this.compressed = false; - this.payloadLength = 0; - this.fragmented = 0; - this.masked = false; - this.fin = false; - this.mask = null; - this.opcode = 0; - - this.totalPayloadLength = 0; - this.messageLength = 0; - this.fragments = []; - - this.cleanupCallback = null; - this.hadError = false; - this.dead = false; - this.loop = false; + this._binaryType = binaryType || constants.BINARY_TYPES[0]; + this._extensions = extensions || {}; + this._maxPayload = maxPayload | 0; + + this._bufferedBytes = 0; + this._buffers = []; + + this._compressed = false; + this._payloadLength = 0; + this._fragmented = 0; + this._masked = false; + this._fin = false; + this._mask = null; + this._opcode = 0; + + this._totalPayloadLength = 0; + this._messageLength = 0; + this._fragments = []; + + this._cleanupCallback = null; + this._hadError = false; + this._dead = false; + this._loop = false; this.onmessage = null; this.onclose = null; @@ -65,7 +65,7 @@ class Receiver { this.onping = null; this.onpong = null; - this.state = GET_INFO; + this._state = GET_INFO; } /** @@ -80,28 +80,28 @@ class Receiver { var dst; var l; - this.bufferedBytes -= bytes; + this._bufferedBytes -= bytes; - if (bytes === this.buffers[0].length) return this.buffers.shift(); + if (bytes === this._buffers[0].length) return this._buffers.shift(); - if (bytes < this.buffers[0].length) { - dst = this.buffers[0].slice(0, bytes); - this.buffers[0] = this.buffers[0].slice(bytes); + if (bytes < this._buffers[0].length) { + dst = this._buffers[0].slice(0, bytes); + this._buffers[0] = this._buffers[0].slice(bytes); return dst; } dst = Buffer.allocUnsafe(bytes); while (bytes > 0) { - l = this.buffers[0].length; + l = this._buffers[0].length; if (bytes >= l) { - this.buffers[0].copy(dst, offset); + this._buffers[0].copy(dst, offset); offset += l; - this.buffers.shift(); + this._buffers.shift(); } else { - this.buffers[0].copy(dst, offset, 0, bytes); - this.buffers[0] = this.buffers[0].slice(bytes); + this._buffers[0].copy(dst, offset, 0, bytes); + this._buffers[0] = this._buffers[0].slice(bytes); } bytes -= l; @@ -119,10 +119,10 @@ class Receiver { * @private */ hasBufferedBytes (n) { - if (this.bufferedBytes >= n) return true; + if (this._bufferedBytes >= n) return true; - this.loop = false; - if (this.dead) this.cleanup(this.cleanupCallback); + this._loop = false; + if (this._dead) this.cleanup(this._cleanupCallback); return false; } @@ -132,10 +132,10 @@ class Receiver { * @public */ add (data) { - if (this.dead) return; + if (this._dead) return; - this.bufferedBytes += data.length; - this.buffers.push(data); + this._bufferedBytes += data.length; + this._buffers.push(data); this.startLoop(); } @@ -145,10 +145,10 @@ class Receiver { * @private */ startLoop () { - this.loop = true; + this._loop = true; - while (this.loop) { - switch (this.state) { + while (this._loop) { + switch (this._state) { case GET_INFO: this.getInfo(); break; @@ -165,7 +165,7 @@ class Receiver { this.getData(); break; default: // `INFLATING` - this.loop = false; + this._loop = false; } } } @@ -187,36 +187,36 @@ class Receiver { const compressed = (buf[0] & 0x40) === 0x40; - if (compressed && !this.extensions[PerMessageDeflate.extensionName]) { + if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { this.error(new Error('RSV1 must be clear'), 1002); return; } - this.fin = (buf[0] & 0x80) === 0x80; - this.opcode = buf[0] & 0x0f; - this.payloadLength = buf[1] & 0x7f; + this._fin = (buf[0] & 0x80) === 0x80; + this._opcode = buf[0] & 0x0f; + this._payloadLength = buf[1] & 0x7f; - if (this.opcode === 0x00) { + if (this._opcode === 0x00) { if (compressed) { this.error(new Error('RSV1 must be clear'), 1002); return; } - if (!this.fragmented) { - this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + if (!this._fragmented) { + this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); return; } else { - this.opcode = this.fragmented; + this._opcode = this._fragmented; } - } else if (this.opcode === 0x01 || this.opcode === 0x02) { - if (this.fragmented) { - this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + } else if (this._opcode === 0x01 || this._opcode === 0x02) { + if (this._fragmented) { + this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); return; } - this.compressed = compressed; - } else if (this.opcode > 0x07 && this.opcode < 0x0b) { - if (!this.fin) { + this._compressed = compressed; + } else if (this._opcode > 0x07 && this._opcode < 0x0b) { + if (!this._fin) { this.error(new Error('FIN must be set'), 1002); return; } @@ -226,21 +226,21 @@ class Receiver { return; } - if (this.payloadLength > 0x7d) { + if (this._payloadLength > 0x7d) { this.error(new Error('invalid payload length'), 1002); return; } } else { - this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); return; } - if (!this.fin && !this.fragmented) this.fragmented = this.opcode; + if (!this._fin && !this._fragmented) this._fragmented = this._opcode; - this.masked = (buf[1] & 0x80) === 0x80; + this._masked = (buf[1] & 0x80) === 0x80; - if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16; - else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64; + if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16; + else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64; else this.haveLength(); } @@ -252,7 +252,7 @@ class Receiver { getPayloadLength16 () { if (!this.hasBufferedBytes(2)) return; - this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); + this._payloadLength = this.readBuffer(2).readUInt16BE(0, true); this.haveLength(); } @@ -276,7 +276,7 @@ class Receiver { return; } - this.payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true); + this._payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true); this.haveLength(); } @@ -286,12 +286,12 @@ class Receiver { * @private */ haveLength () { - if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) { + if (this._opcode < 0x08 && this.maxPayloadExceeded(this._payloadLength)) { return; } - if (this.masked) this.state = GET_MASK; - else this.state = GET_DATA; + if (this._masked) this._state = GET_MASK; + else this._state = GET_DATA; } /** @@ -302,8 +302,8 @@ class Receiver { getMask () { if (!this.hasBufferedBytes(4)) return; - this.mask = this.readBuffer(4); - this.state = GET_DATA; + this._mask = this.readBuffer(4); + this._state = GET_DATA; } /** @@ -314,17 +314,17 @@ class Receiver { getData () { var data = constants.EMPTY_BUFFER; - if (this.payloadLength) { - if (!this.hasBufferedBytes(this.payloadLength)) return; + if (this._payloadLength) { + if (!this.hasBufferedBytes(this._payloadLength)) return; - data = this.readBuffer(this.payloadLength); - if (this.masked) bufferUtil.unmask(data, this.mask); + data = this.readBuffer(this._payloadLength); + if (this._masked) bufferUtil.unmask(data, this._mask); } - if (this.opcode > 0x07) { + if (this._opcode > 0x07) { this.controlMessage(data); - } else if (this.compressed) { - this.state = INFLATING; + } else if (this._compressed) { + this._state = INFLATING; this.decompress(data); } else if (this.pushFragment(data)) { this.dataMessage(); @@ -338,9 +338,9 @@ class Receiver { * @private */ decompress (data) { - const extension = this.extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; - extension.decompress(data, this.fin, (err, buf) => { + perMessageDeflate.decompress(data, this._fin, (err, buf) => { if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; @@ -357,21 +357,21 @@ class Receiver { * @private */ dataMessage () { - if (this.fin) { - const messageLength = this.messageLength; - const fragments = this.fragments; + if (this._fin) { + const messageLength = this._messageLength; + const fragments = this._fragments; - this.totalPayloadLength = 0; - this.messageLength = 0; - this.fragmented = 0; - this.fragments = []; + this._totalPayloadLength = 0; + this._messageLength = 0; + this._fragmented = 0; + this._fragments = []; - if (this.opcode === 2) { + if (this._opcode === 2) { var data; - if (this.binaryType === 'nodebuffer') { + if (this._binaryType === 'nodebuffer') { data = toBuffer(fragments, messageLength); - } else if (this.binaryType === 'arraybuffer') { + } else if (this._binaryType === 'arraybuffer') { data = toArrayBuffer(toBuffer(fragments, messageLength)); } else { data = fragments; @@ -390,7 +390,7 @@ class Receiver { } } - this.state = GET_INFO; + this._state = GET_INFO; } /** @@ -400,11 +400,11 @@ class Receiver { * @private */ controlMessage (data) { - if (this.opcode === 0x08) { + if (this._opcode === 0x08) { if (data.length === 0) { this.onclose(1000, ''); - this.loop = false; - this.cleanup(this.cleanupCallback); + this._loop = false; + this.cleanup(this._cleanupCallback); } else if (data.length === 1) { this.error(new Error('invalid payload length'), 1002); } else { @@ -423,17 +423,17 @@ class Receiver { } this.onclose(code, buf.toString()); - this.loop = false; - this.cleanup(this.cleanupCallback); + this._loop = false; + this.cleanup(this._cleanupCallback); } return; } - if (this.opcode === 0x09) this.onping(data); + if (this._opcode === 0x09) this.onping(data); else this.onpong(data); - this.state = GET_INFO; + this._state = GET_INFO; } /** @@ -445,9 +445,9 @@ class Receiver { */ error (err, code) { this.onerror(err, code); - this.hadError = true; - this.loop = false; - this.cleanup(this.cleanupCallback); + this._hadError = true; + this._loop = false; + this.cleanup(this._cleanupCallback); } /** @@ -457,12 +457,12 @@ class Receiver { * @private */ maxPayloadExceeded (length) { - if (length === 0 || this.maxPayload < 1) return false; + if (length === 0 || this._maxPayload < 1) return false; - const fullLength = this.totalPayloadLength + length; + const fullLength = this._totalPayloadLength + length; - if (fullLength <= this.maxPayload) { - this.totalPayloadLength = fullLength; + if (fullLength <= this._maxPayload) { + this._totalPayloadLength = fullLength; return false; } @@ -481,11 +481,11 @@ class Receiver { pushFragment (fragment) { if (fragment.length === 0) return true; - const totalLength = this.messageLength + fragment.length; + const totalLength = this._messageLength + fragment.length; - if (this.maxPayload < 1 || totalLength <= this.maxPayload) { - this.messageLength = totalLength; - this.fragments.push(fragment); + if (this._maxPayload < 1 || totalLength <= this._maxPayload) { + this._messageLength = totalLength; + this._fragments.push(fragment); return true; } @@ -500,17 +500,17 @@ class Receiver { * @public */ cleanup (cb) { - this.dead = true; + this._dead = true; - if (!this.hadError && (this.loop || this.state === INFLATING)) { - this.cleanupCallback = cb; + if (!this._hadError && (this._loop || this._state === INFLATING)) { + this._cleanupCallback = cb; } else { - this.extensions = null; - this.fragments = null; - this.buffers = null; - this.mask = null; + this._extensions = null; + this._fragments = null; + this._buffers = null; + this._mask = null; - this.cleanupCallback = null; + this._cleanupCallback = null; this.onmessage = null; this.onclose = null; this.onerror = null; diff --git a/lib/Sender.js b/lib/Sender.js index 79e68a5a3..d3502fe16 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -26,15 +26,15 @@ class Sender { * @param {Object} extensions An object containing the negotiated extensions */ constructor (socket, extensions) { - this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName]; + this._extensions = extensions || {}; this._socket = socket; - this.firstFragment = true; - this.compress = false; + this._firstFragment = true; + this._compress = false; - this.bufferedBytes = 0; - this.deflating = false; - this.queue = []; + this._bufferedBytes = 0; + this._deflating = false; + this._queue = []; this.onerror = null; } @@ -123,7 +123,7 @@ class Sender { buf.writeUInt16BE(code || 1000, 0, true); if (buf.length > 2) buf.write(data, 2); - if (this.deflating) { + if (this._deflating) { this.enqueue([this.doClose, buf, mask, cb]); } else { this.doClose(buf, mask, cb); @@ -169,7 +169,7 @@ class Sender { } } - if (this.deflating) { + if (this._deflating) { this.enqueue([this.doPing, data, mask, readOnly]); } else { this.doPing(data, mask, readOnly); @@ -215,7 +215,7 @@ class Sender { } } - if (this.deflating) { + if (this._deflating) { this.enqueue([this.doPong, data, mask, readOnly]); } else { this.doPong(data, mask, readOnly); @@ -268,20 +268,22 @@ class Sender { } } - if (this.firstFragment) { - this.firstFragment = false; - if (rsv1 && this.perMessageDeflate) { - rsv1 = data.length >= this.perMessageDeflate.threshold; + const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + + if (this._firstFragment) { + this._firstFragment = false; + if (rsv1 && perMessageDeflate) { + rsv1 = data.length >= perMessageDeflate._threshold; } - this.compress = rsv1; + this._compress = rsv1; } else { rsv1 = false; opcode = 0; } - if (options.fin) this.firstFragment = true; + if (options.fin) this._firstFragment = true; - if (this.perMessageDeflate) { + if (perMessageDeflate) { const opts = { fin: options.fin, rsv1, @@ -290,10 +292,10 @@ class Sender { readOnly }; - if (this.deflating) { - this.enqueue([this.dispatch, data, this.compress, opts, cb]); + if (this._deflating) { + this.enqueue([this.dispatch, data, this._compress, opts, cb]); } else { - this.dispatch(data, this.compress, opts, cb); + this.dispatch(data, this._compress, opts, cb); } } else { this.sendFrame(Sender.frame(data, { @@ -326,8 +328,10 @@ class Sender { return; } - this.deflating = true; - this.perMessageDeflate.compress(data, options.fin, (err, buf) => { + const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + + this._deflating = true; + perMessageDeflate.compress(data, options.fin, (err, buf) => { if (err) { if (cb) cb(err); else this.onerror(err); @@ -336,7 +340,7 @@ class Sender { options.readOnly = false; this.sendFrame(Sender.frame(buf, options), cb); - this.deflating = false; + this._deflating = false; this.dequeue(); }); } @@ -347,10 +351,10 @@ class Sender { * @private */ dequeue () { - while (!this.deflating && this.queue.length) { - const params = this.queue.shift(); + while (!this._deflating && this._queue.length) { + const params = this._queue.shift(); - this.bufferedBytes -= params[1].length; + this._bufferedBytes -= params[1].length; params[0].apply(this, params.slice(1)); } } @@ -362,8 +366,8 @@ class Sender { * @private */ enqueue (params) { - this.bufferedBytes += params[1].length; - this.queue.push(params); + this._bufferedBytes += params[1].length; + this._queue.push(params); } /** diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 38e6e4eb5..29180ea0f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -83,7 +83,7 @@ class WebSocket extends EventEmitter { var amount = 0; if (this._socket) { - amount = this._socket.bufferSize + this._sender.bufferedBytes; + amount = this._socket.bufferSize + this._sender._bufferedBytes; } return amount; } @@ -106,7 +106,7 @@ class WebSocket extends EventEmitter { // // Allow to change `binaryType` on the fly. // - if (this._receiver) this._receiver.binaryType = type; + if (this._receiver) this._receiver._binaryType = type; } /** @@ -120,7 +120,7 @@ class WebSocket extends EventEmitter { socket.setTimeout(0); socket.setNoDelay(); - this._receiver = new Receiver(this.extensions, this.maxPayload, this.binaryType); + this._receiver = new Receiver(this.extensions, this._maxPayload, this.binaryType); this._sender = new Sender(socket, this.extensions); this._ultron = new Ultron(socket); this._socket = socket; @@ -457,8 +457,8 @@ module.exports = WebSocket; */ function initAsServerClient (socket, head, options) { this.protocolVersion = options.protocolVersion; + this._maxPayload = options.maxPayload; this.extensions = options.extensions; - this.maxPayload = options.maxPayload; this.protocol = options.protocol; this._isServer = true; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index a76e96cbb..3735f990b 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -92,7 +92,6 @@ class WebSocketServer extends EventEmitter { if (options.clientTracking) this.clients = new Set(); this.options = options; - this.path = options.path; } /** diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 6caed81a5..ffd579e00 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -338,9 +338,9 @@ describe('Receiver', function () { message = msg; }; - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); p.add(Buffer.from('810548656c6c6f', 'hex')); - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); assert.strictEqual(message, 'Hello'); }); @@ -352,11 +352,11 @@ describe('Receiver', function () { message = msg; }; - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); p.add(Buffer.from('01024865', 'hex')); - assert.strictEqual(p.totalPayloadLength, 2); + assert.strictEqual(p._totalPayloadLength, 2); p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); assert.strictEqual(message, 'Hello'); }); @@ -368,13 +368,13 @@ describe('Receiver', function () { data.push(buf.toString()); }; - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); p.add(Buffer.from('02024865', 'hex')); - assert.strictEqual(p.totalPayloadLength, 2); + assert.strictEqual(p._totalPayloadLength, 2); p.add(Buffer.from('8900', 'hex')); - assert.strictEqual(p.totalPayloadLength, 2); + assert.strictEqual(p._totalPayloadLength, 2); p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); assert.deepStrictEqual(data, ['', 'Hello']); }); @@ -722,8 +722,8 @@ describe('Receiver', function () { p.add(frame); p.add(frame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, frame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, frame.length); p.cleanup(() => { assert.deepStrictEqual(results, ['Hello', 'Hello']); @@ -754,8 +754,8 @@ describe('Receiver', function () { p.add(textFrame); p.add(closeFrame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, textFrame.length + closeFrame.length); p.cleanup(() => { assert.deepStrictEqual(results, ['Hello', 'Hello', 1000, '']); @@ -786,8 +786,8 @@ describe('Receiver', function () { p.add(textFrame); p.add(invalidFrame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, textFrame.length + invalidFrame.length); p.cleanup(() => { assert.deepStrictEqual(results, [ @@ -821,8 +821,8 @@ describe('Receiver', function () { p.add(textFrame); p.add(incompleteFrame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, incompleteFrame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, incompleteFrame.length); p.cleanup(() => { assert.deepStrictEqual(results, ['Hello']); @@ -867,7 +867,7 @@ describe('Receiver', function () { crypto.randomBytes(623987) ]; - p.binaryType = 'arraybuffer'; + p._binaryType = 'arraybuffer'; p.onmessage = (data) => { assert.ok(data instanceof ArrayBuffer); assert.ok(Buffer.from(data).equals(Buffer.concat(frags))); @@ -895,7 +895,7 @@ describe('Receiver', function () { crypto.randomBytes(1) ]; - p.binaryType = 'fragments'; + p._binaryType = 'fragments'; p.onmessage = (data) => { assert.deepStrictEqual(data, frags); done(); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 4a71823ae..a2916e25f 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -256,14 +256,14 @@ describe('WebSocketServer', function () { }); describe('#maxpayload', function () { - it('maxpayload is passed on to clients,', function (done) { + it('maxpayload is passed on to clients', function (done) { const maxPayload = 20480; const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); wss.on('connection', (client) => { - assert.strictEqual(client.maxPayload, maxPayload); + assert.strictEqual(client._maxPayload, maxPayload); wss.close(done); }); }); @@ -275,7 +275,7 @@ describe('WebSocketServer', function () { }); wss.on('connection', (client) => { - assert.strictEqual(client._receiver.maxPayload, maxPayload); + assert.strictEqual(client._receiver._maxPayload, maxPayload); wss.close(done); }); }); @@ -289,7 +289,7 @@ describe('WebSocketServer', function () { wss.on('connection', (client) => { assert.strictEqual( - client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload, + client._receiver._extensions[PerMessageDeflate.extensionName]._maxPayload, maxPayload ); wss.close(done); From fe44ff81cc414495f19416847ce658cbea079d06 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 16 May 2017 08:49:14 +0200 Subject: [PATCH 306/489] [major] Have permessage-deflate disabled by default (#1107) This commit disables permessage-deflate by default on the server. --- README.md | 22 ++++++---------------- doc/ws.md | 8 ++++---- lib/WebSocketServer.js | 2 +- test/WebSocket.test.js | 11 ++++++----- test/WebSocketServer.test.js | 23 +++++++++++++++++------ 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index bbe86e7d3..1b38327d6 100644 --- a/README.md +++ b/README.md @@ -73,23 +73,13 @@ enables the client and server to negotiate a compression algorithm and its parameters, and then selectively apply it to the data payloads of each WebSocket message. -The extension is enabled by default but adds a significant overhead in terms of -performance and memory comsumption. We suggest to use WebSocket compression -only if it is really needed. +The extension is disabled by default on the server and enabled by default on +the client. It adds a significant overhead in terms of performance and memory +comsumption so we suggest to enable it only if it is really needed. -To disable the extension you can set the `perMessageDeflate` option to `false`. -On the server: - -```js -const WebSocket = require('ws'); - -const wss = new WebSocket.Server({ - perMessageDeflate: false, - port: 8080 -}); -``` - -On the client: +The client will only use the extension if it is supported and enabled on the +server. To always disable the extension on the client set the +`perMessageDeflate` option to `false`. ```js const WebSocket = require('ws'); diff --git a/doc/ws.md b/doc/ws.md index d8ca9d111..21c8c13f0 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -63,7 +63,7 @@ status code, otherwise the returned value sets the value of the `perMessageDeflate` can be used to control the behavior of [permessage-deflate extension][permessage-deflate]. -The extension is disabled when `false`. Defaults to `true`. If an object is +The extension is disabled when `false` (default value). If an object is provided then that is extension parameters: - `serverNoContextTakeover` {Boolean} Whether to use context take over or not. @@ -189,9 +189,9 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `pfx` {String|Buffer} The private key, certificate, and CA certs. - `ca` {Array} Trusted certificates. -`perMessageDeflate` parameters are the same of the server, the only difference -is the direction of requests (e.g. `serverNoContextTakeover` is the value to be -requested to the server). +`perMessageDeflate` default value is `true`. When using an object, parameters +are the same of the server. The only difference is the direction of requests +(e.g. `serverNoContextTakeover` is the value to be requested to the server). Create a new WebSocket instance. diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 3735f990b..790add824 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -47,7 +47,7 @@ class WebSocketServer extends EventEmitter { options = Object.assign({ maxPayload: 100 * 1024 * 1024, - perMessageDeflate: true, + perMessageDeflate: false, handleProtocols: null, clientTracking: true, verifyClient: null, diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index d42f7431a..84fd09ea3 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -105,7 +105,7 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('message', () => { assert.strictEqual(ws.bytesReceived, 8); wss.close(done); @@ -150,7 +150,10 @@ describe('WebSocket', function () { }); it('takes into account the data in the sender queue', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -170,9 +173,7 @@ describe('WebSocket', function () { it('takes into account the data in the socket queue', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: false - }); + const ws = new WebSocket(`ws://localhost:${port}`); }); wss.on('connection', (ws) => { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index a2916e25f..83519527e 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -283,9 +283,11 @@ describe('WebSocketServer', function () { it('maxpayload is passed on to permessage-deflate', function (done) { const PerMessageDeflate = require('../lib/PerMessageDeflate'); const maxPayload = 20480; - const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - }); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port, + maxPayload + }, () => new WebSocket(`ws://localhost:${port}`)); wss.on('connection', (client) => { assert.strictEqual( @@ -930,7 +932,10 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('accept connections with permessage-deflate extension', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const req = http.request({ headers: { 'Connection': 'Upgrade', @@ -950,7 +955,10 @@ describe('WebSocketServer', function () { }); it('does not accept connections with not defined extension parameter', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const req = http.request({ headers: { 'Connection': 'Upgrade', @@ -977,7 +985,10 @@ describe('WebSocketServer', function () { }); it('does not accept connections with invalid extension parameter', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const req = http.request({ headers: { 'Connection': 'Upgrade', From e6f6eb1b1ab261c5058da54a7e4b79bba0648174 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 17 May 2017 07:39:17 +0200 Subject: [PATCH 307/489] [doc] Add missing argument types --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 21c8c13f0..207aa5afa 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -240,7 +240,7 @@ handshake. This allows you to read headers from the server, for example ### Event: 'message' -- `data` {String|Buffer} +- `data` {String|Buffer|ArrayBuffer|Buffer[]} Emitted when a message is received from the server. From 38df5a330aa91123851d8b49c231adf6c337ea77 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 17 May 2017 09:12:45 +0200 Subject: [PATCH 308/489] [dist] 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bd59aaacf..330b5d79a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "2.3.1", + "version": "3.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From dbab3fd0fd37d70b21639bfbde591380332e0c0e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 21 May 2017 10:18:41 +0200 Subject: [PATCH 309/489] [doc] Add FAQ about proxy support --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b38327d6..0e8129588 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) -`ws` is a simple to use, blazing fast, and thoroughly tested WebSocket client +ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation. Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/ @@ -35,6 +35,7 @@ communication. Browser clients must use the native * [FAQ](#faq) + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) + + [How to connect via a proxy?](#how-to-connect-via-a-proxy) * [Changelog](#changelog) * [License](#license) @@ -51,7 +52,7 @@ npm install --save ws ### Opt-in for performance and spec compliance -There are 2 optional modules that can be installed along side with the `ws` +There are 2 optional modules that can be installed along side with the ws module. These modules are binary addons which improve certain operations. Prebuilt binaries are available for the most popular platforms so you don't necessarily need to have a C++ compiler installed on your machine. @@ -68,7 +69,7 @@ See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes. ## WebSocket compression -`ws` supports the [permessage-deflate extension][permessage-deflate] which +ws supports the [permessage-deflate extension][permessage-deflate] which enables the client and server to negotiate a compression algorithm and its parameters, and then selectively apply it to the data payloads of each WebSocket message. @@ -319,6 +320,11 @@ const interval = setInterval(function ping() { Pong messages are automatically sent in reponse to ping messages as required by the spec. +### How to connect via a proxy? + +Use a custom `http.Agent` implementation like [https-proxy-agent][] or +[socks-proxy-agent][]. + ## Changelog We're using the GitHub [releases][changelog] for changelog entries. @@ -327,5 +333,7 @@ We're using the GitHub [releases][changelog] for changelog entries. [MIT](LICENSE) +[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent +[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent [permessage-deflate]: https://tools.ietf.org/html/rfc7692 [changelog]: https://github.com/websockets/ws/releases From 62cd03ea3705123136c20eedac1b57559d8ea542 Mon Sep 17 00:00:00 2001 From: Jussi Virtanen Date: Tue, 23 May 2017 07:40:24 +0200 Subject: [PATCH 310/489] [doc] Fix typos in API documentation (#1119) --- doc/ws.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 207aa5afa..2313ea9bd 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -201,14 +201,14 @@ Create a new WebSocket instance. following URL scheme: ``` -ws+unix:///absolule/path/to/uds_socket:/pathname?search_params +ws+unix:///absolute/path/to/uds_socket:/pathname?search_params ``` Note that `:` is the separator between the socket path and the URL path. If the URL path is omitted ``` -ws+unix:///absolule/path/to/uds_socket +ws+unix:///absolute/path/to/uds_socket ``` it defaults to `/`. @@ -359,7 +359,7 @@ Send a ping. ### websocket.pong([data[, mask[, failSilently]]]) -- `data` {Any} The data to send in the ping frame. +- `data` {Any} The data to send in the pong frame. - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. - `failSilently` {Boolean} Specifies whether or not to throw an error if the @@ -394,7 +394,7 @@ Removes an event listener emulating the `EventTarget` interface. ### websocket.resume() -Resume the socket +Resume the socket. ### websocket.send(data, [options][, callback]) From cb1c893ce94f3d800cd6e4166e6640bda24d74ab Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 24 May 2017 14:06:46 +0200 Subject: [PATCH 311/489] chore(package): update eslint-plugin-import to version 2.3.0 (#1121) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 330b5d79a..a4e764a2f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.2.0", + "eslint-plugin-import": "~2.3.0", "eslint-plugin-node": "~4.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 4edcc45cf001ac0f0036111b4e50d2a84b1dcffa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 27 May 2017 09:14:35 +0200 Subject: [PATCH 312/489] [minor] Merge two consecutive if statements --- lib/PerMessageDeflate.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 17bbf75ed..8e457db4e 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -139,13 +139,10 @@ class PerMessageDeflate { ) { accepted.server_no_context_takeover = true; } - if (this._options.clientNoContextTakeover) { - accepted.client_no_context_takeover = true; - } - if ( + if (this._options.clientNoContextTakeover || ( this._options.clientNoContextTakeover !== false && params.client_no_context_takeover - ) { + )) { accepted.client_no_context_takeover = true; } if (typeof this._options.serverMaxWindowBits === 'number') { @@ -164,7 +161,7 @@ class PerMessageDeflate { return true; }); - if (!result) throw new Error(`Doesn't support the offered configuration`); + if (!result) throw new Error("Doesn't support the offered configuration"); return accepted; } From a7bd1e3b4042cf6a4a4f06a53185e9306c66cf28 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 27 May 2017 09:47:08 +0200 Subject: [PATCH 313/489] [doc] Add ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..f75647683 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ + + +- [ ] I've searched for any related issues and avoided creating a duplicate issue. + +#### Description + + + +#### Reproducible in: + +version: +Node.js version(s): +OS version(s): + +#### Steps to reproduce: + +1. +2. +3. + +### Expected result: + + + +### Actual result: + + + +### Attachments: + + From 2e7f0b0f70393bd95f873d313423d24438198dc7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 28 May 2017 10:09:18 +0200 Subject: [PATCH 314/489] [minor] Use zlib module constants --- lib/PerMessageDeflate.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 8e457db4e..c1e08f092 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -7,11 +7,8 @@ const bufferUtil = require('./BufferUtil'); const Buffer = safeBuffer.Buffer; -const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); -const DEFAULT_WINDOW_BITS = 15; -const DEFAULT_MEM_LEVEL = 8; /** * Per-message Deflate implementation. @@ -232,7 +229,11 @@ class PerMessageDeflate { case 'client_max_window_bits': if (typeof value === 'string') { value = parseInt(value, 10); - if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + if ( + Number.isNaN(value) || + value < zlib.Z_MIN_WINDOWBITS || + value > zlib.Z_MAX_WINDOWBITS + ) { throw new Error(`invalid extension parameter value for ${key} (${value})`); } } @@ -261,10 +262,12 @@ class PerMessageDeflate { const endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { - const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; - this._inflate = zlib.createInflateRaw({ - windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS - }); + const key = `${endpoint}_max_window_bits`; + const windowBits = typeof this.params[key] !== 'number' + ? zlib.Z_DEFAULT_WINDOWBITS + : this.params[key]; + + this._inflate = zlib.createInflateRaw({ windowBits }); } this._inflate.writeInProgress = true; @@ -332,11 +335,15 @@ class PerMessageDeflate { const endpoint = this._isServer ? 'server' : 'client'; if (!this._deflate) { - const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; + const key = `${endpoint}_max_window_bits`; + const windowBits = typeof this.params[key] !== 'number' + ? zlib.Z_DEFAULT_WINDOWBITS + : this.params[key]; + this._deflate = zlib.createDeflateRaw({ + memLevel: this._options.memLevel, flush: zlib.Z_SYNC_FLUSH, - windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, - memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + windowBits }); } this._deflate.writeInProgress = true; From 5af6f79e644800c900108ac792e367d933e1bc62 Mon Sep 17 00:00:00 2001 From: Anoop Mundathan Date: Mon, 29 May 2017 19:44:49 +0100 Subject: [PATCH 315/489] [doc] Fix typo (#1127) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e8129588..c869c207e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ WebSocket message. The extension is disabled by default on the server and enabled by default on the client. It adds a significant overhead in terms of performance and memory -comsumption so we suggest to enable it only if it is really needed. +consumption so we suggest to enable it only if it is really needed. The client will only use the extension if it is supported and enabled on the server. To always disable the extension on the client set the From b66a6562aaf62a2b13bae942d0146adf52328bc5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 2 Jun 2017 06:56:43 +0200 Subject: [PATCH 316/489] chore(package): update nyc to version 11.0.1 (#1130) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4e764a2f..4c99d8708 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", - "nyc": "~10.3.0", + "nyc": "~11.0.1", "utf-8-validate": "~3.0.0" } } From 9c8024c41f5e2887975071c20c3ed0cf4effaf9e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 2 Jun 2017 06:57:12 +0200 Subject: [PATCH 317/489] chore(package): update eslint-plugin-node to version 5.0.0 (#1129) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c99d8708..5d0aa38f8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.3.0", - "eslint-plugin-node": "~4.2.0", + "eslint-plugin-node": "~5.0.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", From 4e53caa723ed34e33c43cc2b562d00e2a33ab728 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 3 Jun 2017 07:15:48 +0200 Subject: [PATCH 318/489] fix(package): update safe-buffer to version 5.1.0 (#1132) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d0aa38f8..e4559db4a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "lint": "eslint ." }, "dependencies": { - "safe-buffer": "~5.0.1", + "safe-buffer": "~5.1.0", "ultron": "~1.1.0" }, "devDependencies": { From 942f825ff0702ce7ce66d8731a56b071269d88b4 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Jun 2017 09:26:58 +0200 Subject: [PATCH 319/489] [minor] Simplify if condition --- lib/WebSocket.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 29180ea0f..320c725e1 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -131,10 +131,7 @@ class WebSocket extends EventEmitter { this._ultron.on('end', this._finalize); // ensure that the head is added to the receiver - if (head && head.length > 0) { - socket.unshift(head); - head = null; - } + if (head.length > 0) socket.unshift(head); // subsequent packets are pushed to the receiver this._ultron.on('data', (data) => { From 8c02d923413ec01afb660e98c12b583486e596be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Jun 2017 10:22:21 +0200 Subject: [PATCH 320/489] [example] Refactor SSL example --- examples/ssl.js | 70 +++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/examples/ssl.js b/examples/ssl.js index f1f1dee44..496daea80 100644 --- a/examples/ssl.js +++ b/examples/ssl.js @@ -1,47 +1,37 @@ +'use strict'; -(function () { - 'use strict'; +const https = require('https'); +const fs = require('fs'); - var fs = require('fs'); +const WebSocket = require('..'); - // you'll probably load configuration from config - var cfg = { - ssl: true, - port: 8080, - ssl_key: '/path/to/you/ssl.key', - ssl_cert: '/path/to/you/ssl.crt' - }; +const server = https.createServer({ + cert: fs.readFileSync('../test/fixtures/certificate.pem'), + key: fs.readFileSync('../test/fixtures/key.pem') +}); - var httpServ = (cfg.ssl) ? require('https') : require('http'); +const wss = new WebSocket.Server({ server }); - var WebSocketServer = require('../').Server; - - var app = null; - - // dummy request processing - var processRequest = function (req, res) { - res.writeHead(200); - res.end('All glory to WebSockets!\n'); - }; - - if (cfg.ssl) { - app = httpServ.createServer({ - - // providing server with SSL key/cert - key: fs.readFileSync(cfg.ssl_key), - cert: fs.readFileSync(cfg.ssl_cert) - - }, processRequest).listen(cfg.port); - } else { - app = httpServ.createServer(processRequest).listen(cfg.port); - } - - // passing or reference to web server so WS would knew port and SSL capabilities - var wss = new WebSocketServer({ server: app }); +wss.on('connection', function connection (ws) { + ws.on('message', function message (msg) { + console.log(msg); + }); +}); + +server.listen(function listening () { + // + // If the `rejectUnauthorized` option is not `false`, the server certificate + // is verified against a list of well-known CAs. An 'error' event is emitted + // if verification fails. + // + // The certificate used in this example is self-signed so `rejectUnauthorized` + // is set to `false`. + // + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + rejectUnauthorized: false + }); - wss.on('connection', function (wsConnect) { - wsConnect.on('message', function (message) { - console.log(message); - }); + ws.on('open', function open () { + ws.send('All glory to WebSockets!'); }); -}()); +}); From f8f149e54e70f8fc1a49dd44b674e0fc9ddcf763 Mon Sep 17 00:00:00 2001 From: Steve Date: Wed, 14 Jun 2017 17:10:26 +1200 Subject: [PATCH 321/489] [doc] Fix typo (#1141) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c869c207e..12aefb1aa 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ wss.on('connection', function connection(ws, req) { }); ``` -When the server runs behing a proxy like NGINX, the de-facto standard is to use +When the server runs behind a proxy like NGINX, the de-facto standard is to use the `X-Forwarded-For` header. ```js From bb2e952e6be9f92cd75a501935ac215b97413458 Mon Sep 17 00:00:00 2001 From: Roy Miloh Date: Mon, 19 Jun 2017 23:15:48 +0300 Subject: [PATCH 322/489] [fix] Set wasClean to true when close code is in 3000-4999 range (#1146) --- lib/EventTarget.js | 2 +- test/WebSocket.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index 8f9a943a8..fe1730255 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -55,7 +55,7 @@ class CloseEvent extends Event { constructor (code, reason, target) { super('close', target); - this.wasClean = code === undefined || code === 1000; + this.wasClean = code === undefined || code === 1000 || (code >= 3000 && code <= 4999); this.reason = reason; this.target = target; this.type = 'close'; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 84fd09ea3..0ebfbe52d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1417,6 +1417,32 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.close(1000)); }); + it('should assign "true" to wasClean when server closes with code 3000', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(closeEvent.wasClean); + wss.close(done); + }); + }); + + wss.on('connection', (client) => client.close(3000)); + }); + + it('should assign "true" to wasClean when server closes with code 4999', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(closeEvent.wasClean); + wss.close(done); + }); + }); + + wss.on('connection', (client) => client.close(4999)); + }); + it('should receive valid CloseEvent when server closes with code 1001', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); From cd55ced624c910469ddfe0d288fd11ad89579342 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 22 Jun 2017 22:02:59 +0200 Subject: [PATCH 323/489] chore(package): update eslint-plugin-import to version 2.5.0 (#1147) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4559db4a..5c31c4e73 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.3.0", + "eslint-plugin-import": "~2.5.0", "eslint-plugin-node": "~5.0.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 2d4dc08a624ec8fee135135f47df2c208da8e5fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 23 Jun 2017 20:45:27 +0200 Subject: [PATCH 324/489] chore(package): update eslint-plugin-import to version 2.6.0 (#1149) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c31c4e73..356548731 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.5.0", + "eslint-plugin-import": "~2.6.0", "eslint-plugin-node": "~5.0.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 043442855f58f84a99f1b2eb21ef76d98b7c1144 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 24 Jun 2017 07:35:57 +0200 Subject: [PATCH 325/489] chore(package): update eslint to version 4.1.0 (#1151) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 356548731..ba9cf32ae 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~3.19.0", + "eslint": "~4.1.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.6.0", "eslint-plugin-node": "~5.0.0", From c311c43bff75e961e719df6d1e41a98de4c712e0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 24 Jun 2017 07:38:15 +0200 Subject: [PATCH 326/489] [lint] Fix lint issues --- lib/PerMessageDeflate.js | 42 +++++++++++++++++++--------------------- lib/Validation.js | 2 +- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c1e08f092..c2c613759 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -113,20 +113,17 @@ class PerMessageDeflate { acceptAsServer (paramsList) { const accepted = {}; const result = paramsList.some((params) => { - if (( - this._options.serverNoContextTakeover === false && - params.server_no_context_takeover - ) || ( - this._options.serverMaxWindowBits === false && - params.server_max_window_bits - ) || ( - typeof this._options.serverMaxWindowBits === 'number' && - typeof params.server_max_window_bits === 'number' && - this._options.serverMaxWindowBits > params.server_max_window_bits - ) || ( - typeof this._options.clientMaxWindowBits === 'number' && - !params.client_max_window_bits - )) { + if ( + (this._options.serverNoContextTakeover === false && + params.server_no_context_takeover) || + (this._options.serverMaxWindowBits === false && + params.server_max_window_bits) || + (typeof this._options.serverMaxWindowBits === 'number' && + typeof params.server_max_window_bits === 'number' && + this._options.serverMaxWindowBits > params.server_max_window_bits) || + (typeof this._options.clientMaxWindowBits === 'number' && + !params.client_max_window_bits) + ) { return; } @@ -136,10 +133,11 @@ class PerMessageDeflate { ) { accepted.server_no_context_takeover = true; } - if (this._options.clientNoContextTakeover || ( - this._options.clientNoContextTakeover !== false && - params.client_no_context_takeover - )) { + if ( + this._options.clientNoContextTakeover || + (this._options.clientNoContextTakeover !== false && + params.client_no_context_takeover) + ) { accepted.client_no_context_takeover = true; } if (typeof this._options.serverMaxWindowBits === 'number') { @@ -189,10 +187,10 @@ class PerMessageDeflate { throw new Error('Invalid value for "client_max_window_bits"'); } if ( - typeof this._options.clientMaxWindowBits === 'number' && ( - !params.client_max_window_bits || - params.client_max_window_bits > this._options.clientMaxWindowBits - )) { + typeof this._options.clientMaxWindowBits === 'number' && + (!params.client_max_window_bits || + params.client_max_window_bits > this._options.clientMaxWindowBits) + ) { throw new Error('Invalid value for "client_max_window_bits"'); } } diff --git a/lib/Validation.js b/lib/Validation.js index fcb170f31..35c7e4f2a 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -10,7 +10,7 @@ try { const isValidUTF8 = require('utf-8-validate'); module.exports = typeof isValidUTF8 === 'object' - ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 + ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 : isValidUTF8; } catch (e) /* istanbul ignore next */ { module.exports = () => true; From 034ab89734d61c0d12a26fa033a37ca7ab398d56 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 24 Jun 2017 07:42:48 +0200 Subject: [PATCH 327/489] [ci] Test on node 8 --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 81cf3a9a2..1670a8aff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js dist: trusty sudo: false node_js: - - "7" + - "8" - "6" - "4" - "4.1.0" diff --git a/appveyor.yml b/appveyor.yml index d893ad905..db5faaaff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ environment: matrix: - - nodejs_version: "7" + - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - nodejs_version: "4.1.0" From 833766fc5d9df27f5bfd5476c03e8f96f92a324d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 26 Jun 2017 07:41:57 +0200 Subject: [PATCH 328/489] chore(package): update eslint-plugin-node to version 5.1.0 (#1155) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba9cf32ae..6d1b11ad8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint": "~4.1.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.6.0", - "eslint-plugin-node": "~5.0.0", + "eslint-plugin-node": "~5.1.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", From afb36e92ca7dba7b7f70fe5b58104e8a9692cade Mon Sep 17 00:00:00 2001 From: Yaowei Date: Wed, 5 Jul 2017 22:01:30 +0800 Subject: [PATCH 329/489] [doc] Fix typos in API docs (#1165) --- doc/ws.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 2313ea9bd..4da6e4ea7 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -136,7 +136,7 @@ when the HTTP server is passed via the `server` option, this method is called automatically. When operating in "noServer" mode, this method must be called manually. -If the upgrade is successfull, the `callback` is called with a `WebSocket` +If the upgrade is successful, the `callback` is called with a `WebSocket` object as parameter. ### server.shouldHandle(request) @@ -149,7 +149,7 @@ against the `path` option if provided. The return value, `true` or `false`, determines whether or not to accept the handshake. -This method can be overriden when a custom handling logic is required. +This method can be overridden when a custom handling logic is required. ## Class: WebSocket @@ -285,7 +285,7 @@ A string indicating the type of binary data being transmitted by the connection. This should be one of "nodebuffer", "arraybuffer" or "fragments". Defaults to "nodebuffer". Type "fragments" will emit the array of fragments as received from the sender, without copyfull concatenation, which is useful for the performance -of binary protocols transfering large messages with multiple fragments. +of binary protocols transferring large messages with multiple fragments. ### websocket.bufferedAmount From 3544e3045794b011426368d3e26e0fd3f934b6a7 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 6 Jul 2017 19:04:46 +0200 Subject: [PATCH 330/489] chore(package): update eslint-plugin-import to version 2.7.0 (#1167) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d1b11ad8..07bd5a63f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.1.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.6.0", + "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 16bbddb23adec778978d4dd9cdd6a56ce92e4786 Mon Sep 17 00:00:00 2001 From: Matt Audesse Date: Fri, 7 Jul 2017 01:32:45 -0400 Subject: [PATCH 331/489] [doc] Fix "unware" and "reponse" typos in README.md (#1168) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12aefb1aa..88c480d78 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ wss.on('connection', function connection(ws, req) { ### How to detect and close broken connections? Sometimes the link between the server and the client can be interrupted in a -way that keeps both the server and the client unware of the broken state of the +way that keeps both the server and the client unaware of the broken state of the connection (e.g. when pulling the cord). In these cases ping messages can be used as a means to verify that the remote @@ -317,7 +317,7 @@ const interval = setInterval(function ping() { }, 30000); ``` -Pong messages are automatically sent in reponse to ping messages as required +Pong messages are automatically sent in response to ping messages as required by the spec. ### How to connect via a proxy? From d7811907ad590cbc7a6354f61dd283e36d1eee4d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 9 Jul 2017 07:18:21 +0200 Subject: [PATCH 332/489] chore(package): update eslint to version 4.2.0 (#1169) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07bd5a63f..b9b358406 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.1.0", + "eslint": "~4.2.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From ab6c1c23fc2bac092d4f0ad0aaeb4322bdd48252 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 16 Jul 2017 15:21:36 +0200 Subject: [PATCH 333/489] [minor] Remove redundant assignments --- lib/EventTarget.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index fe1730255..9d0461a18 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -57,8 +57,6 @@ class CloseEvent extends Event { this.wasClean = code === undefined || code === 1000 || (code >= 3000 && code <= 4999); this.reason = reason; - this.target = target; - this.type = 'close'; this.code = code; } } From 5c06c8f4127b8aea4ec34cb3f2236a579901214a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 21 Jul 2017 20:51:28 +0200 Subject: [PATCH 334/489] chore(package): update eslint to version 4.3.0 (#1176) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b9b358406..eec744f56 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.2.0", + "eslint": "~4.3.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From b32016995e9620ed6d5826464b2c3ee931e2240a Mon Sep 17 00:00:00 2001 From: Ido schachter Date: Thu, 27 Jul 2017 08:42:35 +0300 Subject: [PATCH 335/489] [feature] Allow client to specify handshake request timeout (#1177) --- doc/ws.md | 1 + lib/WebSocket.js | 10 ++++++++++ test/WebSocket.test.js | 15 +++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/doc/ws.md b/doc/ws.md index 4da6e4ea7..9e5e6f413 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 320c725e1..760ae34fa 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; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 0ebfbe52d..1aa45d12e 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 () { From 4e3ada1291b58c2a46303744835e5d466fcbc9b1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 27 Jul 2017 07:59:12 +0200 Subject: [PATCH 336/489] [dist] 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eec744f56..996cdc1b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.0.0", + "version": "3.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From faea8121a0fc05561b170cca9c523a0751df1df0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 29 Jul 2017 23:29:56 +0200 Subject: [PATCH 337/489] chore(package): update nyc to version 11.1.0 (#1178) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 996cdc1b2..ad8270469 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", - "nyc": "~11.0.1", + "nyc": "~11.1.0", "utf-8-validate": "~3.0.0" } } From 63b3c11f0cadcd5ba3ac897e7f1b20299cb24597 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 1 Aug 2017 07:20:07 +0200 Subject: [PATCH 338/489] chore(package): update mocha to version 3.5.0 (#1179) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad8270469..603da662b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-node": "~5.1.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~3.4.1", + "mocha": "~3.5.0", "nyc": "~11.1.0", "utf-8-validate": "~3.0.0" } From a1ac65994d59d46feb7b93ab1f91cd658394e301 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 5 Aug 2017 21:05:14 +0200 Subject: [PATCH 339/489] chore(package): update eslint to version 4.4.0 (#1183) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 603da662b..60562d230 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.3.0", + "eslint": "~4.4.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 381e7e0411fc45e05b3bb4fa9fd884ff03c6c886 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Aug 2017 09:25:51 +0200 Subject: [PATCH 340/489] [benchmark] Fix parser benchmark --- bench/parser.benchmark.js | 61 ++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 5e6259cb4..b3484b611 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -10,10 +10,10 @@ const safeBuffer = require('safe-buffer'); const benchmark = require('benchmark'); const crypto = require('crypto'); -const util = require('../test/hybi-util'); const WebSocket = require('..'); const Receiver = WebSocket.Receiver; +const Sender = WebSocket.Sender; const Buffer = safeBuffer.Buffer; // @@ -21,44 +21,51 @@ const Buffer = safeBuffer.Buffer; // expected. // Receiver.prototype.cleanup = function () { - this.state = 0; + this._state = 0; }; -function createBinaryPacket (length) { - const message = crypto.randomBytes(length); +const options = { + fin: true, + rsv1: false, + mask: true, + readOnly: false +}; - return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + - '3483a868' + util.mask(message, '3483a868').toString('hex'), 'hex'); -} +function createBinaryFrame (length) { + const list = Sender.frame( + crypto.randomBytes(length), + Object.assign({ opcode: 0x02 }, options) + ); -const pingMessage = 'Hello'; -const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + - '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); + return Buffer.concat(list); +} -const textMessage = 'a'.repeat(20); -const maskedTextPacket = Buffer.from('81' + util.pack(2, 0x80 | textMessage.length) + - '61616161' + util.mask(textMessage, '61616161').toString('hex'), 'hex'); +const pingFrame1 = Buffer.concat(Sender.frame( + crypto.randomBytes(5), + Object.assign({ opcode: 0x09 }, options) +)); -const pingPacket2 = Buffer.from('8900', 'hex'); -const closePacket = Buffer.from('8800', 'hex'); -const binaryDataPacket = createBinaryPacket(125); -const binaryDataPacket2 = createBinaryPacket(65535); -const binaryDataPacket3 = createBinaryPacket(200 * 1024); -const binaryDataPacket4 = createBinaryPacket(1024 * 1024); +const textFrame = Buffer.from('819461616161' + '61'.repeat(20), 'hex'); +const pingFrame2 = Buffer.from('8900', 'hex'); +const closeFrame = Buffer.from('8800', 'hex'); +const binaryFrame1 = createBinaryFrame(125); +const binaryFrame2 = createBinaryFrame(65535); +const binaryFrame3 = createBinaryFrame(200 * 1024); +const binaryFrame4 = createBinaryFrame(1024 * 1024); const suite = new benchmark.Suite(); const receiver = new Receiver(); receiver.onmessage = receiver.onclose = receiver.onping = () => {}; -suite.add('ping message', () => receiver.add(pingPacket1)); -suite.add('ping with no data', () => receiver.add(pingPacket2)); -suite.add('close message', () => receiver.add(closePacket)); -suite.add('masked text message (20 bytes)', () => receiver.add(maskedTextPacket)); -suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); -suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); -suite.add('binary data (200 KiB)', () => receiver.add(binaryDataPacket3)); -suite.add('binary data (1 MiB)', () => receiver.add(binaryDataPacket4)); +suite.add('ping frame (5 bytes payload)', () => receiver.add(pingFrame1)); +suite.add('ping frame (no payload)', () => receiver.add(pingFrame2)); +suite.add('close frame (no payload)', () => receiver.add(closeFrame)); +suite.add('text frame (20 bytes payload)', () => receiver.add(textFrame)); +suite.add('binary frame (125 bytes payload)', () => receiver.add(binaryFrame1)); +suite.add('binary frame (65535 bytes payload)', () => receiver.add(binaryFrame2)); +suite.add('binary frame (200 KiB payload)', () => receiver.add(binaryFrame3)); +suite.add('binary frame (1 MiB payload)', () => receiver.add(binaryFrame4)); suite.on('cycle', (e) => console.log(e.target.toString())); From ed4cf531001aa8e330daf1cec5526769963cb232 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Aug 2017 12:17:10 +0200 Subject: [PATCH 341/489] [test] Remove hybi-util.js --- test/Receiver.test.js | 206 +++++++++++++++++++++++++++++------------- test/hybi-util.js | 74 --------------- 2 files changed, 142 insertions(+), 138 deletions(-) delete mode 100644 test/hybi-util.js diff --git a/test/Receiver.test.js b/test/Receiver.test.js index ffd579e00..bb75a8993 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -7,7 +7,6 @@ const crypto = require('crypto'); const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Receiver = require('../lib/Receiver'); const Sender = require('../lib/Sender'); -const util = require('./hybi-util'); const Buffer = safeBuffer.Buffer; @@ -50,9 +49,15 @@ describe('Receiver', function () { const p = new Receiver(); const msg = 'A'.repeat(200); - const mask = '3483a868'; - const frame = Buffer.from('81FE' + util.pack(4, msg.length) + mask + - util.mask(msg, mask).toString('hex'), 'hex'); + const list = Sender.frame(Buffer.from(msg), { + fin: true, + rsv1: false, + opcode: 0x01, + mask: true, + readOnly: false + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.strictEqual(data, msg); @@ -67,16 +72,22 @@ describe('Receiver', function () { const p = new Receiver(); const msg = 'A'.repeat(64 * 1024); - const mask = '3483a868'; - const frame = '81FF' + util.pack(16, msg.length) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(Buffer.from(msg), { + fin: true, + rsv1: false, + opcode: 0x01, + mask: true, + readOnly: false + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a fragmented masked text message of 300 B', function (done) { @@ -86,35 +97,46 @@ describe('Receiver', function () { const fragment1 = msg.substr(0, 150); const fragment2 = msg.substr(150); - const mask = '3483a868'; - const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + - util.mask(fragment1, mask).toString('hex'); - const frame2 = '80FE' + util.pack(4, fragment2.length) + mask + - util.mask(fragment2, mask).toString('hex'); + const options = { rsv1: false, mask: true, readOnly: false }; + + const frame1 = Buffer.concat(Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + )); + const frame2 = Buffer.concat(Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + )); p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; - p.add(Buffer.from(frame1, 'hex')); - p.add(Buffer.from(frame2, 'hex')); + p.add(frame1); + p.add(frame2); }); it('can parse a ping message', function (done) { const p = new Receiver(); const msg = 'Hello'; - const mask = '3483a868'; - const frame = '89' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(Buffer.from(msg), { + fin: true, + rsv1: false, + opcode: 0x09, + mask: true, + readOnly: false + }); + + const frame = Buffer.concat(list); p.onping = function (data) { assert.strictEqual(data.toString(), msg); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a ping with no data', function (done) { @@ -136,13 +158,20 @@ describe('Receiver', function () { const fragment1 = msg.substr(0, 150); const fragment2 = msg.substr(150); - const mask = '3483a868'; - const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + - util.mask(fragment1, mask).toString('hex'); - const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + - util.mask(pingMessage, mask).toString('hex'); - const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + - util.mask(fragment2, mask).toString('hex'); + const options = { rsv1: false, mask: true, readOnly: false }; + + const frame1 = Buffer.concat(Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + )); + const frame2 = Buffer.concat(Sender.frame( + Buffer.from(pingMessage), + Object.assign({ fin: true, opcode: 0x09 }, options) + )); + const frame3 = Buffer.concat(Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + )); let gotPing = false; @@ -156,9 +185,9 @@ describe('Receiver', function () { assert.strictEqual(data.toString(), pingMessage); }; - p.add(Buffer.from(frame1, 'hex')); - p.add(Buffer.from(frame2, 'hex')); - p.add(Buffer.from(frame3, 'hex')); + p.add(frame1); + p.add(frame2); + p.add(frame3); }); it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { @@ -169,19 +198,30 @@ describe('Receiver', function () { const fragment1 = msg.substr(0, 150); const fragment2 = msg.substr(150); - const mask = '3483a868'; - const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + - util.mask(fragment1, mask).toString('hex'); - const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + - util.mask(pingMessage, mask).toString('hex'); - const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + - util.mask(fragment2, mask).toString('hex'); + const options = { rsv1: false, mask: true, readOnly: false }; + + const frame1 = Buffer.concat(Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + )); + const frame2 = Buffer.concat(Sender.frame( + Buffer.from(pingMessage), + Object.assign({ fin: true, opcode: 0x09 }, options) + )); + const frame3 = Buffer.concat(Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + )); - let buffers = []; + let chunks = []; + const splitBuffer = (buf) => { + const i = Math.floor(buf.length / 2); + return [buf.slice(0, i), buf.slice(i)]; + }; - buffers = buffers.concat(util.splitBuffer(Buffer.from(frame1, 'hex'))); - buffers = buffers.concat(util.splitBuffer(Buffer.from(frame2, 'hex'))); - buffers = buffers.concat(util.splitBuffer(Buffer.from(frame3, 'hex'))); + chunks = chunks.concat(splitBuffer(frame1)); + chunks = chunks.concat(splitBuffer(frame2)); + chunks = chunks.concat(splitBuffer(frame3)); let gotPing = false; @@ -195,8 +235,8 @@ describe('Receiver', function () { assert.strictEqual(data.toString(), pingMessage); }; - for (let i = 0; i < buffers.length; ++i) { - p.add(buffers[i]); + for (let i = 0; i < chunks.length; ++i) { + p.add(chunks[i]); } }); @@ -204,63 +244,88 @@ describe('Receiver', function () { const p = new Receiver(); const msg = crypto.randomBytes(100); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a 256 B long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(256); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a 200 KiB long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a 200 KiB long unmasked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); - const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + - msg.toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: false, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse compressed message', function (done) { @@ -602,9 +667,15 @@ describe('Receiver', function () { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onerror = function (err, code) { assert.ok(err instanceof Error); @@ -613,15 +684,22 @@ describe('Receiver', function () { done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('raises an error on a 200 KiB long unmasked binary message when `maxPayload` is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); - const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + - msg.toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: false, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onerror = function (err, code) { assert.ok(err instanceof Error); @@ -630,7 +708,7 @@ describe('Receiver', function () { done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('raises an error on a compressed message that exceeds `maxPayload`', function (done) { diff --git a/test/hybi-util.js b/test/hybi-util.js deleted file mode 100644 index 3f0196b92..000000000 --- a/test/hybi-util.js +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -const safeBuffer = require('safe-buffer'); - -const Buffer = safeBuffer.Buffer; - -/** - * Performs hybi07+ type masking. - */ -function mask (buf, maskString) { - const _mask = Buffer.from(maskString || '3483a868', 'hex'); - - buf = Buffer.from(buf); - - for (let i = 0; i < buf.length; ++i) { - buf[i] ^= _mask[i % 4]; - } - - return buf; -} - -/** - * Left pads the string `s` to a total length of `n` with char `c`. - */ -function padl (s, n, c) { - return c.repeat(n - s.length) + s; -} - -/** - * Returns a hex string, representing a specific byte count `length`, from a number. - */ -function pack (length, number) { - return padl(number.toString(16), length, '0'); -} - -/** - * Returns a hex string representing the length of a message. - */ -function getHybiLengthAsHexString (len, masked) { - let s; - - masked = masked ? 0x80 : 0; - - if (len < 126) { - s = pack(2, masked | len); - } else if (len < 65536) { - s = pack(2, masked | 126) + pack(4, len); - } else { - s = pack(2, masked | 127) + pack(16, len); - } - - return s; -} - -/** - * Split a buffer in two. - */ -function splitBuffer (buf) { - const i = Math.floor(buf.length / 2); - return [buf.slice(0, i), buf.slice(i)]; -} - -module.exports = { - getHybiLengthAsHexString, - splitBuffer, - mask, - pack -}; From 6663b89cd7c19269c6f9a527e96f3cd7db0c5fea Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 19 Aug 2017 07:23:40 +0200 Subject: [PATCH 342/489] chore(package): update eslint to version 4.5.0 (#1190) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 60562d230..3f6c0a893 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.4.0", + "eslint": "~4.5.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 92750e359462c732edc74234d6c7a58f94233011 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 2 Sep 2017 07:03:54 +0200 Subject: [PATCH 343/489] chore(package): update eslint to version 4.6.0 (#1197) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f6c0a893..64cc2fd54 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.5.0", + "eslint": "~4.6.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 7234fd4981cd799198ddbec5d7df6b7623d4be24 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 5 Sep 2017 22:19:21 +0200 Subject: [PATCH 344/489] chore(package): update nyc to version 11.2.0 (#1198) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64cc2fd54..cedbec7e3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.5.0", - "nyc": "~11.1.0", + "nyc": "~11.2.0", "utf-8-validate": "~3.0.0" } } From 80445e7d6caf0ec021f0dcdb703d048b2f005dce Mon Sep 17 00:00:00 2001 From: Samuel Reed Date: Thu, 7 Sep 2017 15:39:03 -0500 Subject: [PATCH 345/489] [feature] Allow 'level' option (#1199) This corresponds to the zlib level as described in https://nodejs.org/api/zlib.html#zlib_class_options --- doc/ws.md | 3 +- lib/PerMessageDeflate.js | 1 + test/PerMessageDeflate.test.js | 55 ++++++++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 9e5e6f413..c80ca5a1e 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -72,7 +72,8 @@ provided then that is extension parameters: - `serverMaxWindowBits` {Number} The value of windowBits. - `clientMaxWindowBits` {Number} The value of max windowBits to be requested to clients. -- `memLevel` {Number} The value of memLevel. +- `level` {Number} The value of zlib's `level` param (0-9, default 8). +- `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c2c613759..7044b31f7 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -340,6 +340,7 @@ class PerMessageDeflate { this._deflate = zlib.createDeflateRaw({ memLevel: this._options.memLevel, + level: this._options.level, flush: zlib.Z_SYNC_FLUSH, windowBits }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index dbaf67e4c..b66ddd082 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -257,28 +257,77 @@ describe('PerMessageDeflate', function () { it('should compress/decompress data with parameters', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0, - memLevel: 5 + memLevel: 5, + level: 9 }); const extensions = Extensions.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' ); + const srcData = 'Some compressible data, it\'s compressible.'; perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { + perMessageDeflate.compress(Buffer.from(srcData, 'utf8'), true, (err, compressed) => { if (err) return done(err); perMessageDeflate.decompress(compressed, true, (err, data) => { if (err) return done(err); - assert.ok(data.equals(Buffer.from([1, 2, 3]))); + assert.ok(data.equals(Buffer.from(srcData, 'utf8'))); done(); }); }); }); + it('should compress/decompress with level parameter', function (done) { + const perMessageDeflateLev9 = new PerMessageDeflate({ + threshold: 0, + level: 9 + }); + const perMessageDeflateLev0 = new PerMessageDeflate({ + threshold: 0, + level: 0 + }); + const extensionStr = ( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + const srcData = 'Some compressible data, it\'s compressible.'; + const srcDataBuffer = Buffer.from(srcData, 'utf8'); + + perMessageDeflateLev0.accept(Extensions.parse(extensionStr)['permessage-deflate']); + perMessageDeflateLev9.accept(Extensions.parse(extensionStr)['permessage-deflate']); + + perMessageDeflateLev0.compress(srcDataBuffer, true, (err, compressed1) => { + if (err) return done(err); + + perMessageDeflateLev0.decompress(compressed1, true, (err, data1) => { + if (err) return done(err); + + perMessageDeflateLev9.compress(srcDataBuffer, true, (err, compressed2) => { + if (err) return done(err); + + perMessageDeflateLev9.decompress(compressed2, true, (err, data2) => { + if (err) return done(err); + + // Level 0 compression actually adds a few bytes due to headers + assert.ok(compressed1.length > srcDataBuffer.length); + // Level 9 should not, of course. + assert.ok(compressed2.length < compressed1.length); + assert.ok(compressed2.length < srcDataBuffer.length); + // Ensure they both decompress back properly. + assert.ok(data1.equals(srcDataBuffer)); + assert.ok(data2.equals(srcDataBuffer)); + done(); + }); + }); + }); + }); + }); + it('should compress/decompress data with no context takeover', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const extensions = Extensions.parse( From f2d18b6a6e488290dba216e964d787fbf27b1267 Mon Sep 17 00:00:00 2001 From: Samuel Reed Date: Wed, 13 Sep 2017 00:57:06 -0500 Subject: [PATCH 346/489] [feature] zlib deflate concurrency limit (#1204) --- doc/ws.md | 5 ++++ lib/PerMessageDeflate.js | 56 +++++++++++++++++++++++++++++++++++++--- package.json | 1 + 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index c80ca5a1e..9e5e636c5 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -76,6 +76,10 @@ provided then that is extension parameters: - `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. +- `concurrencyLimit` {Number} The number of concurrent calls to zlib. + Calls above this limit will be queued. Default 10. You usually won't + need to touch this option. See [concurrency-limit][this issue] for more + details. If a property is empty then either an offered configuration or a default value is used. @@ -425,4 +429,5 @@ Forcibly close the connection. The URL of the WebSocket server. Server clients don't have this attribute. +[concurrency-limit]: https://github.com/websockets/ws/issues/1202 [permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 7044b31f7..27a00ca37 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -2,6 +2,7 @@ const safeBuffer = require('safe-buffer'); const zlib = require('zlib'); +const Limiter = require('async-limiter'); const bufferUtil = require('./BufferUtil'); @@ -10,6 +11,14 @@ const Buffer = safeBuffer.Buffer; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +// We limit zlib concurrency, which prevents severe memory fragmentation +// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 +// and https://github.com/websockets/ws/issues/1202 +// +// Intentionally global; it's the global thread pool that's +// an issue. +let zlibLimiter; + /** * Per-message Deflate implementation. */ @@ -25,6 +34,13 @@ class PerMessageDeflate { this._inflate = null; this.params = null; + + if (!zlibLimiter) { + const concurrency = this._options.concurrencyLimit !== undefined + ? this._options.concurrencyLimit + : 10; + zlibLimiter = new Limiter({ concurrency }); + } } static get extensionName () { @@ -249,7 +265,7 @@ class PerMessageDeflate { } /** - * Decompress data. + * Decompress data. Concurrency limited by async-limiter. * * @param {Buffer} data Compressed data * @param {Boolean} fin Specifies whether or not this is the last fragment @@ -257,6 +273,40 @@ class PerMessageDeflate { * @public */ decompress (data, fin, callback) { + zlibLimiter.push((done) => { + this._decompress(data, fin, (err, result) => { + done(); + callback(err, result); + }); + }); + } + + /** + * Compress data. Concurrency limited by async-limiter. + * + * @param {Buffer} data Data to compress + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @public + */ + compress (data, fin, callback) { + zlibLimiter.push((done) => { + this._compress(data, fin, (err, result) => { + done(); + callback(err, result); + }); + }); + } + + /** + * Decompress data. + * + * @param {Buffer} data Compressed data + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @private + */ + _decompress (data, fin, callback) { const endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { @@ -322,9 +372,9 @@ class PerMessageDeflate { * @param {Buffer} data Data to compress * @param {Boolean} fin Specifies whether or not this is the last fragment * @param {Function} callback Callback - * @public + * @private */ - compress (data, fin, callback) { + _compress (data, fin, callback) { if (!data || data.length === 0) { process.nextTick(callback, null, EMPTY_BLOCK); return; diff --git a/package.json b/package.json index cedbec7e3..8c3116263 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "lint": "eslint ." }, "dependencies": { + "async-limiter": "~1.0.0", "safe-buffer": "~5.1.0", "ultron": "~1.1.0" }, From 4082e695cd56cbd7a2e4bd385b77a78acd2a5b53 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 15 Sep 2017 08:02:33 +0200 Subject: [PATCH 347/489] [dist] 3.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c3116263..01d141608 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.1.0", + "version": "3.2.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 36e1c7aa3654e8f7d2e71db34d0d1779fb39d669 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 16 Sep 2017 07:33:24 +0200 Subject: [PATCH 348/489] chore(package): update eslint to version 4.7.0 (#1206) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 01d141608..9fafb0612 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.6.0", + "eslint": "~4.7.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 9fe606e5d957fd13951803f5ae5c247720bcab8d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 28 Sep 2017 16:29:45 +0200 Subject: [PATCH 349/489] chore(package): update eslint-plugin-node to version 5.2.0 (#1209) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9fafb0612..82a909ea1 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint": "~4.7.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", - "eslint-plugin-node": "~5.1.0", + "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.5.0", From 6f44ee982ef7bec03cfd77f9508f3903bd9738a8 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 30 Sep 2017 09:33:32 +0200 Subject: [PATCH 350/489] chore(package): update eslint to version 4.8.0 (#1210) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82a909ea1..7308ad237 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.7.0", + "eslint": "~4.8.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.2.0", From 71bb8f4c6412bee227034a268c29e597156c9f95 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 3 Oct 2017 10:08:08 +0200 Subject: [PATCH 351/489] chore(package): update mocha to version 4.0.0 (#1211) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7308ad237..7472670e9 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~3.5.0", + "mocha": "~4.0.0", "nyc": "~11.2.0", "utf-8-validate": "~3.0.0" } From 892a84ff272ed259ea49f00feff835ad3366ae64 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Oct 2017 10:40:48 +0200 Subject: [PATCH 352/489] [ci] Remove no longer needed `dist` key from .travis.yml --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1670a8aff..c94d7feab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: node_js -dist: trusty sudo: false node_js: - "8" From f8cb85b6d998b06ad563a1e4ab72d066b52584c8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Oct 2017 10:45:22 +0200 Subject: [PATCH 353/489] [test] Fix issue that prevented process from exiting --- test/WebSocket.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 1aa45d12e..3c6ec9eaf 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -72,8 +72,8 @@ describe('WebSocket', function () { ws.on('error', (err) => { // Skip this test on machines where 127.0.0.2 is disabled. - if (err.code === 'EADDRNOTAVAIL') return done(); - throw err; + if (err.code === 'EADDRNOTAVAIL') err = undefined; + wss.close(() => done(err)); }); }); From c751a45283976374762816e75e576cca12702871 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 13 Oct 2017 21:17:53 +0200 Subject: [PATCH 354/489] chore(package): update eslint-plugin-promise to version 3.6.0 (#1215) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7472670e9..d44844353 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.2.0", - "eslint-plugin-promise": "~3.5.0", + "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", "nyc": "~11.2.0", From b6a928b5119117f756b7266b194e3bd30f40248a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 15 Oct 2017 09:05:16 +0200 Subject: [PATCH 355/489] chore(package): update eslint to version 4.9.0 (#1217) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d44844353..1ce0684af 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.8.0", + "eslint": "~4.9.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.2.0", From e01fa4a3805cf1a337c6df996abe0e20babf6218 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 19 Oct 2017 14:31:34 +0200 Subject: [PATCH 356/489] chore(package): update eslint-plugin-import to version 2.8.0 (#1219) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ce0684af..33d53602f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.9.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.7.0", + "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", From 5bb78ddc72712a62c2e2f4a4ba82c2339fa6d33f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 28 Oct 2017 07:54:40 +0200 Subject: [PATCH 357/489] chore(package): update eslint to version 4.10.0 (#1223) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33d53602f..6f879454b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.9.0", + "eslint": "~4.10.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 5e0424ce627a8879a4a4995b6bcdc542c3fdd21c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 28 Oct 2017 17:52:28 +0200 Subject: [PATCH 358/489] [doc] Fix broken link --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 9e5e636c5..ba577ec7f 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -78,7 +78,7 @@ provided then that is extension parameters: Defaults to 1024 bytes. - `concurrencyLimit` {Number} The number of concurrent calls to zlib. Calls above this limit will be queued. Default 10. You usually won't - need to touch this option. See [concurrency-limit][this issue] for more + need to touch this option. See [this issue][concurrency-limit] for more details. If a property is empty then either an offered configuration or a default value From 8ea3739d62fd7b2e467ec814e31c7f65d32da98b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Oct 2017 08:55:33 +0100 Subject: [PATCH 359/489] [minor] Remove unreachable code (#1224) `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use it is made after it has already been closed and this cannot happen in our case. --- lib/PerMessageDeflate.js | 57 ++++++++++++++++++++++------------------ lib/Sender.js | 10 +------ lib/WebSocket.js | 19 +++----------- 3 files changed, 37 insertions(+), 49 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 27a00ca37..cee793cfa 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -394,28 +394,27 @@ class PerMessageDeflate { flush: zlib.Z_SYNC_FLUSH, windowBits }); - } - this._deflate.writeInProgress = true; - var totalLength = 0; - const buffers = []; - - const onData = (data) => { - totalLength += data.length; - buffers.push(data); - }; + this._deflate.totalLength = 0; + this._deflate.buffers = []; - const onError = (err) => { - cleanup(); - callback(err); - }; + // + // `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use + // it is made after it has already been closed. This cannot happen here, + // so we only add a listener for the `'data'` event. + // + this._deflate.on('data', deflateOnData); + } - const cleanup = () => { - if (!this._deflate) return; + this._deflate.writeInProgress = true; - this._deflate.removeListener('error', onError); - this._deflate.removeListener('data', onData); - this._deflate.writeInProgress = false; + this._deflate.write(data); + this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { + var data = bufferUtil.concat( + this._deflate.buffers, + this._deflate.totalLength + ); + if (fin) data = data.slice(0, data.length - 4); if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || @@ -423,18 +422,26 @@ class PerMessageDeflate { ) { this._deflate.close(); this._deflate = null; + } else { + this._deflate.writeInProgress = false; + this._deflate.totalLength = 0; + this._deflate.buffers = []; } - }; - this._deflate.on('error', onError).on('data', onData); - this._deflate.write(data); - this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { - cleanup(); - var data = bufferUtil.concat(buffers, totalLength); - if (fin) data = data.slice(0, data.length - 4); callback(null, data); }); } } module.exports = PerMessageDeflate; + +/** + * The listener of the `zlib.DeflateRaw` stream `'data'` event. + * + * @param {Buffer} chunk A chunk of data + * @private + */ +function deflateOnData (chunk) { + this.buffers.push(chunk); + this.totalLength += chunk.length; +} diff --git a/lib/Sender.js b/lib/Sender.js index d3502fe16..53953d8d2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -35,8 +35,6 @@ class Sender { this._bufferedBytes = 0; this._deflating = false; this._queue = []; - - this.onerror = null; } /** @@ -331,13 +329,7 @@ class Sender { const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; this._deflating = true; - perMessageDeflate.compress(data, options.fin, (err, buf) => { - if (err) { - if (cb) cb(err); - else this.onerror(err); - return; - } - + perMessageDeflate.compress(data, options.fin, (_, buf) => { options.readOnly = false; this.sendFrame(Sender.frame(buf, options), cb); this._deflating = false; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 760ae34fa..82d335458 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -157,12 +157,6 @@ class WebSocket extends EventEmitter { this.emit('error', error); }; - // sender event handlers - this._sender.onerror = (error) => { - this.close(1002, ''); - this.emit('error', error); - }; - this.readyState = WebSocket.OPEN; this.emit('open'); } @@ -198,17 +192,12 @@ class WebSocket extends EventEmitter { if (!error) this._socket.end(); else this._socket.destroy(); - this._socket = null; - this._ultron = null; - } - - if (this._sender) { - this._sender = this._sender.onerror = null; - } - - if (this._receiver) { this._receiver.cleanup(() => this.emitClose()); + this._receiver = null; + this._sender = null; + this._socket = null; + this._ultron = null; } else { this.emitClose(); } From 1d6ba8678bf138b7fd4307cc6c97d943501292ab Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Oct 2017 09:11:55 +0100 Subject: [PATCH 360/489] [minor] Prevent possible property name collisions --- lib/PerMessageDeflate.js | 43 ++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index cee793cfa..380042d37 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -1,8 +1,8 @@ 'use strict'; const safeBuffer = require('safe-buffer'); -const zlib = require('zlib'); const Limiter = require('async-limiter'); +const zlib = require('zlib'); const bufferUtil = require('./BufferUtil'); @@ -11,6 +11,11 @@ const Buffer = safeBuffer.Buffer; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +const kWriteInProgress = Symbol('write-in-progress'); +const kPendingClose = Symbol('pending-close'); +const kTotalLength = Symbol('total-length'); +const kBuffers = Symbol('buffers'); + // We limit zlib concurrency, which prevents severe memory fragmentation // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 // and https://github.com/websockets/ws/issues/1202 @@ -102,16 +107,16 @@ class PerMessageDeflate { */ cleanup () { if (this._inflate) { - if (this._inflate.writeInProgress) { - this._inflate.pendingClose = true; + if (this._inflate[kWriteInProgress]) { + this._inflate[kPendingClose] = true; } else { this._inflate.close(); this._inflate = null; } } if (this._deflate) { - if (this._deflate.writeInProgress) { - this._deflate.pendingClose = true; + if (this._deflate[kWriteInProgress]) { + this._deflate[kPendingClose] = true; } else { this._deflate.close(); this._deflate = null; @@ -317,7 +322,7 @@ class PerMessageDeflate { this._inflate = zlib.createInflateRaw({ windowBits }); } - this._inflate.writeInProgress = true; + this._inflate[kWriteInProgress] = true; var totalLength = 0; const buffers = []; @@ -344,11 +349,11 @@ class PerMessageDeflate { this._inflate.removeListener('error', onError); this._inflate.removeListener('data', onData); - this._inflate.writeInProgress = false; + this._inflate[kWriteInProgress] = false; if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || - this._inflate.pendingClose + this._inflate[kPendingClose] ) { this._inflate.close(); this._inflate = null; @@ -395,8 +400,8 @@ class PerMessageDeflate { windowBits }); - this._deflate.totalLength = 0; - this._deflate.buffers = []; + this._deflate[kTotalLength] = 0; + this._deflate[kBuffers] = []; // // `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use @@ -406,26 +411,26 @@ class PerMessageDeflate { this._deflate.on('data', deflateOnData); } - this._deflate.writeInProgress = true; + this._deflate[kWriteInProgress] = true; this._deflate.write(data); this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { var data = bufferUtil.concat( - this._deflate.buffers, - this._deflate.totalLength + this._deflate[kBuffers], + this._deflate[kTotalLength] ); if (fin) data = data.slice(0, data.length - 4); if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || - this._deflate.pendingClose + this._deflate[kPendingClose] ) { this._deflate.close(); this._deflate = null; } else { - this._deflate.writeInProgress = false; - this._deflate.totalLength = 0; - this._deflate.buffers = []; + this._deflate[kWriteInProgress] = false; + this._deflate[kTotalLength] = 0; + this._deflate[kBuffers] = []; } callback(null, data); @@ -442,6 +447,6 @@ module.exports = PerMessageDeflate; * @private */ function deflateOnData (chunk) { - this.buffers.push(chunk); - this.totalLength += chunk.length; + this[kBuffers].push(chunk); + this[kTotalLength] += chunk.length; } From 31c81a96914c3367b52c667b211dbaf2cf773b11 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Oct 2017 14:30:17 +0100 Subject: [PATCH 361/489] [minor] Refactor `PerMessageDeflate#_decompress()` If context takeover is enabled there is no reason to remove and add the listeners of the `'data'` and `'error'` events every time `PerMessageDeflate#_decompress()` is used. --- lib/PerMessageDeflate.js | 100 ++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 380042d37..35c687d30 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -14,7 +14,10 @@ const EMPTY_BLOCK = Buffer.from([0x00]); const kWriteInProgress = Symbol('write-in-progress'); const kPendingClose = Symbol('pending-close'); const kTotalLength = Symbol('total-length'); +const kCallback = Symbol('callback'); const kBuffers = Symbol('buffers'); +const kError = Symbol('error'); +const kOwner = Symbol('owner'); // We limit zlib concurrency, which prevents severe memory fragmentation // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 @@ -321,35 +324,33 @@ class PerMessageDeflate { : this.params[key]; this._inflate = zlib.createInflateRaw({ windowBits }); + this._inflate[kTotalLength] = 0; + this._inflate[kBuffers] = []; + this._inflate[kOwner] = this; + this._inflate.on('error', inflateOnError); + this._inflate.on('data', inflateOnData); } - this._inflate[kWriteInProgress] = true; - - var totalLength = 0; - const buffers = []; - var err; - const onData = (data) => { - totalLength += data.length; - if (this._maxPayload < 1 || totalLength <= this._maxPayload) { - return buffers.push(data); - } + this._inflate[kCallback] = callback; + this._inflate[kWriteInProgress] = true; - err = new Error('max payload size exceeded'); - err.closeCode = 1009; - this._inflate.reset(); - }; + this._inflate.write(data); + if (fin) this._inflate.write(TRAILER); - const onError = (err) => { - cleanup(); - callback(err); - }; + this._inflate.flush(() => { + const err = this._inflate[kError]; - const cleanup = () => { - if (!this._inflate) return; + if (err) { + this._inflate.close(); + this._inflate = null; + callback(err); + return; + } - this._inflate.removeListener('error', onError); - this._inflate.removeListener('data', onData); - this._inflate[kWriteInProgress] = false; + const data = bufferUtil.concat( + this._inflate[kBuffers], + this._inflate[kTotalLength] + ); if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || @@ -357,17 +358,13 @@ class PerMessageDeflate { ) { this._inflate.close(); this._inflate = null; + } else { + this._inflate[kWriteInProgress] = false; + this._inflate[kTotalLength] = 0; + this._inflate[kBuffers] = []; } - }; - this._inflate.on('error', onError).on('data', onData); - this._inflate.write(data); - if (fin) this._inflate.write(TRAILER); - - this._inflate.flush(() => { - cleanup(); - if (err) callback(err); - else callback(null, bufferUtil.concat(buffers, totalLength)); + callback(null, data); }); } @@ -419,6 +416,7 @@ class PerMessageDeflate { this._deflate[kBuffers], this._deflate[kTotalLength] ); + if (fin) data = data.slice(0, data.length - 4); if ( @@ -450,3 +448,41 @@ function deflateOnData (chunk) { this[kBuffers].push(chunk); this[kTotalLength] += chunk.length; } + +/** + * The listener of the `zlib.InflateRaw` stream `'data'` event. + * + * @param {Buffer} chunk A chunk of data + * @private + */ +function inflateOnData (chunk) { + this[kTotalLength] += chunk.length; + + if ( + this[kOwner]._maxPayload < 1 || + this[kTotalLength] <= this[kOwner]._maxPayload + ) { + this[kBuffers].push(chunk); + return; + } + + this[kError] = new Error('max payload size exceeded'); + this[kError].closeCode = 1009; + this.removeListener('data', inflateOnData); + this.reset(); +} + +/** + * The listener of the `zlib.InflateRaw` stream `'error'` event. + * + * @param {Error} err The emitted error + * @private + */ +function inflateOnError (err) { + // + // There is no need to call `Zlib#close()` as the handle is automatically + // closed when an error is emitted. + // + this[kOwner]._inflate = null; + this[kCallback](err); +} From d6934afcf22afed25b1b9fd06bd4b1df66659aae Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 31 Oct 2017 09:00:10 +0100 Subject: [PATCH 362/489] [test] Fix error validation on node 9 --- test/WebSocket.test.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 3c6ec9eaf..db5304d88 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -84,10 +84,14 @@ describe('WebSocket', function () { }); it('accepts the localAddress option whether it was wrong interface', function () { - assert.throws( - () => new WebSocket(`ws://localhost:${port}`, { localAddress: '123.456.789.428' }), - /must be a valid IP: 123.456.789.428/ - ); + const localAddress = '123.456.789.428'; + + assert.throws(() => { + const ws = new WebSocket(`ws://localhost:${port}`, { localAddress }); + }, (err) => { + return err instanceof TypeError && (err.code === 'ERR_INVALID_IP_ADDRESS' || + err.message.includes(`must be a valid IP: ${localAddress}`)); + }); }); it('accepts the family option', function (done) { From 48b0496879899f35602856d80460926a4a6c299d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 31 Oct 2017 11:11:45 +0100 Subject: [PATCH 363/489] [ci] Do not test on node 4.1.0, use 4.2.0 instead All versions in the range >=4.0.0 <4.2.0 are bugged. --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c94d7feab..b3cd4dd25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,6 @@ node_js: - "8" - "6" - "4" - - "4.1.0" + - "4.2.0" after_success: - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index db5faaaff..dc7e5adbc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ environment: - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - - nodejs_version: "4.1.0" + - nodejs_version: "4.2.0" platform: - x86 - x64 From 9303db3cfafcc1f97e27501d5d3ddc4079f15f5c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Nov 2017 10:33:43 +0100 Subject: [PATCH 364/489] [ci] Test on node 9 --- .travis.yml | 3 ++- appveyor.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b3cd4dd25..1d63d7d33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: node_js sudo: false node_js: + - "9" - "8" - "6" - "4" - "4.2.0" after_success: - - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls" + - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index dc7e5adbc..2502b8c8f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,6 @@ environment: matrix: + - nodejs_version: "9" - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" From d0741faeec6fc6bc6db163545b3534ed822f6cf3 Mon Sep 17 00:00:00 2001 From: Hativ3 Date: Wed, 1 Nov 2017 22:21:05 +0100 Subject: [PATCH 365/489] [feature] Add ecdhCurve option (#1228) Support ecdhCurve option as discussed in #1227. --- lib/WebSocket.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 82d335458..ce1831287 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -472,6 +472,7 @@ function initAsServerClient (socket, head, options) { * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate * @param {String} options.passphrase The passphrase for the private key or pfx * @param {String} options.ciphers The ciphers to use or exclude + * @param {String} options.ecdhCurve The curves for ECDH key agreement to use or exclude * @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key * @param {(String|String[]|Buffer|Buffer[])} options.key The private key * @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs @@ -498,6 +499,7 @@ function initAsClient (address, protocols, options) { rejectUnauthorized: null, passphrase: null, ciphers: null, + ecdhCurve: null, cert: null, key: null, pfx: null, @@ -598,6 +600,7 @@ function initAsClient (address, protocols, options) { options.checkServerIdentity || options.passphrase || options.ciphers || + options.ecdhCurve || options.cert || options.key || options.pfx || @@ -605,6 +608,7 @@ function initAsClient (address, protocols, options) { ) { if (options.passphrase) requestOptions.passphrase = options.passphrase; if (options.ciphers) requestOptions.ciphers = options.ciphers; + if (options.ecdhCurve) requestOptions.ecdhCurve = options.ecdhCurve; if (options.cert) requestOptions.cert = options.cert; if (options.key) requestOptions.key = options.key; if (options.pfx) requestOptions.pfx = options.pfx; From db729efe920d8ebca53254c5cbf0a57f7f43744a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 2 Nov 2017 07:51:33 +0100 Subject: [PATCH 366/489] [doc] Add documentation for the `ecdhCurve` option --- doc/ws.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index ba577ec7f..14f981eec 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -183,12 +183,14 @@ This class represents a WebSocket. It extends the `EventEmitter`. request. - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header depending on the `protocolVersion`. - - `agent` {http.Agent|https.Agent} Use the specified Agent, + - `agent` {http.Agent|https.Agent} Use the specified Agent. - `host` {String} Value of the `Host` header. - `family` {Number} IP address family to use during hostname lookup (4 or 6). - `checkServerIdentity` {Function} A function to validate the server hostname. - `rejectUnauthorized` {Boolean} Verify or not the server certificate. - `passphrase` {String} The passphrase for the private key or pfx. + - `ecdhCurve` {String} A named curve or a colon separated list of curve NIDs + or names to use for ECDH key agreement. - `ciphers` {String} The ciphers to use or exclude - `cert` {String|Array|Buffer} The certificate key. - `key` {String|Array|Buffer} The private key. From e5772a35f151f397f6b555ee3a947b4654c95676 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 2 Nov 2017 08:50:31 +0100 Subject: [PATCH 367/489] chore(package): update nyc to version 11.3.0 (#1230) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f879454b..8ab261128 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", - "nyc": "~11.2.0", + "nyc": "~11.3.0", "utf-8-validate": "~3.0.0" } } From 72751d3d72007f64f97f14f1d4472665b6354e63 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 2 Nov 2017 10:40:27 +0100 Subject: [PATCH 368/489] [test] Skip `family` option test if IPv6 is not supported --- test/WebSocket.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index db5304d88..99cd7551c 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -99,6 +99,12 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); + wss.on('error', (err) => { + // Skip this test on machines where IPv6 is not supported. + if (err.code === 'EADDRNOTAVAIL') err = undefined; + wss.close(() => done(err)); + }); + wss.on('connection', (ws, req) => { assert.strictEqual(req.connection.remoteAddress, '::1'); wss.close(done); From 56f80625399de02abfe6c0d718ea5a8939969318 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 4 Nov 2017 10:20:38 +0100 Subject: [PATCH 369/489] [dist] 3.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ab261128..9b7c16783 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.2.0", + "version": "3.3.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From c4fe46608acd61fbf7397eadc47378903f95b78a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Nov 2017 12:13:20 +0100 Subject: [PATCH 370/489] [security] Fix DoS vulnerability Ignore extension and parameter names that are property names of `Object.prototype` when parsing the `Sec-WebSocket-Extensions` header. --- lib/Extensions.js | 17 ++++++++++++++--- test/Extensions.test.js | 21 ++++++++++++++------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index a91910eb2..9cb555494 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -15,7 +15,13 @@ const parse = (value) => { value.split(',').forEach((v) => { const params = v.split(';'); const token = params.shift().trim(); - const paramsList = extensions[token] = extensions[token] || []; + + if (extensions[token] === undefined) { + extensions[token] = []; + } else if (!extensions.hasOwnProperty(token)) { + return; + } + const parsedParams = {}; params.forEach((param) => { @@ -34,10 +40,15 @@ const parse = (value) => { value = value.slice(0, value.length - 1); } } - (parsedParams[key] = parsedParams[key] || []).push(value); + + if (parsedParams[key] === undefined) { + parsedParams[key] = [value]; + } else if (parsedParams.hasOwnProperty(key)) { + parsedParams[key].push(value); + } }); - paramsList.push(parsedParams); + extensions[token].push(parsedParams); }); return extensions; diff --git a/test/Extensions.test.js b/test/Extensions.test.js index 8b8fe2fe9..169f68ec5 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -6,13 +6,13 @@ const Extensions = require('../lib/Extensions'); describe('Extensions', function () { describe('parse', function () { - it('should parse', function () { + it('parses a single extension', function () { const extensions = Extensions.parse('foo'); assert.deepStrictEqual(extensions, { foo: [{}] }); }); - it('should parse params', function () { + it('parses params', function () { const extensions = Extensions.parse('foo; bar; baz=1; bar=2'); assert.deepStrictEqual(extensions, { @@ -20,7 +20,7 @@ describe('Extensions', function () { }); }); - it('should parse multiple extensions', function () { + it('parse multiple extensions', function () { const extensions = Extensions.parse('foo, bar; baz, foo; baz'); assert.deepStrictEqual(extensions, { @@ -29,29 +29,36 @@ describe('Extensions', function () { }); }); - it('should parse quoted params', function () { + it('parses quoted params', function () { const extensions = Extensions.parse('foo; bar="hi"'); assert.deepStrictEqual(extensions, { foo: [{ bar: ['hi'] }] }); }); + + it('ignores names that match Object.prototype properties', function () { + const parse = Extensions.parse; + + assert.deepStrictEqual(parse('hasOwnProperty, toString'), {}); + assert.deepStrictEqual(parse('foo; constructor'), { foo: [{}] }); + }); }); describe('format', function () { - it('should format', function () { + it('formats a single extension', function () { const extensions = Extensions.format({ foo: {} }); assert.strictEqual(extensions, 'foo'); }); - it('should format params', function () { + it('formats params', function () { const extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); - it('should format multiple extensions', function () { + it('formats multiple extensions', function () { const extensions = Extensions.format({ foo: [{}, { baz: true }], bar: { baz: true } From 70eb3b2f6284a361768ea518acb072d13986dade Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Nov 2017 17:02:01 +0100 Subject: [PATCH 371/489] [dist] 3.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b7c16783..5deece34b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.0", + "version": "3.3.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From cfdecaefe68e48c97e7702fbeb393694bc40ec85 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Nov 2017 18:53:11 +0100 Subject: [PATCH 372/489] [security] Add DoS vulnerablity to SECURITY.md --- SECURITY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SECURITY.md b/SECURITY.md index fd8e07bc5..e8a5b70fb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -31,3 +31,4 @@ all vulnerabilities to the [Node Security Project](https://nodesecurity.io/). ## History 04 Jan 2016: [Buffer vulnerablity](https://github.com/websockets/ws/releases/tag/1.0.1) +08 Nov 2017: [DoS vulnerablity](https://github.com/websockets/ws/releases/tag/3.3.1) From d96c58cda8fa2e67784cb4e37d375c14b2634fb1 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 11 Nov 2017 07:25:32 +0100 Subject: [PATCH 373/489] chore(package): update eslint to version 4.11.0 (#1234) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5deece34b..c20eb812e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.10.0", + "eslint": "~4.11.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From b7089ffc4e10b3dea9703fa1953c99db2241c22d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 16 Nov 2017 14:37:21 +0100 Subject: [PATCH 374/489] [fix] Rewrite the parser of the Sec-WebSocket-Extensions header Make the parser correctly handle quoted values and close the connection if the `Sec-WebSocket-Extensions` header is invalid. Fixes #1235 --- lib/Extensions.js | 212 +++++++++++++++++++++++++++------ lib/WebSocket.js | 17 ++- lib/WebSocketServer.js | 4 +- test/Extensions.test.js | 126 ++++++++++++++++++-- test/PerMessageDeflate.test.js | 7 -- 5 files changed, 301 insertions(+), 65 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 9cb555494..14c38ac2b 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -1,67 +1,203 @@ 'use strict'; +// +// Allowed token characters: +// +// '!', '#', '$', '%', '&', ''', '*', '+', '-', +// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~' +// +// tokenChars[32] === 0 // ' ' +// tokenChars[33] === 1 // '!' +// tokenChars[34] === 0 // '"' +// ... +// +const tokenChars = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127 +]; + /** - * Parse the `Sec-WebSocket-Extensions` header into an object. + * Adds an offer to the map of extension offers. * - * @param {String} value field value of the header + * @param {Object} offers The map of extension offers + * @param {String} name The extension name + * @param {Object} params The extension parameters + * @private + */ +function pushOffer (offers, name, params) { + if (Object.hasOwnProperty.call(offers, name)) offers[name].push(params); + else offers[name] = [params]; +} + +/** + * Adds a parameter to the map of parameters. + * + * @param {Object} params The map of parameters + * @param {String} name The parameter name + * @param {Object} value The parameter value + * @private + */ +function pushParam (params, name, value) { + if (Object.hasOwnProperty.call(params, name)) params[name].push(value); + else params[name] = [value]; +} + +/** + * Parses the `Sec-WebSocket-Extensions` header into an object. + * + * @param {String} header The field value of the header * @return {Object} The parsed object * @public */ -const parse = (value) => { - value = value || ''; +function parse (header) { + const offers = {}; - const extensions = {}; + if (header === undefined || header === '') return offers; - value.split(',').forEach((v) => { - const params = v.split(';'); - const token = params.shift().trim(); + var params = {}; + var mustUnescape = false; + var isEscaping = false; + var inQuotes = false; + var extensionName; + var paramName; + var start = -1; + var end = -1; - if (extensions[token] === undefined) { - extensions[token] = []; - } else if (!extensions.hasOwnProperty(token)) { - return; - } + for (var i = 0; i < header.length; i++) { + const code = header.charCodeAt(i); - const parsedParams = {}; + if (extensionName === undefined) { + if (end === -1 && tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) { + if (end === -1 && start !== -1) end = i; + } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) { + if (start === -1) throw new Error(`unexpected character at index ${i}`); - params.forEach((param) => { - const parts = param.trim().split('='); - const key = parts[0]; - var value = parts[1]; + if (end === -1) end = i; + const name = header.slice(start, end); + if (code === 0x2c) { + pushOffer(offers, name, params); + params = {}; + } else { + extensionName = name; + } - if (value === undefined) { - value = true; + start = end = -1; } else { - // unquote value - if (value[0] === '"') { - value = value.slice(1); - } - if (value[value.length - 1] === '"') { - value = value.slice(0, value.length - 1); + throw new Error(`unexpected character at index ${i}`); + } + } else if (paramName === undefined) { + if (end === -1 && tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (code === 0x20 || code === 0x09) { + if (end === -1 && start !== -1) end = i; + } else if (code === 0x3b || code === 0x2c) { + if (start === -1) throw new Error(`unexpected character at index ${i}`); + + if (end === -1) end = i; + pushParam(params, header.slice(start, end), true); + if (code === 0x2c) { + pushOffer(offers, extensionName, params); + params = {}; + extensionName = undefined; } + + start = end = -1; + } else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) { + paramName = header.slice(start, i); + start = end = -1; + } else { + throw new Error(`unexpected character at index ${i}`); } + } else { + // + // The value of a quoted-string after unescaping must conform to the + // token ABNF, so only token characters are valid. + // Ref: https://tools.ietf.org/html/rfc6455#section-9.1 + // + if (isEscaping) { + if (tokenChars[code] !== 1) { + throw new Error(`unexpected character at index ${i}`); + } + if (start === -1) start = i; + else if (!mustUnescape) mustUnescape = true; + isEscaping = false; + } else if (inQuotes) { + if (tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (code === 0x22/* '"' */ && start !== -1) { + inQuotes = false; + end = i; + } else if (code === 0x5c/* '\' */) { + isEscaping = true; + } else { + throw new Error(`unexpected character at index ${i}`); + } + } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { + inQuotes = true; + } else if (end === -1 && tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (start !== -1 && (code === 0x20 || code === 0x09)) { + if (end === -1) end = i; + } else if (code === 0x3b || code === 0x2c) { + if (start === -1) throw new Error(`unexpected character at index ${i}`); + + if (end === -1) end = i; + var value = header.slice(start, end); + if (mustUnescape) { + value = value.replace(/\\/g, ''); + mustUnescape = false; + } + pushParam(params, paramName, value); + if (code === 0x2c) { + pushOffer(offers, extensionName, params); + params = {}; + extensionName = undefined; + } - if (parsedParams[key] === undefined) { - parsedParams[key] = [value]; - } else if (parsedParams.hasOwnProperty(key)) { - parsedParams[key].push(value); + paramName = undefined; + start = end = -1; + } else { + throw new Error(`unexpected character at index ${i}`); } - }); + } + } + + if (start === -1 || inQuotes) throw new Error('unexpected end of input'); - extensions[token].push(parsedParams); - }); + if (end === -1) end = i; + const token = header.slice(start, end); + if (extensionName === undefined) { + pushOffer(offers, token, {}); + } else { + if (paramName === undefined) { + pushParam(params, token, true); + } else if (mustUnescape) { + pushParam(params, paramName, token.replace(/\\/g, '')); + } else { + pushParam(params, paramName, token); + } + pushOffer(offers, extensionName, params); + } - return extensions; -}; + return offers; +} /** - * Serialize a parsed `Sec-WebSocket-Extensions` header to a string. + * Serializes a parsed `Sec-WebSocket-Extensions` header to a string. * * @param {Object} value The object to format * @return {String} A string representing the given value * @public */ -const format = (value) => { +function format (value) { return Object.keys(value).map((token) => { var paramsList = value[token]; if (!Array.isArray(paramsList)) paramsList = [paramsList]; @@ -73,6 +209,6 @@ const format = (value) => { })).join('; '); }).join(', '); }).join(', '); -}; +} module.exports = { format, parse }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ce1831287..ccfc169ab 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -692,18 +692,17 @@ function initAsClient (address, protocols, options) { if (serverProt) this.protocol = serverProt; - const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); + try { + const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); - if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { - try { + if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); - } catch (err) { - socket.destroy(); - this.emit('error', new Error('invalid extension parameter')); - return this.finalize(true); + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - - this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } catch (err) { + socket.destroy(); + this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); + return this.finalize(true); } this.setSocket(socket, head); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 790add824..fd1fb8347 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -227,11 +227,11 @@ class WebSocketServer extends EventEmitter { if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - const offer = Extensions.parse(req.headers['sec-websocket-extensions']); var extensions; try { - extensions = acceptExtensions(this.options, offer); + const offers = Extensions.parse(req.headers['sec-websocket-extensions']); + extensions = acceptExtensions(this.options, offers); } catch (err) { return abortConnection(socket, 400); } diff --git a/test/Extensions.test.js b/test/Extensions.test.js index 169f68ec5..f08ab0852 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -6,6 +6,11 @@ const Extensions = require('../lib/Extensions'); describe('Extensions', function () { describe('parse', function () { + it('returns an empty object if the argument is undefined', function () { + assert.deepStrictEqual(Extensions.parse(), {}); + assert.deepStrictEqual(Extensions.parse(''), {}); + }); + it('parses a single extension', function () { const extensions = Extensions.parse('foo'); @@ -13,15 +18,15 @@ describe('Extensions', function () { }); it('parses params', function () { - const extensions = Extensions.parse('foo; bar; baz=1; bar=2'); + const extensions = Extensions.parse('foo;bar;baz=1;bar=2'); assert.deepStrictEqual(extensions, { foo: [{ bar: [true, '2'], baz: ['1'] }] }); }); - it('parse multiple extensions', function () { - const extensions = Extensions.parse('foo, bar; baz, foo; baz'); + it('parses multiple extensions', function () { + const extensions = Extensions.parse('foo,bar;baz,foo;baz'); assert.deepStrictEqual(extensions, { foo: [{}, { baz: [true] }], @@ -30,18 +35,121 @@ describe('Extensions', function () { }); it('parses quoted params', function () { - const extensions = Extensions.parse('foo; bar="hi"'); - - assert.deepStrictEqual(extensions, { + assert.deepStrictEqual(Extensions.parse('foo;bar="hi"'), { foo: [{ bar: ['hi'] }] }); + assert.deepStrictEqual(Extensions.parse('foo;bar="\\0"'), { + foo: [{ bar: ['0'] }] + }); + assert.deepStrictEqual(Extensions.parse('foo;bar="b\\a\\z"'), { + foo: [{ bar: ['baz'] }] + }); + assert.deepStrictEqual(Extensions.parse('foo;bar="b\\az";bar'), { + foo: [{ bar: ['baz', true] }] + }); + assert.throws( + () => Extensions.parse('foo;bar="baz"qux'), + /^Error: unexpected character at index 13$/ + ); + assert.throws( + () => Extensions.parse('foo;bar="baz" qux'), + /^Error: unexpected character at index 14$/ + ); }); - it('ignores names that match Object.prototype properties', function () { + it('works with names that match Object.prototype property names', function () { const parse = Extensions.parse; - assert.deepStrictEqual(parse('hasOwnProperty, toString'), {}); - assert.deepStrictEqual(parse('foo; constructor'), { foo: [{}] }); + assert.deepStrictEqual(parse('hasOwnProperty, toString'), { + hasOwnProperty: [{}], + toString: [{}] + }); + assert.deepStrictEqual(parse('foo;constructor'), { + foo: [{ constructor: [true] }] + }); + }); + + it('ignores the optional white spaces', function () { + const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf '; + + assert.deepStrictEqual(Extensions.parse(header), { + foo: [{ bar: [true, '1'], baz: ['1'] }], + qux: [{ norf: [true] }] + }); + }); + + it('throws an error if a name is empty', function () { + [ + [',', 0], + ['foo,,', 4], + ['foo, ,', 6], + ['foo;=', 4], + ['foo; =', 5], + ['foo;;', 4], + ['foo; ;', 5], + ['foo;bar=,', 8], + ['foo;bar=""', 9] + ].forEach((element) => { + assert.throws( + () => Extensions.parse(element[0]), + new RegExp(`^Error: unexpected character at index ${element[1]}$`) + ); + }); + }); + + it('throws an error if a white space is misplaced', function () { + [ + ['f oo', 2], + ['foo;ba r', 7], + ['foo;bar =', 8], + ['foo;bar= ', 8] + ].forEach((element) => { + assert.throws( + () => Extensions.parse(element[0]), + new RegExp(`^Error: unexpected character at index ${element[1]}$`) + ); + }); + }); + + it('throws an error if a token contains invalid characters', function () { + [ + ['f@o', 1], + ['f\\oo', 1], + ['"foo"', 0], + ['f"oo"', 1], + ['foo;b@r', 5], + ['foo;b\\ar', 5], + ['foo;"bar"', 4], + ['foo;b"ar"', 5], + ['foo;bar=b@z', 9], + ['foo;bar=b\\az ', 9], + ['foo;bar="b@z"', 10], + ['foo;bar="baz;"', 12], + ['foo;bar=b"az"', 9], + ['foo;bar="\\\\"', 10] + ].forEach((element) => { + assert.throws( + () => Extensions.parse(element[0]), + new RegExp(`^Error: unexpected character at index ${element[1]}$`) + ); + }); + }); + + it('throws an error if the header value ends prematurely', function () { + [ + 'foo, ', + 'foo;', + 'foo;bar,', + 'foo;bar; ', + 'foo;bar=', + 'foo;bar="baz', + 'foo;bar="1\\' + ].forEach((header) => { + assert.throws( + () => Extensions.parse(header), + /^Error: unexpected end of input$/ + ); + }); }); }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index b66ddd082..bfe25acb7 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -174,13 +174,6 @@ describe('PerMessageDeflate', function () { assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); - it('should throw an error if a parameter is undefined', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; foo;'); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - it('should throw an error if server_no_context_takeover has a value', function () { const perMessageDeflate = new PerMessageDeflate(); const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); From 5d973fb42d4cd96ff742946a0224d687f03b0209 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 16 Nov 2017 17:52:36 +0100 Subject: [PATCH 375/489] [minor] Parse the Sec-WebSocket-Extensions header only when necessary Avoid parsing the `Sec-WebSocket-Extensions` header if the only supported extension, `permessage-deflate`, is disabled. --- lib/WebSocket.js | 49 +++++++++++----------- lib/WebSocketServer.js | 95 +++++++++++++++++++----------------------- 2 files changed, 68 insertions(+), 76 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ccfc169ab..76e42c078 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -527,21 +527,8 @@ function initAsClient (address, protocols, options) { const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; - - // - // Prepare extensions. - // - const extensionsOffer = {}; var perMessageDeflate; - if (options.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate( - options.perMessageDeflate !== true ? options.perMessageDeflate : {}, - false - ); - extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); - } - const requestOptions = { port: serverUrl.port || (isSecure ? 443 : 80), host: serverUrl.hostname, @@ -555,8 +542,14 @@ function initAsClient (address, protocols, options) { }; if (options.headers) Object.assign(requestOptions.headers, options.headers); - if (Object.keys(extensionsOffer).length) { - requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); + if (options.perMessageDeflate) { + perMessageDeflate = new PerMessageDeflate( + options.perMessageDeflate !== true ? options.perMessageDeflate : {}, + false + ); + requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format({ + [PerMessageDeflate.extensionName]: perMessageDeflate.offer() + }); } if (options.protocol) { requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; @@ -692,17 +685,23 @@ function initAsClient (address, protocols, options) { if (serverProt) this.protocol = serverProt; - try { - const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); - - if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { - perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); - this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + if (perMessageDeflate) { + try { + const serverExtensions = Extensions.parse( + res.headers['sec-websocket-extensions'] + ); + + if (serverExtensions[PerMessageDeflate.extensionName]) { + perMessageDeflate.accept( + serverExtensions[PerMessageDeflate.extensionName] + ); + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } + } catch (err) { + socket.destroy(); + this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); + return this.finalize(true); } - } catch (err) { - socket.destroy(); - this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); - return this.finalize(true); } this.setSocket(socket, head); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index fd1fb8347..571078f80 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -90,6 +90,7 @@ class WebSocketServer extends EventEmitter { }); } + if (options.perMessageDeflate === true) options.perMessageDeflate = {}; if (options.clientTracking) this.clients = new Set(); this.options = options; } @@ -151,6 +152,7 @@ class WebSocketServer extends EventEmitter { socket.on('error', socketError); const version = +req.headers['sec-websocket-version']; + const extensions = {}; if ( req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || @@ -160,6 +162,27 @@ class WebSocketServer extends EventEmitter { return abortConnection(socket, 400); } + if (this.options.perMessageDeflate) { + const perMessageDeflate = new PerMessageDeflate( + this.options.perMessageDeflate, + true, + this.options.maxPayload + ); + + try { + const offers = Extensions.parse( + req.headers['sec-websocket-extensions'] + ); + + if (offers[PerMessageDeflate.extensionName]) { + perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); + extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } + } catch (err) { + return abortConnection(socket, 400); + } + } + var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); // @@ -186,21 +209,30 @@ class WebSocketServer extends EventEmitter { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - this.completeUpgrade(protocol, version, req, socket, head, cb); + this.completeUpgrade( + protocol, + extensions, + version, + req, + socket, + head, + cb + ); }); return; - } else if (!this.options.verifyClient(info)) { - return abortConnection(socket, 401); } + + if (!this.options.verifyClient(info)) return abortConnection(socket, 401); } - this.completeUpgrade(protocol, version, req, socket, head, cb); + this.completeUpgrade(protocol, extensions, version, req, socket, head, cb); } /** * Upgrade the connection to WebSocket. * * @param {String} protocol The chosen subprotocol + * @param {Object} extensions The accepted extensions * @param {Number} version The WebSocket protocol version * @param {http.IncomingMessage} req The request object * @param {net.Socket} socket The network socket between the server and client @@ -208,7 +240,7 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @private */ - completeUpgrade (protocol, version, req, socket, head, cb) { + completeUpgrade (protocol, extensions, version, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // @@ -226,25 +258,12 @@ class WebSocketServer extends EventEmitter { ]; if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - - var extensions; - - try { - const offers = Extensions.parse(req.headers['sec-websocket-extensions']); - extensions = acceptExtensions(this.options, offers); - } catch (err) { - return abortConnection(socket, 400); - } - - const props = Object.keys(extensions); - - if (props.length) { - const serverExtensions = props.reduce((obj, key) => { - obj[key] = [extensions[key].params]; - return obj; - }, {}); - - headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); + if (extensions[PerMessageDeflate.extensionName]) { + const params = extensions[PerMessageDeflate.extensionName].params; + const value = Extensions.format({ + [PerMessageDeflate.extensionName]: [params] + }); + headers.push(`Sec-WebSocket-Extensions: ${value}`); } // @@ -252,7 +271,7 @@ class WebSocketServer extends EventEmitter { // this.emit('headers', headers, req); - socket.write(headers.concat('', '').join('\r\n')); + socket.write(headers.concat('\r\n').join('\r\n')); const client = new WebSocket([socket, head], null, { maxPayload: this.options.maxPayload, @@ -282,32 +301,6 @@ function socketError () { this.destroy(); } -/** - * Accept WebSocket extensions. - * - * @param {Object} options The `WebSocketServer` configuration options - * @param {Object} offer The parsed value of the `sec-websocket-extensions` header - * @return {Object} Accepted extensions - * @private - */ -function acceptExtensions (options, offer) { - const pmd = options.perMessageDeflate; - const extensions = {}; - - if (pmd && offer[PerMessageDeflate.extensionName]) { - const perMessageDeflate = new PerMessageDeflate( - pmd !== true ? pmd : {}, - true, - options.maxPayload - ); - - perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]); - extensions[PerMessageDeflate.extensionName] = perMessageDeflate; - } - - return extensions; -} - /** * Close the connection when preconditions are not fulfilled. * From b4465f6c86e0ca7317f97a180b347baa0f109f6e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 17 Nov 2017 10:50:23 +0100 Subject: [PATCH 376/489] [test] Increase code coverage --- test/WebSocket.test.js | 87 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 99cd7551c..84e20baf7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -352,7 +352,7 @@ describe('WebSocket', function () { it('invalid server key is denied', function (done) { server.once('upgrade', (req, socket) => { - socket.on('end', () => socket.end()); + socket.on('end', socket.end); socket.write( 'HTTP/1.1 101 Switching Protocols\r\n' + 'Upgrade: websocket\r\n' + @@ -408,7 +408,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'unexpected server response (401)'); @@ -430,8 +430,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', () => done(new Error("unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -462,8 +462,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', () => done(new Error("unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -472,20 +472,75 @@ describe('WebSocket', function () { }); }); - it('emits an error if the opening handshake timeout expires', function (done) { + it('fails 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('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'opening handshake has timed out'); done(); }); }); + + it('fails if the Sec-WebSocket-Extensions response header is invalid', function (done) { + server.once('upgrade', (req, socket) => { + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') + .digest('base64'); + + socket.end( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept: ${key}\r\n` + + 'Sec-WebSocket-Extensions: foo;=\r\n' + + '\r\n' + ); + }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid Sec-WebSocket-Extensions header'); + ws.on('close', () => done()); + }); + }); + + it('fails if server sends a subprotocol when none was requested', function (done) { + server.once('upgrade', (req, socket) => { + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') + .digest('base64'); + + socket.end( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept: ${key}\r\n` + + 'Sec-WebSocket-Protocol: foo\r\n' + + '\r\n' + ); + }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'server sent a subprotocol even though none requested' + ); + ws.on('close', () => done()); + }); + }); }); describe('connection with query string', function () { @@ -968,7 +1023,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -985,7 +1040,7 @@ describe('WebSocket', function () { }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -998,7 +1053,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket(`ws://localhost:${++port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1011,7 +1066,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -1192,7 +1247,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -1209,7 +1264,7 @@ describe('WebSocket', function () { }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -1222,7 +1277,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket(`ws://localhost:${++port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1235,7 +1290,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); From 0c8c0b8785ad0b85ef248cf921e58b1b5026db3c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Nov 2017 15:29:12 +0100 Subject: [PATCH 377/489] [minor] Add JSDoc for `PerMessageDeflate` constructor --- lib/PerMessageDeflate.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 35c687d30..7c538892b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -19,18 +19,41 @@ const kBuffers = Symbol('buffers'); const kError = Symbol('error'); const kOwner = Symbol('owner'); +// // We limit zlib concurrency, which prevents severe memory fragmentation // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 // and https://github.com/websockets/ws/issues/1202 // -// Intentionally global; it's the global thread pool that's -// an issue. +// Intentionally global; it's the global thread pool that's an issue. +// let zlibLimiter; /** - * Per-message Deflate implementation. + * permessage-deflate implementation. */ class PerMessageDeflate { + /** + * Creates a PerMessageDeflate instance. + * + * @param {Object} options Configuration options + * @param {Boolean} options.serverNoContextTakeover Request/accept disabling + * of server context takeover + * @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge + * disabling of client context takeover + * @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the + * use of a custom server window size + * @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support + * for, or request, a custom client window size + * @param {Number} options.level The value of zlib's `level` param + * @param {Number} options.memLevel The value of zlib's `memLevel` param + * @param {Number} options.threshold Size (in bytes) below which messages + * should not be compressed + * @param {Number} options.concurrencyLimit The number of concurrent calls to + * zlib + * @param {Boolean} isServer Create the instance in either server or client + * mode + * @param {Number} maxPayload The maximum allowed message length + */ constructor (options, isServer, maxPayload) { this._maxPayload = maxPayload | 0; this._options = options || {}; @@ -51,6 +74,9 @@ class PerMessageDeflate { } } + /** + * @type {String} + */ static get extensionName () { return 'permessage-deflate'; } From 16f727d3e061eff7713eabc1ae367f65438d8ddb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Nov 2017 16:05:12 +0100 Subject: [PATCH 378/489] [doc] Clarify `PerMessageDeflate` options --- doc/ws.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 14f981eec..2975958f1 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -66,12 +66,11 @@ status code, otherwise the returned value sets the value of the The extension is disabled when `false` (default value). If an object is provided then that is extension parameters: -- `serverNoContextTakeover` {Boolean} Whether to use context take over or not. -- `clientNoContextTakeover` {Boolean} The value to be requested to clients - whether to use context take over or not. -- `serverMaxWindowBits` {Number} The value of windowBits. -- `clientMaxWindowBits` {Number} The value of max windowBits to be requested - to clients. +- `serverNoContextTakeover` {Boolean} Whether to use context takeover or not. +- `clientNoContextTakeover` {Boolean} Acknowledge disabling of client context + takeover. +- `serverMaxWindowBits` {Number} The value of `windowBits`. +- `clientMaxWindowBits` {Number} Request a custom client window size. - `level` {Number} The value of zlib's `level` param (0-9, default 8). - `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). - `threshold` {Number} Payloads smaller than this will not be compressed. @@ -198,8 +197,9 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `ca` {Array} Trusted certificates. `perMessageDeflate` default value is `true`. When using an object, parameters -are the same of the server. The only difference is the direction of requests -(e.g. `serverNoContextTakeover` is the value to be requested to the server). +are the same of the server. The only difference is the direction of requests. +For example, `serverNoContextTakeover` can be used to ask the server to +disable context takeover. Create a new WebSocket instance. From 0a9621f9ff35e6f80c9c8471d0b202af4e357705 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Nov 2017 18:41:58 +0100 Subject: [PATCH 379/489] [minor] Merge `pushOffer()` and `pushParam()` into `push()` --- lib/Extensions.js | 49 ++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 14c38ac2b..15fee1216 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -23,29 +23,18 @@ const tokenChars = [ ]; /** - * Adds an offer to the map of extension offers. + * Adds an offer to the map of extension offers or a parameter to the map of + * parameters. * - * @param {Object} offers The map of extension offers - * @param {String} name The extension name - * @param {Object} params The extension parameters + * @param {Object} dest The map of extension offers or parameters + * @param {String} name The extension or parameter name + * @param {(Object|Boolean|String)} elem The extension parameters or the + * parameter value * @private */ -function pushOffer (offers, name, params) { - if (Object.hasOwnProperty.call(offers, name)) offers[name].push(params); - else offers[name] = [params]; -} - -/** - * Adds a parameter to the map of parameters. - * - * @param {Object} params The map of parameters - * @param {String} name The parameter name - * @param {Object} value The parameter value - * @private - */ -function pushParam (params, name, value) { - if (Object.hasOwnProperty.call(params, name)) params[name].push(value); - else params[name] = [value]; +function push (dest, name, elem) { + if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem); + else dest[name] = [elem]; } /** @@ -83,7 +72,7 @@ function parse (header) { if (end === -1) end = i; const name = header.slice(start, end); if (code === 0x2c) { - pushOffer(offers, name, params); + push(offers, name, params); params = {}; } else { extensionName = name; @@ -102,9 +91,9 @@ function parse (header) { if (start === -1) throw new Error(`unexpected character at index ${i}`); if (end === -1) end = i; - pushParam(params, header.slice(start, end), true); + push(params, header.slice(start, end), true); if (code === 0x2c) { - pushOffer(offers, extensionName, params); + push(offers, extensionName, params); params = {}; extensionName = undefined; } @@ -155,9 +144,9 @@ function parse (header) { value = value.replace(/\\/g, ''); mustUnescape = false; } - pushParam(params, paramName, value); + push(params, paramName, value); if (code === 0x2c) { - pushOffer(offers, extensionName, params); + push(offers, extensionName, params); params = {}; extensionName = undefined; } @@ -175,16 +164,16 @@ function parse (header) { if (end === -1) end = i; const token = header.slice(start, end); if (extensionName === undefined) { - pushOffer(offers, token, {}); + push(offers, token, {}); } else { if (paramName === undefined) { - pushParam(params, token, true); + push(params, token, true); } else if (mustUnescape) { - pushParam(params, paramName, token.replace(/\\/g, '')); + push(params, paramName, token.replace(/\\/g, '')); } else { - pushParam(params, paramName, token); + push(params, paramName, token); } - pushOffer(offers, extensionName, params); + push(offers, extensionName, params); } return offers; From 9c73abe805db1cf7d8fd511c7e0a46b3a7139ede Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 19 Nov 2017 10:58:06 +0100 Subject: [PATCH 380/489] [minor] Remove some redundant code --- lib/PerMessageDeflate.js | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 7c538892b..6ed12a7b7 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -221,28 +221,21 @@ class PerMessageDeflate { acceptAsClient (paramsList) { const params = paramsList[0]; - if (this._options.clientNoContextTakeover != null) { - if ( - this._options.clientNoContextTakeover === false && - params.client_no_context_takeover - ) { - throw new Error('Invalid value for "client_no_context_takeover"'); - } + if ( + this._options.clientNoContextTakeover === false && + params.client_no_context_takeover + ) { + throw new Error('Invalid value for "client_no_context_takeover"'); } - if (this._options.clientMaxWindowBits != null) { - if ( - this._options.clientMaxWindowBits === false && - params.client_max_window_bits - ) { - throw new Error('Invalid value for "client_max_window_bits"'); - } - if ( - typeof this._options.clientMaxWindowBits === 'number' && + + if ( + (typeof this._options.clientMaxWindowBits === 'number' && (!params.client_max_window_bits || - params.client_max_window_bits > this._options.clientMaxWindowBits) - ) { - throw new Error('Invalid value for "client_max_window_bits"'); - } + params.client_max_window_bits > this._options.clientMaxWindowBits)) || + (this._options.clientMaxWindowBits === false && + params.client_max_window_bits) + ) { + throw new Error('Invalid value for "client_max_window_bits"'); } return params; From 02d0011e73786fd00b09f66a06338ee204535255 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 20 Nov 2017 15:43:20 +0100 Subject: [PATCH 381/489] [test] Use an OS-assigned arbitrary unused port --- test/WebSocket.test.js | 358 ++++++++++++++++++------------ test/WebSocketServer.test.js | 411 ++++++++++++++++------------------- 2 files changed, 406 insertions(+), 363 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 84e20baf7..0cad5ddac 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -13,8 +13,6 @@ const constants = require('../lib/Constants'); const WebSocket = require('..'); const Buffer = safeBuffer.Buffer; -const WebSocketServer = WebSocket.Server; -let port = 20000; class CustomAgent extends http.Agent { addRequest () {} @@ -65,7 +63,8 @@ describe('WebSocket', function () { }); it('accepts the localAddress option', function (done) { - const wss = new WebSocketServer({ host: '127.0.0.1', port: ++port }, () => { + const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { localAddress: '127.0.0.2' }); @@ -87,7 +86,7 @@ describe('WebSocket', function () { const localAddress = '123.456.789.428'; assert.throws(() => { - const ws = new WebSocket(`ws://localhost:${port}`, { localAddress }); + const ws = new WebSocket('ws://localhost', { localAddress }); }, (err) => { return err instanceof TypeError && (err.code === 'ERR_INVALID_IP_ADDRESS' || err.message.includes(`must be a valid IP: ${localAddress}`)); @@ -95,7 +94,8 @@ describe('WebSocket', function () { }); it('accepts the family option', function (done) { - const wss = new WebSocketServer({ host: '::1', port: ++port }, () => { + const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); @@ -114,7 +114,8 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('message', () => { assert.strictEqual(ws.bytesReceived, 8); @@ -125,14 +126,14 @@ describe('WebSocket', function () { }); it('#url exposes the server url', function () { - const url = `ws://localhost:${port}`; + const url = 'ws://localhost'; const ws = new WebSocket(url, { agent: new CustomAgent() }); assert.strictEqual(ws.url, url); }); it('#protocolVersion exposes the protocol version', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -141,7 +142,7 @@ describe('WebSocket', function () { describe('#bufferedAmount', function () { it('defaults to zero', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -149,7 +150,8 @@ describe('WebSocket', function () { }); it('defaults to zero upon "open"', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.onopen = () => { @@ -160,10 +162,11 @@ describe('WebSocket', function () { }); it('takes into account the data in the sender queue', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -182,7 +185,8 @@ describe('WebSocket', function () { }); it('takes into account the data in the socket queue', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -202,11 +206,11 @@ describe('WebSocket', function () { describe('Custom headers', function () { const server = http.createServer(); - beforeEach((done) => server.listen(++port, done)); + beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); it('request has an authorization header', function (done) { - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); const auth = 'test:testpass'; server.once('upgrade', (req, socket, head) => { @@ -219,11 +223,12 @@ describe('WebSocket', function () { wss.close(done); }); + const port = server.address().port; const ws = new WebSocket(`ws://${auth}@localhost:${port}`); }); it('accepts custom headers', function (done) { - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); server.once('upgrade', (req, socket, head) => { assert.ok(req.headers.cookie); @@ -232,7 +237,7 @@ describe('WebSocket', function () { wss.close(done); }); - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { headers: { 'Cookie': 'foo=bar' } }); }); @@ -240,7 +245,7 @@ describe('WebSocket', function () { describe('#readyState', function () { it('defaults to connecting', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -248,7 +253,8 @@ describe('WebSocket', function () { }); it('set to open once connection is established', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -261,7 +267,8 @@ describe('WebSocket', function () { }); it('set to closed once connection is closed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { @@ -274,7 +281,8 @@ describe('WebSocket', function () { }); it('set to closed once connection is terminated', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { @@ -316,7 +324,8 @@ describe('WebSocket', function () { describe('events', function () { it('emits a ping event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('ping', () => wss.close(done)); }); @@ -325,7 +334,8 @@ describe('WebSocket', function () { }); it('emits a pong event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('pong', () => wss.close(done)); }); @@ -334,7 +344,8 @@ describe('WebSocket', function () { }); it('emits a headers event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('headers', (headers, res) => { assert.strictEqual(headers, res.headers); @@ -347,7 +358,7 @@ describe('WebSocket', function () { describe('connection establishing', function () { const server = http.createServer(); - beforeEach((done) => server.listen(++port, done)); + beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); it('invalid server key is denied', function (done) { @@ -362,7 +373,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('error', (err) => { assert.ok(err instanceof Error); @@ -386,7 +397,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('close', (code, reason) => { assert.strictEqual(code, 1006); @@ -406,7 +417,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -428,7 +439,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', () => done(new Error("unexpected 'error' event"))); @@ -460,7 +471,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', () => done(new Error("unexpected 'error' event"))); @@ -475,6 +486,7 @@ describe('WebSocket', function () { it('fails if the opening handshake timeout expires', function (done) { server.once('upgrade', (req, socket) => socket.on('end', socket.end)); + const port = server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, null, { handshakeTimeout: 100 }); @@ -503,7 +515,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -529,7 +541,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -545,7 +557,8 @@ describe('WebSocket', function () { describe('connection with query string', function () { it('connects when pathname is not null', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -553,7 +566,8 @@ describe('WebSocket', function () { }); it('connects when pathname is null', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -601,7 +615,8 @@ describe('WebSocket', function () { client.send('foo'); }; - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); serverClient = ws; @@ -617,7 +632,7 @@ describe('WebSocket', function () { describe('#ping', function () { it('before connect should fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -625,7 +640,7 @@ describe('WebSocket', function () { }); it('before connect can silently fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -633,7 +648,8 @@ describe('WebSocket', function () { }); it('without message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.ping()); @@ -645,7 +661,8 @@ describe('WebSocket', function () { }); it('with message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -664,7 +681,8 @@ describe('WebSocket', function () { }); it('can send numbers as ping payload', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.ping(0)); @@ -681,7 +699,7 @@ describe('WebSocket', function () { describe('#pong', function () { it('before connect should fail', () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -689,7 +707,7 @@ describe('WebSocket', function () { }); it('before connect can silently fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -697,7 +715,8 @@ describe('WebSocket', function () { }); it('without message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.pong()); @@ -709,7 +728,8 @@ describe('WebSocket', function () { }); it('with message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -728,7 +748,8 @@ describe('WebSocket', function () { }); it('can send numbers as pong payload', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.pong(0)); @@ -745,13 +766,14 @@ describe('WebSocket', function () { describe('#send', function () { it('very long binary data can be sent and received', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5 * 1024 * 1024); for (let i = 0; i < array.length; i++) { array[i] = i / 5; } + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { compress: false })); @@ -767,7 +789,8 @@ describe('WebSocket', function () { }); it('can send and receive text data', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi')); @@ -783,7 +806,8 @@ describe('WebSocket', function () { }); it('does not override the `fin` option', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -801,7 +825,8 @@ describe('WebSocket', function () { }); it('sends numbers as strings', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(0)); @@ -816,7 +841,7 @@ describe('WebSocket', function () { }); it('can send binary data as an array', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(6); for (let i = 0; i < array.length; ++i) { @@ -827,6 +852,7 @@ describe('WebSocket', function () { const buf = Buffer.from(partial.buffer) .slice(partial.byteOffset, partial.byteOffset + partial.byteLength); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(partial, { binary: true })); @@ -842,8 +868,9 @@ describe('WebSocket', function () { }); it('can send binary data as a buffer', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(buf, { binary: true })); @@ -859,13 +886,14 @@ describe('WebSocket', function () { }); it('ArrayBuffer is auto-detected without binary flag', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5); for (let i = 0; i < array.length; ++i) { array[i] = i / 2; } + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array.buffer)); @@ -881,8 +909,9 @@ describe('WebSocket', function () { }); it('Buffer is auto-detected without binary flag', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(buf)); @@ -899,7 +928,7 @@ describe('WebSocket', function () { }); it('before connect should fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -907,7 +936,7 @@ describe('WebSocket', function () { }); it('before connect should pass error through callback, if present', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -918,7 +947,8 @@ describe('WebSocket', function () { }); it('without data should be successful', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send()); @@ -933,7 +963,8 @@ describe('WebSocket', function () { }); it('calls optional callback when flushed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -946,7 +977,8 @@ describe('WebSocket', function () { }); it('with unmasked message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi', { mask: false })); @@ -961,7 +993,8 @@ describe('WebSocket', function () { }); it('with masked message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi', { mask: true })); @@ -982,7 +1015,8 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { mask: false, binary: true })); @@ -1003,7 +1037,8 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { mask: true, binary: true })); @@ -1020,7 +1055,8 @@ describe('WebSocket', function () { describe('#close', function () { it('closes the connection if called while connecting (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1034,10 +1070,11 @@ describe('WebSocket', function () { }); it('closes the connection if called while connecting (2/2)', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1051,7 +1088,7 @@ describe('WebSocket', function () { }); it('can be called from an error listener while connecting', function (done) { - const ws = new WebSocket(`ws://localhost:${++port}`); + const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -1063,7 +1100,8 @@ describe('WebSocket', function () { }); it('can be called from a listener of the headers event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1077,7 +1115,8 @@ describe('WebSocket', function () { }); it('throws an error if the first argument is invalid (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -1092,7 +1131,8 @@ describe('WebSocket', function () { }); it('throws an error if the first argument is invalid (2/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -1107,7 +1147,8 @@ describe('WebSocket', function () { }); it('works when close reason is not specified', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close(1000)); @@ -1123,7 +1164,8 @@ describe('WebSocket', function () { }); it('works when close reason is specified', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close(1000, 'some reason')); @@ -1139,10 +1181,11 @@ describe('WebSocket', function () { }); it('ends connection to the server', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ clientTracking: false, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -1157,10 +1200,11 @@ describe('WebSocket', function () { }); it('permits all buffered data to be delivered', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; @@ -1181,7 +1225,8 @@ describe('WebSocket', function () { }); it('allows close code 1013', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { @@ -1195,7 +1240,7 @@ describe('WebSocket', function () { it('closes the connection when an error occurs', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); let closed = false; wss.on('connection', (ws) => { @@ -1212,8 +1257,8 @@ describe('WebSocket', function () { }); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => ws._socket.write(Buffer.from([0xa1, 0x00]))); ws.on('close', (code, reason) => { @@ -1227,7 +1272,8 @@ describe('WebSocket', function () { }); it('does nothing if the connection is already CLOSED', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { @@ -1244,7 +1290,8 @@ describe('WebSocket', function () { describe('#terminate', function () { it('closes the connection if called while connecting (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1258,10 +1305,11 @@ describe('WebSocket', function () { }); it('closes the connection if called while connecting (2/2)', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1275,7 +1323,7 @@ describe('WebSocket', function () { }); it('can be called from an error listener while connecting', function (done) { - const ws = new WebSocket(`ws://localhost:${++port}`); + const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -1287,7 +1335,8 @@ describe('WebSocket', function () { }); it('can be called from a listener of the headers event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1301,7 +1350,8 @@ describe('WebSocket', function () { }); it('does nothing if the connection is already CLOSED', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { @@ -1363,10 +1413,11 @@ describe('WebSocket', function () { }); it('should work the same as the EventEmitter api', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ clientTracking: false, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); let message = 0; let close = 0; @@ -1468,7 +1519,8 @@ describe('WebSocket', function () { }); it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', () => ws.send('hi')); @@ -1484,7 +1536,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1498,7 +1551,8 @@ describe('WebSocket', function () { }); it('should assign "true" to wasClean when server closes with code 3000', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1511,7 +1565,8 @@ describe('WebSocket', function () { }); it('should assign "true" to wasClean when server closes with code 4999', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1524,7 +1579,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1001', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1539,7 +1595,8 @@ describe('WebSocket', function () { }); it('should have target set on Events', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', (openEvent) => { @@ -1565,7 +1622,8 @@ describe('WebSocket', function () { }); it('should have type set on Events', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', (openEvent) => { @@ -1591,7 +1649,8 @@ describe('WebSocket', function () { }); it('should pass binary data as a Node.js Buffer by default', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.onmessage = (evt) => { @@ -1604,7 +1663,8 @@ describe('WebSocket', function () { }); it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; @@ -1619,7 +1679,8 @@ describe('WebSocket', function () { }); it('should ignore binaryType for text messages', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; @@ -1634,7 +1695,8 @@ describe('WebSocket', function () { }); it('should allow to update binaryType on the fly', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); function testType (binaryType, next) { @@ -1678,16 +1740,18 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { wss.close(); server.close(done); }); - server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { - rejectUnauthorized: false - })); + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + rejectUnauthorized: false + }); + }); }); it('can connect to secure websocket server with client side certificate', function (done) { @@ -1699,7 +1763,7 @@ describe('WebSocket', function () { }); let success = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { success = !!info.req.client.authorized; return true; @@ -1713,8 +1777,8 @@ describe('WebSocket', function () { wss.close(); }); - server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { cert: fs.readFileSync('test/fixtures/agent1-cert.pem'), key: fs.readFileSync('test/fixtures/agent1-key.pem'), rejectUnauthorized: false @@ -1727,10 +1791,10 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { rejectUnauthorized: false }); @@ -1746,7 +1810,7 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { ws.on('message', (message) => { @@ -1756,8 +1820,8 @@ describe('WebSocket', function () { }); }); - server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { rejectUnauthorized: false }); @@ -1773,14 +1837,14 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { ws.on('message', (message) => ws.send(message)); }); - server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { rejectUnauthorized: false }); @@ -1800,11 +1864,11 @@ describe('WebSocket', function () { const agent = new CustomAgent(); agent.addRequest = (req) => { - assert.strictEqual(req._headers.host, `localhost:${port}`); + assert.strictEqual(req._headers.host, 'localhost:1337'); done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { agent }); + const ws = new WebSocket('ws://localhost:1337', { agent }); }); it('lacks default origin header', function (done) { @@ -1815,7 +1879,7 @@ describe('WebSocket', function () { done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { agent }); + const ws = new WebSocket('ws://localhost', { agent }); }); it('honors origin set in options (1/2)', function (done) { @@ -1826,7 +1890,7 @@ describe('WebSocket', function () { done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { origin: 'https://example.com:8000', agent }); @@ -1843,7 +1907,7 @@ describe('WebSocket', function () { done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { origin: 'https://example.com:8000', protocolVersion: 8, agent @@ -1877,14 +1941,14 @@ describe('WebSocket', function () { describe('permessage-deflate', function () { it('is enabled by default', (done) => { const server = http.createServer(); - const wss = new WebSocketServer({ server, perMessageDeflate: true }); + const wss = new WebSocket.Server({ server, perMessageDeflate: true }); server.on('upgrade', (req, socket, head) => { assert.ok(req.headers['sec-websocket-extensions'].includes('permessage-deflate')); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => { assert.ok(ws.extensions['permessage-deflate']); @@ -1896,14 +1960,14 @@ describe('WebSocket', function () { it('can be disabled', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server, perMessageDeflate: true }); + const wss = new WebSocket.Server({ server, perMessageDeflate: true }); server.on('upgrade', (req, socket, head) => { assert.strictEqual(req.headers['sec-websocket-extensions'], undefined); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { perMessageDeflate: false }); @@ -1916,7 +1980,7 @@ describe('WebSocket', function () { it('can send extension parameters', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server, perMessageDeflate: true }); + const wss = new WebSocket.Server({ server, perMessageDeflate: true }); server.on('upgrade', (req, socket, head) => { const extensions = req.headers['sec-websocket-extensions']; @@ -1928,8 +1992,8 @@ describe('WebSocket', function () { assert.ok(extensions.includes('client_max_window_bits')); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { perMessageDeflate: { serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -1946,10 +2010,11 @@ describe('WebSocket', function () { }); it('can send and receive text data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -1973,10 +2038,11 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2000,10 +2066,11 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2021,10 +2088,11 @@ describe('WebSocket', function () { }); it('consumes all received data when connection is closed abnormally', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; @@ -2046,7 +2114,8 @@ describe('WebSocket', function () { describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); @@ -2066,10 +2135,11 @@ describe('WebSocket', function () { describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2094,10 +2164,11 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2113,10 +2184,11 @@ describe('WebSocket', function () { }); it('can call during receiving data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 83519527e..5fca819bb 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -13,29 +13,30 @@ const fs = require('fs'); const WebSocket = require('..'); const Buffer = safeBuffer.Buffer; -const WebSocketServer = WebSocket.Server; -let port = 8000; describe('WebSocketServer', function () { describe('#ctor', function () { it('throws an error if no option object is passed', function () { - assert.throws(() => new WebSocketServer()); + assert.throws(() => new WebSocket.Server()); }); it('throws an error if no port or server is specified', function () { - assert.throws(() => new WebSocketServer({})); + assert.throws(() => new WebSocket.Server({})); }); it('emits an error if http server bind fails', function (done) { - const wss1 = new WebSocketServer({ port: 50003 }, () => { - const wss2 = new WebSocketServer({ port: 50003 }); + const wss1 = new WebSocket.Server({ port: 0 }, () => { + const wss2 = new WebSocket.Server({ + port: wss1._server.address().port + }); wss2.on('error', () => wss1.close(done)); }); }); it('starts a server on a given port', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const port = 1337; + const wss = new WebSocket.Server({ port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -43,7 +44,7 @@ describe('WebSocketServer', function () { }); it('binds the server on any IPv6 address when available', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss._server.address().address, '::'); wss.close(done); }); @@ -52,9 +53,9 @@ describe('WebSocketServer', function () { it('uses a precreated http server', function (done) { const server = http.createServer(); - server.listen(++port, () => { - const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const wss = new WebSocket.Server({ server }); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); wss.on('connection', (client) => { wss.close(); @@ -64,8 +65,8 @@ describe('WebSocketServer', function () { }); it('426s for non-Upgrade requests', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - http.get(`http://localhost:${port}`, (res) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + http.get(`http://localhost:${wss._server.address().port}`, (res) => { let body = ''; assert.strictEqual(res.statusCode, 426); @@ -88,7 +89,7 @@ describe('WebSocketServer', function () { const sockPath = `/tmp/ws.${crypto.randomBytes(16).toString('hex')}.socket`; server.listen(sockPath, () => { - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws, req) => { if (wss.clients.size === 1) { @@ -106,21 +107,26 @@ describe('WebSocketServer', function () { }); it('will not crash when it receives an unhandled opcode', function (done) { - const wss = new WebSocketServer({ port: ++port }); + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}/`); - wss.on('connection', (ws) => { - ws.onerror = () => wss.close(done); + ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); }); - const ws = new WebSocket(`ws://localhost:${port}/`); - - ws.onopen = () => ws._socket.write(Buffer.from([0x85, 0x00])); + wss.on('connection', (ws) => { + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 5'); + wss.close(done); + }); + }); }); }); describe('#close', function () { it('does not thrown when called twice', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(); wss.close(); wss.close(); @@ -130,15 +136,16 @@ describe('WebSocketServer', function () { }); it('closes all clients', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { if (++closes === 2) done(); }); }); let closes = 0; - wss.on('connection', (client) => { - client.on('close', () => { + wss.on('connection', (ws) => { + ws.on('close', () => { if (++closes === 2) done(); }); wss.close(); @@ -153,7 +160,7 @@ describe('WebSocketServer', function () { throw new Error('must not close pre-created server'); }; - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { wss.close(); @@ -161,22 +168,22 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); }); }); it('invokes the callback in noServer mode', function (done) { - const wss = new WebSocketServer({ noServer: true }); + const wss = new WebSocket.Server({ noServer: true }); wss.close(done); }); it('cleans event handlers on precreated server', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); - server.listen(++port, () => { + server.listen(0, () => { wss.close(() => { assert.strictEqual(server.listeners('listening').length, 0); assert.strictEqual(server.listeners('upgrade').length, 0); @@ -190,8 +197,9 @@ describe('WebSocketServer', function () { describe('#clients', function () { it('returns a list of connected clients', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.clients.size, 0); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -202,8 +210,9 @@ describe('WebSocketServer', function () { }); it('can be disabled', function (done) { - const wss = new WebSocketServer({ port: ++port, clientTracking: false }, () => { + const wss = new WebSocket.Server({ port: 0, clientTracking: false }, () => { assert.strictEqual(wss.clients, undefined); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close()); @@ -216,7 +225,8 @@ describe('WebSocketServer', function () { }); it('is updated when client terminates the connection', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.terminate()); @@ -231,7 +241,8 @@ describe('WebSocketServer', function () { }); it('is updated when client closes the connection', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close()); @@ -248,8 +259,8 @@ describe('WebSocketServer', function () { describe('#options', function () { it('exposes options passed to constructor', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - assert.strictEqual(wss.options.port, port); + const wss = new WebSocket.Server({ port: 0 }, () => { + assert.strictEqual(wss.options.port, 0); wss.close(done); }); }); @@ -258,7 +269,8 @@ describe('WebSocketServer', function () { describe('#maxpayload', function () { it('maxpayload is passed on to clients', function (done) { const maxPayload = 20480; - const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -270,7 +282,8 @@ describe('WebSocketServer', function () { it('maxpayload is passed on to hybi receivers', function (done) { const maxPayload = 20480; - const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -283,11 +296,14 @@ describe('WebSocketServer', function () { it('maxpayload is passed on to permessage-deflate', function (done) { const PerMessageDeflate = require('../lib/PerMessageDeflate'); const maxPayload = 20480; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port, - maxPayload - }, () => new WebSocket(`ws://localhost:${port}`)); + maxPayload, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + }); wss.on('connection', (client) => { assert.strictEqual( @@ -301,13 +317,13 @@ describe('WebSocketServer', function () { describe('#shouldHandle', function () { it('returns true when the path matches', function () { - const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); }); it('returns false when the path does not match', function () { - const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); }); @@ -317,14 +333,14 @@ describe('WebSocketServer', function () { it('can be used for a pre-existing server', function (done) { const server = http.createServer(); - server.listen(++port, () => { - const wss = new WebSocketServer({ noServer: true }); + server.listen(0, () => { + const wss = new WebSocket.Server({ noServer: true }); server.on('upgrade', (req, socket, head) => { wss.handleUpgrade(req, socket, head, (client) => client.send('hello')); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('message', (message) => { assert.strictEqual(message, 'hello'); @@ -335,66 +351,58 @@ describe('WebSocketServer', function () { }); it('closes the connection when path does not match', function (done) { - const wss = new WebSocketServer({ port: ++port, path: '/ws' }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); }); it('closes the connection when protocol version is Hixie-76', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 'Sec-WebSocket-Protocol': 'sample' - }, - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); }); }); describe('connection establishing', function () { it('does not accept connections with no sec-websocket-key', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -403,23 +411,20 @@ describe('WebSocketServer', function () { }); it('does not accept connections with no sec-websocket-version', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -428,24 +433,21 @@ describe('WebSocketServer', function () { }); it('does not accept connections with invalid sec-websocket-version', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 12 - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -454,28 +456,24 @@ describe('WebSocketServer', function () { }); it('client can be denied', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o) => false, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 8 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 401); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -484,23 +482,19 @@ describe('WebSocketServer', function () { }); it('client can be accepted', function (done) { - const wss = new WebSocketServer({ - port: ++port, + const wss = new WebSocket.Server({ + port: 0, verifyClient: (o) => true }, () => { - const req = http.request({ + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); @@ -508,38 +502,35 @@ describe('WebSocketServer', function () { it('verifyClient gets client origin', function (done) { let verifyClientCalled = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { assert.strictEqual(info.origin, 'http://foobarbaz.com'); verifyClientCalled = true; return false; }, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobarbaz.com' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.ok(verifyClientCalled); wss.close(done); }); - - req.end(); }); }); it('verifyClient gets original request', function (done) { let verifyClientCalled = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { assert.strictEqual( info.req.headers['sec-websocket-key'], @@ -548,26 +539,22 @@ describe('WebSocketServer', function () { verifyClientCalled = true; return false; }, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); req.on('response', (res) => { assert.ok(verifyClientCalled); wss.close(done); }); - - req.end(); }); }); @@ -578,7 +565,7 @@ describe('WebSocketServer', function () { }); let success = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { success = info.secure === true; return true; @@ -592,16 +579,18 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { - rejectUnauthorized: false - })); + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + rejectUnauthorized: false + }); + }); }); it('verifyClient has secure:false for non-ssl connections', function (done) { const server = http.createServer(); let success = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ server: server, verifyClient: (info) => { success = info.secure === false; @@ -615,34 +604,30 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); }); }); it('client can be denied asynchronously', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o, cb) => process.nextTick(cb, false), - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 8 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 401); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -651,28 +636,24 @@ describe('WebSocketServer', function () { }); it('client can be denied asynchronously with custom response code', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o, cb) => process.nextTick(cb, false, 404), - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 8 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 404); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -681,23 +662,19 @@ describe('WebSocketServer', function () { }); it('client can be accepted asynchronously', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o, cb) => process.nextTick(cb, true), - port: ++port + port: 0 }, () => { - const req = http.request({ + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); @@ -706,8 +683,8 @@ describe('WebSocketServer', function () { it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) { const server = http.createServer(); - server.listen(++port, () => { - const wss = new WebSocketServer({ + server.listen(0, () => { + const wss = new WebSocket.Server({ verifyClient: (o, cb) => setTimeout(cb, 100, true), server }); @@ -716,7 +693,7 @@ describe('WebSocketServer', function () { throw new Error('connection event emitted'); }); - const socket = net.connect({ host: 'localhost', port }, () => { + const socket = net.connect({ port: server.address().port }, () => { socket.write([ 'GET / HTTP/1.1', 'Host: localhost', @@ -739,17 +716,15 @@ describe('WebSocketServer', function () { }); it('handles messages passed along with the upgrade request (upgrade head)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); @@ -765,7 +740,8 @@ describe('WebSocketServer', function () { }); it('selects the first protocol by default', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => { @@ -781,7 +757,8 @@ describe('WebSocketServer', function () { assert.strictEqual(request.url, '/'); return protocols.pop(); }; - const wss = new WebSocketServer({ handleProtocols, port: ++port }, () => { + const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => { @@ -792,10 +769,11 @@ describe('WebSocketServer', function () { }); it('client detects invalid server protocol', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ handleProtocols: (ps) => 'prot3', - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => done(new Error('connection must not be established'))); @@ -804,10 +782,11 @@ describe('WebSocketServer', function () { }); it('client detects no server protocol', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ handleProtocols: (ps) => {}, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => done(new Error('connection must not be established'))); @@ -816,53 +795,47 @@ describe('WebSocketServer', function () { }); it('server detects unauthorized protocol handler', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ handleProtocols: (ps) => false, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 401); wss.close(done); }); - - req.end(); }); }); it('accept connections with sec-websocket-extensions', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' - }, - host: '127.0.0.1', - port + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); }); it('emits the `headers` event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); wss.on('headers', (headers, request) => { @@ -889,7 +862,8 @@ describe('WebSocketServer', function () { } data = data.join(''); - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('message', (message) => ws.send(message)); @@ -908,7 +882,8 @@ describe('WebSocketServer', function () { describe('client properties', function () { it('protocol is exposed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, 'hi'); }); @@ -919,8 +894,11 @@ describe('WebSocketServer', function () { }); it('protocolVersion is exposed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { + protocolVersion: 8 + }); }); wss.on('connection', (client) => { @@ -932,51 +910,47 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('accept connections with permessage-deflate extension', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { - const req = http.request({ + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits=8; server_max_window_bits=8; client_no_context_takeover; server_no_context_takeover' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Extensions': 'permessage-deflate; ' + + 'client_max_window_bits=8; server_max_window_bits=8; ' + + 'client_no_context_takeover; server_no_context_takeover' + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); }); - it('does not accept connections with not defined extension parameter', function (done) { - const wss = new WebSocketServer({ + it('does not accept connections with invalid extension parameters (name)', function (done) { + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; foo=15' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -984,29 +958,26 @@ describe('WebSocketServer', function () { }); }); - it('does not accept connections with invalid extension parameter', function (done) { - const wss = new WebSocketServer({ + it('does not accept connections with invalid extension parameters (value)', function (done) { + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; server_max_window_bits=foo' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { From 46b25476b13fdd24e1ab2c85d304b05bea75287e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 21 Nov 2017 07:42:24 +0100 Subject: [PATCH 382/489] [dist] 3.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c20eb812e..2fe112887 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.1", + "version": "3.3.2", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 355d24a2db2c32a8ed7b2b820ddf60e511f26d7c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 21 Nov 2017 09:29:32 +0100 Subject: [PATCH 383/489] [doc] Add direct links to the Autobahn test suite reports --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 88c480d78..e2cc3bd38 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation. -Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/ -for the full reports. +Passes the quite extensive Autobahn test suite: [server][server-report], +[client][client-report]. **Note**: This module does not work in the browser. The client in the docs is a reference to a back end with the role of a client in the WebSocket @@ -335,5 +335,7 @@ We're using the GitHub [releases][changelog] for changelog entries. [https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent [socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent +[client-report]: http://websockets.github.io/ws/autobahn/clients/ +[server-report]: http://websockets.github.io/ws/autobahn/servers/ [permessage-deflate]: https://tools.ietf.org/html/rfc7692 [changelog]: https://github.com/websockets/ws/releases From 5ccb86e4a880ed139bb5ef141753c10451c318b2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 24 Nov 2017 21:57:30 +0100 Subject: [PATCH 384/489] [test] Simplify a test --- test/WebSocket.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 0cad5ddac..fef3b2fb7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -2183,7 +2183,7 @@ describe('WebSocket', function () { }); }); - it('can call during receiving data', function (done) { + it('can be used while data is being processed', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, port: 0 @@ -2198,9 +2198,8 @@ describe('WebSocket', function () { client.send('hi'); } client.send('hi', () => { - ws.extensions['permessage-deflate']._inflate.on('close', () => { - wss.close(done); - }); + assert.strictEqual(ws._receiver._state, 5); + ws.on('close', () => wss.close(done)); ws.terminate(); }); }); From ac86a04bafc92207d5f57b532a8226fe33b3489a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 25 Nov 2017 16:44:02 +0100 Subject: [PATCH 385/489] [minor] Refactor `WebSocket#finalize()` Clear the close timer only when it could be set. --- lib/WebSocket.js | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 76e42c078..341dcd71b 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -55,9 +55,9 @@ class WebSocket extends EventEmitter { this._binaryType = constants.BINARY_TYPES[0]; this._finalize = this.finalize.bind(this); - this._finalizeCalled = false; this._closeMessage = null; this._closeTimer = null; + this._finalized = false; this._closeCode = null; this._receiver = null; this._sender = null; @@ -164,17 +164,14 @@ class WebSocket extends EventEmitter { /** * Clean up and release internal resources. * - * @param {(Boolean|Error)} Indicates whether or not an error occurred + * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ finalize (error) { - if (this._finalizeCalled) return; + if (this._finalized) return; this.readyState = WebSocket.CLOSING; - this._finalizeCalled = true; - - clearTimeout(this._closeTimer); - this._closeTimer = null; + this._finalized = true; // // If the connection was closed abnormally (with an error), or if the close @@ -183,24 +180,24 @@ class WebSocket extends EventEmitter { // if (error) this._closeCode = 1006; - if (this._socket) { - this._ultron.destroy(); - this._socket.on('error', function onerror () { - this.destroy(); - }); + if (!this._socket) return this.emitClose(); - if (!error) this._socket.end(); - else this._socket.destroy(); + clearTimeout(this._closeTimer); + this._closeTimer = null; - this._receiver.cleanup(() => this.emitClose()); + this._ultron.destroy(); + this._ultron = null; - this._receiver = null; - this._sender = null; - this._socket = null; - this._ultron = null; - } else { - this.emitClose(); - } + this._socket.on('error', constants.NOOP); + + if (!error) this._socket.end(); + else this._socket.destroy(); + + this._socket = null; + this._sender = null; + + this._receiver.cleanup(() => this.emitClose()); + this._receiver = null; } /** From 81cd85b85c1057659e7a15d8c83ab1fbcfbdef78 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 26 Nov 2017 07:47:40 +0100 Subject: [PATCH 386/489] chore(package): update eslint to version 4.12.0 (#1242) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fe112887..6720fabe8 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.11.0", + "eslint": "~4.12.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 4e7d48aafec4ad85958e4c5171db2fd2b0e85ea4 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 28 Nov 2017 12:02:11 +0100 Subject: [PATCH 387/489] [fix] Close cleanly only if a close frame has been sent and received --- lib/WebSocket.js | 69 +++++++++++++++++--------- test/WebSocket.test.js | 110 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 22 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 341dcd71b..72a62e56f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -55,6 +55,7 @@ class WebSocket extends EventEmitter { this._binaryType = constants.BINARY_TYPES[0]; this._finalize = this.finalize.bind(this); + this._closeFrameSent = false; this._closeMessage = null; this._closeTimer = null; this._finalized = false; @@ -149,7 +150,7 @@ class WebSocket extends EventEmitter { this._receiver.onclose = (code, reason) => { this._closeMessage = reason; this._closeCode = code; - this.close(code, reason); + if (!this._finalized) this.close(code, reason); }; this._receiver.onerror = (error, code) => { // close the connection when the receiver reports a HyBi error code @@ -173,14 +174,7 @@ class WebSocket extends EventEmitter { this.readyState = WebSocket.CLOSING; this._finalized = true; - // - // If the connection was closed abnormally (with an error), or if the close - // control frame was malformed or not received then the close code must be - // 1006. - // - if (error) this._closeCode = 1006; - - if (!this._socket) return this.emitClose(); + if (!this._socket) return this.emitClose(error); clearTimeout(this._closeTimer); this._closeTimer = null; @@ -196,17 +190,28 @@ class WebSocket extends EventEmitter { this._socket = null; this._sender = null; - this._receiver.cleanup(() => this.emitClose()); + this._receiver.cleanup(() => this.emitClose(error)); this._receiver = null; } /** * Emit the `close` event. * + * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ - emitClose () { + emitClose (error) { this.readyState = WebSocket.CLOSED; + + // + // If the connection was closed abnormally (with an error), or if the close + // control frame was not sent or received then the close code must be 1006. + // + if (error || !this._closeFrameSent) { + this._closeMessage = ''; + this._closeCode = 1006; + } + this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this.extensions[PerMessageDeflate.extensionName]) { @@ -216,7 +221,6 @@ class WebSocket extends EventEmitter { this.extensions = null; this.removeAllListeners(); - this.on('error', constants.NOOP); // Catch all errors after this. } /** @@ -244,6 +248,22 @@ class WebSocket extends EventEmitter { /** * Start a closing handshake. * + * +----------+ +-----------+ +----------+ + * + - - -|ws.close()|---->|close frame|-->|ws.close()|- - - - + * +----------+ +-----------+ +----------+ | + * | +----------+ +-----------+ | + * |ws.close()|<----|close frame|<--------+ | + * +----------+ +-----------+ | + * CLOSING | +---+ | CLOSING + * | +---|fin|<------------+ + * | | | +---+ | + * | | +---+ +-------------+ + * | +----------+-->|fin|----->|ws.finalize()| - - + + * | +---+ +-------------+ + * | +-------------+ | + * - - -|ws.finalize()|<--+ + * +-------------+ + * * @param {Number} code Status code explaining why the connection is closing * @param {String} data A string explaining why the connection is closing * @public @@ -260,23 +280,28 @@ class WebSocket extends EventEmitter { } if (this.readyState === WebSocket.CLOSING) { - if (this._closeCode && this._socket) this._socket.end(); + if (this._closeFrameSent && this._closeCode) this._socket.end(); return; } this.readyState = WebSocket.CLOSING; this._sender.close(code, data, !this._isServer, (err) => { - if (err) this.emit('error', err); + if (this._finalized) return; - if (this._socket) { - if (this._closeCode) this._socket.end(); - // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. - // - clearTimeout(this._closeTimer); - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + if (err) { + this.emit('error', err); + this.finalize(true); + return; } + + if (this._closeCode) this._socket.end(); + this._closeFrameSent = true; + + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); }); } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index fef3b2fb7..178dbfd15 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -7,6 +7,7 @@ const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); +const net = require('net'); const fs = require('fs'); const constants = require('../lib/Constants'); @@ -1146,6 +1147,45 @@ describe('WebSocket', function () { }); }); + it('emits an error if the close frame can not be sent', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const socket = net.createConnection(port, () => { + socket.write( + 'GET / HTTP/1.1\r\n' + + 'Host: localhost\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Key: qqFVFwaCnSMXiqfezY/AZQ==\r\n' + + 'Sec-WebSocket-Version: 13\r\n' + + '\r\n' + ); + socket.destroy(); + }); + + wss.on('connection', (ws) => { + // + // Remove our `'error'` listener from the socket to ensure that + // `WebSocket#finalize()` is not called before the `Sender#close()` + // callback. + // + const listeners = ws._socket.listeners('error'); + ws._socket.removeListener('error', listeners[listeners.length - 1]); + + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.ok(err.message.startsWith('write E')); + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + ws.close(); + }); + }); + }); + it('works when close reason is not specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; @@ -1221,6 +1261,7 @@ describe('WebSocket', function () { ws.send('bar'); ws.send('baz'); ws.close(); + ws.close(); }); }); @@ -1366,6 +1407,75 @@ describe('WebSocket', function () { }); }); + describe('abnormal closures', function () { + it('ignores close frame data if connection is forcibly closed', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + const emitClose = ws.emitClose; + const messages = []; + + ws.on('message', (message) => { + messages.push(message); + if (messages.length > 1) ws.terminate(); + }); + + ws.emitClose = (error) => { + assert.deepStrictEqual(messages, ['', '', '']); + assert.strictEqual(ws._closeMessage, 'foo'); + assert.strictEqual(ws._closeCode, 4000); + assert.strictEqual(error, true); + emitClose.call(ws, error); + }; + + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + const buf = Buffer.from('81008100810088050fa0666f6f', 'hex'); + ws._socket.write(buf); + }); + }); + + it('closes with 1006 if close frame is received but not sent', function (done) { + const wss = new WebSocket.Server({ + perMessageDeflate: { threshold: 0 }, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + const emitClose = ws.emitClose; + const messages = []; + + ws.on('message', (message) => messages.push(message)); + + ws.emitClose = (error) => { + assert.deepStrictEqual(messages, ['', '', '']); + assert.strictEqual(ws._closeFrameSent, false); + assert.strictEqual(ws._closeMessage, 'foo'); + assert.strictEqual(ws._closeCode, 4000); + assert.strictEqual(error, undefined); + emitClose.call(ws, error); + }; + + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + const buf = Buffer.from('c10100c10100c1010088050fa0666f6f', 'hex'); + ws._socket.end(buf); + }); + }); + }); + describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function () { const listener = () => {}; From 009d05c4384b5fe4762decaa6e37592d8885fafe Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 1 Dec 2017 10:12:03 +0100 Subject: [PATCH 388/489] [test] Mark skipped tests as pending --- test/WebSocket.test.js | 22 ++++++++++++++++------ test/WebSocketServer.test.js | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 178dbfd15..302ac493c 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -71,9 +71,14 @@ describe('WebSocket', function () { }); ws.on('error', (err) => { - // Skip this test on machines where 127.0.0.2 is disabled. - if (err.code === 'EADDRNOTAVAIL') err = undefined; - wss.close(() => done(err)); + wss.close(() => { + // + // Skip this test on machines where 127.0.0.2 is disabled. + // + if (err.code === 'EADDRNOTAVAIL') return this.skip(); + + done(err); + }); }); }); @@ -101,9 +106,14 @@ describe('WebSocket', function () { }); wss.on('error', (err) => { - // Skip this test on machines where IPv6 is not supported. - if (err.code === 'EADDRNOTAVAIL') err = undefined; - wss.close(() => done(err)); + wss.close(() => { + // + // Skip this test on machines where IPv6 is not supported. + // + if (err.code === 'EADDRNOTAVAIL') return this.skip(); + + done(err); + }); }); wss.on('connection', (ws, req) => { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 5fca819bb..c367d6117 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -83,7 +83,7 @@ describe('WebSocketServer', function () { // // Skip this test on Windows as it throws errors for obvious reasons. // - if (process.platform === 'win32') return done(); + if (process.platform === 'win32') return this.skip(); const server = http.createServer(); const sockPath = `/tmp/ws.${crypto.randomBytes(16).toString('hex')}.socket`; From a166af4c50ab7092c0bc64c42579ed2c34b49cc5 Mon Sep 17 00:00:00 2001 From: dcharbonnier Date: Fri, 1 Dec 2017 10:53:26 +0100 Subject: [PATCH 389/489] [test] Skip `family` test if localhost doesn't resolve to ::1 (#1246) --- test/WebSocket.test.js | 43 ++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 302ac493c..70df7c101 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -6,9 +6,11 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); +const dns = require('dns'); const http = require('http'); const net = require('net'); const fs = require('fs'); +const os = require('os'); const constants = require('../lib/Constants'); const WebSocket = require('..'); @@ -100,25 +102,38 @@ describe('WebSocket', function () { }); it('accepts the family option', function (done) { - const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; + const ifaces = os.networkInterfaces(); + const hasIPv6 = Object.keys(ifaces).some((name) => { + return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); }); - wss.on('error', (err) => { - wss.close(() => { - // - // Skip this test on machines where IPv6 is not supported. - // - if (err.code === 'EADDRNOTAVAIL') return this.skip(); + // + // Skip this test on machines where IPv6 is not supported. + // + if (!hasIPv6) return this.skip(); + + dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { + // + // Skip this test if localhost does not resolve to ::1. + // + if (err) { + return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' + ? this.skip() + : done(err); + } + + if (!addresses.some((val) => val.address === '::1')) return this.skip(); - done(err); + const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); - }); - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '::1'); - wss.close(done); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '::1'); + wss.close(done); + }); }); }); }); From b3bc7dbb881605e1717baf6d99220c1a6c3bb280 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 1 Dec 2017 12:02:45 +0100 Subject: [PATCH 390/489] [minor] Do not set `allowHalfOpen` to `false` Use the default value when the HTTP server is created internally. --- lib/WebSocketServer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 571078f80..8ebd6bf81 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -73,7 +73,6 @@ class WebSocketServer extends EventEmitter { }); res.end(body); }); - this._server.allowHalfOpen = false; this._server.listen(options.port, options.host, options.backlog, callback); } else if (options.server) { this._server = options.server; From f6e56855ab213510ab058b1171cffba3dce0f701 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 3 Dec 2017 13:40:27 +0100 Subject: [PATCH 391/489] chore(package): update utf-8-validate to version 4.0.0 (#1247) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6720fabe8..b87e5ec2b 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,6 @@ "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", "nyc": "~11.3.0", - "utf-8-validate": "~3.0.0" + "utf-8-validate": "~4.0.0" } } From ca76e58f4a969618319e1993e3072f406679cde9 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 9 Dec 2017 07:06:22 +0100 Subject: [PATCH 392/489] chore(package): update eslint to version 4.13.0 (#1249) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b87e5ec2b..76af690e0 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.12.0", + "eslint": "~4.13.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From ae903b1ab2d2f0a9e1b41e849317977e49dcf789 Mon Sep 17 00:00:00 2001 From: George Date: Sat, 9 Dec 2017 11:40:32 -0500 Subject: [PATCH 393/489] [doc] Fix rendering of history in SECURITY.md (#1250) --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index e8a5b70fb..8e063cca5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -30,5 +30,5 @@ all vulnerabilities to the [Node Security Project](https://nodesecurity.io/). ## History -04 Jan 2016: [Buffer vulnerablity](https://github.com/websockets/ws/releases/tag/1.0.1) -08 Nov 2017: [DoS vulnerablity](https://github.com/websockets/ws/releases/tag/3.3.1) +- 04 Jan 2016: [Buffer vulnerability](https://github.com/websockets/ws/releases/tag/1.0.1) +- 08 Nov 2017: [DoS vulnerability](https://github.com/websockets/ws/releases/tag/3.3.1) From beff62081d5591c4f1beb9a3993b24ea7473d792 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 10 Dec 2017 14:50:10 +0100 Subject: [PATCH 394/489] [fix] Use status code from close frame if received --- lib/EventTarget.js | 2 +- lib/WebSocket.js | 102 +++++++++----------- test/WebSocket.test.js | 174 +++++------------------------------ test/WebSocketServer.test.js | 46 +++++---- 4 files changed, 97 insertions(+), 227 deletions(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index 9d0461a18..c48137b91 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -55,7 +55,7 @@ class CloseEvent extends Event { constructor (code, reason, target) { super('close', target); - this.wasClean = code === undefined || code === 1000 || (code >= 3000 && code <= 4999); + this.wasClean = target._closeFrameReceived && target._closeFrameSent; this.reason = reason; this.code = code; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 72a62e56f..a8c7b99b1 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -55,11 +55,12 @@ class WebSocket extends EventEmitter { this._binaryType = constants.BINARY_TYPES[0]; this._finalize = this.finalize.bind(this); + this._closeFrameReceived = false; this._closeFrameSent = false; - this._closeMessage = null; + this._closeMessage = ''; this._closeTimer = null; this._finalized = false; - this._closeCode = null; + this._closeCode = 1006; this._receiver = null; this._sender = null; this._socket = null; @@ -126,21 +127,17 @@ class WebSocket extends EventEmitter { this._ultron = new Ultron(socket); this._socket = socket; - // socket cleanup handlers this._ultron.on('close', this._finalize); this._ultron.on('error', this._finalize); this._ultron.on('end', this._finalize); - // ensure that the head is added to the receiver if (head.length > 0) socket.unshift(head); - // subsequent packets are pushed to the receiver this._ultron.on('data', (data) => { this.bytesReceived += data.length; this._receiver.add(data); }); - // receiver event handlers this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { this.pong(data, !this._isServer, true); @@ -148,14 +145,22 @@ class WebSocket extends EventEmitter { }; this._receiver.onpong = (data) => this.emit('pong', data); this._receiver.onclose = (code, reason) => { + this._closeFrameReceived = true; this._closeMessage = reason; this._closeCode = code; if (!this._finalized) this.close(code, reason); }; this._receiver.onerror = (error, code) => { - // close the connection when the receiver reports a HyBi error code - this.close(code, ''); + this._closeMessage = ''; + this._closeCode = code; + + // + // Ensure that the error is emitted even if `WebSocket#finalize()` has + // already been called. + // + this.readyState = WebSocket.CLOSING; this.emit('error', error); + this.finalize(true); }; this.readyState = WebSocket.OPEN; @@ -174,7 +179,8 @@ class WebSocket extends EventEmitter { this.readyState = WebSocket.CLOSING; this._finalized = true; - if (!this._socket) return this.emitClose(error); + if (typeof error === 'object') this.emit('error', error); + if (!this._socket) return this.emitClose(); clearTimeout(this._closeTimer); this._closeTimer = null; @@ -190,29 +196,19 @@ class WebSocket extends EventEmitter { this._socket = null; this._sender = null; - this._receiver.cleanup(() => this.emitClose(error)); + this._receiver.cleanup(() => this.emitClose()); this._receiver = null; } /** * Emit the `close` event. * - * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ - emitClose (error) { + emitClose () { this.readyState = WebSocket.CLOSED; - // - // If the connection was closed abnormally (with an error), or if the close - // control frame was not sent or received then the close code must be 1006. - // - if (error || !this._closeFrameSent) { - this._closeMessage = ''; - this._closeCode = 1006; - } - - this.emit('close', this._closeCode || 1006, this._closeMessage || ''); + this.emit('close', this._closeCode, this._closeMessage); if (this.extensions[PerMessageDeflate.extensionName]) { this.extensions[PerMessageDeflate.extensionName].cleanup(); @@ -271,37 +267,35 @@ class WebSocket extends EventEmitter { close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - if (this._req && !this._req.aborted) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - this.finalize(true); - } + this._req.abort(); + this.finalize(new Error('closed before the connection is established')); return; } if (this.readyState === WebSocket.CLOSING) { - if (this._closeFrameSent && this._closeCode) this._socket.end(); + if (this._closeFrameSent && this._closeFrameReceived) this._socket.end(); return; } this.readyState = WebSocket.CLOSING; this._sender.close(code, data, !this._isServer, (err) => { - if (this._finalized) return; - - if (err) { - this.emit('error', err); - this.finalize(true); - return; - } + // + // This error is handled by the `'error'` listener on the socket. We only + // want to know if the close frame has been sent here. + // + if (err) return; - if (this._closeCode) this._socket.end(); this._closeFrameSent = true; - // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. - // - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + if (!this._finalized) { + if (this._closeFrameReceived) this._socket.end(); + + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } }); } @@ -391,11 +385,8 @@ class WebSocket extends EventEmitter { terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - if (this._req && !this._req.aborted) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - this.finalize(true); - } + this._req.abort(); + this.finalize(new Error('closed before the connection is established')); return; } @@ -645,8 +636,7 @@ function initAsClient (address, protocols, options) { 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.finalize(new Error('opening handshake has timed out')); }); } @@ -654,15 +644,13 @@ function initAsClient (address, protocols, options) { if (this._req.aborted) return; this._req = null; - this.emit('error', error); - this.finalize(true); + this.finalize(error); }); this._req.on('response', (res) => { if (!this.emit('unexpected-response', this._req, res)) { this._req.abort(); - this.emit('error', new Error(`unexpected server response (${res.statusCode})`)); - this.finalize(true); + this.finalize(new Error(`unexpected server response (${res.statusCode})`)); } }); @@ -683,8 +671,7 @@ function initAsClient (address, protocols, options) { if (res.headers['sec-websocket-accept'] !== digest) { socket.destroy(); - this.emit('error', new Error('invalid server key')); - return this.finalize(true); + return this.finalize(new Error('invalid server key')); } const serverProt = res.headers['sec-websocket-protocol']; @@ -701,8 +688,7 @@ function initAsClient (address, protocols, options) { if (protError) { socket.destroy(); - this.emit('error', new Error(protError)); - return this.finalize(true); + return this.finalize(new Error(protError)); } if (serverProt) this.protocol = serverProt; @@ -721,8 +707,8 @@ function initAsClient (address, protocols, options) { } } catch (err) { socket.destroy(); - this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); - return this.finalize(true); + this.finalize(new Error('invalid Sec-WebSocket-Extensions header')); + return; } } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 70df7c101..32a4245b8 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -6,8 +6,8 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); -const dns = require('dns'); const http = require('http'); +const dns = require('dns'); const net = require('net'); const fs = require('fs'); const os = require('os'); @@ -1189,14 +1189,6 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - // - // Remove our `'error'` listener from the socket to ensure that - // `WebSocket#finalize()` is not called before the `Sender#close()` - // callback. - // - const listeners = ws._socket.listeners('error'); - ws._socket.removeListener('error', listeners[listeners.length - 1]); - ws.on('error', (err) => { assert.ok(err instanceof Error); assert.ok(err.message.startsWith('write E')); @@ -1304,39 +1296,6 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1013)); }); - it('closes the connection when an error occurs', function (done) { - const server = http.createServer(); - const wss = new WebSocket.Server({ server }); - let closed = false; - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1006); - assert.strictEqual(reason, ''); - - closed = true; - }); - }); - }); - - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`); - - ws.on('open', () => ws._socket.write(Buffer.from([0xa1, 0x00]))); - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1002); - assert.strictEqual(reason, ''); - assert.ok(closed); - - server.close(done); - }); - }); - }); - it('does nothing if the connection is already CLOSED', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; @@ -1432,75 +1391,6 @@ describe('WebSocket', function () { }); }); - describe('abnormal closures', function () { - it('ignores close frame data if connection is forcibly closed', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - const emitClose = ws.emitClose; - const messages = []; - - ws.on('message', (message) => { - messages.push(message); - if (messages.length > 1) ws.terminate(); - }); - - ws.emitClose = (error) => { - assert.deepStrictEqual(messages, ['', '', '']); - assert.strictEqual(ws._closeMessage, 'foo'); - assert.strictEqual(ws._closeCode, 4000); - assert.strictEqual(error, true); - emitClose.call(ws, error); - }; - - ws.on('close', (code, message) => { - assert.strictEqual(message, ''); - assert.strictEqual(code, 1006); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - const buf = Buffer.from('81008100810088050fa0666f6f', 'hex'); - ws._socket.write(buf); - }); - }); - - it('closes with 1006 if close frame is received but not sent', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - const emitClose = ws.emitClose; - const messages = []; - - ws.on('message', (message) => messages.push(message)); - - ws.emitClose = (error) => { - assert.deepStrictEqual(messages, ['', '', '']); - assert.strictEqual(ws._closeFrameSent, false); - assert.strictEqual(ws._closeMessage, 'foo'); - assert.strictEqual(ws._closeCode, 4000); - assert.strictEqual(error, undefined); - emitClose.call(ws, error); - }; - - ws.on('close', (code, message) => { - assert.strictEqual(message, ''); - assert.strictEqual(code, 1006); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - const buf = Buffer.from('c10100c10100c1010088050fa0666f6f', 'hex'); - ws._socket.end(buf); - }); - }); - }); - describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function () { const listener = () => {}; @@ -1677,6 +1567,7 @@ describe('WebSocket', function () { ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); + assert.strictEqual(closeEvent.reason, ''); assert.strictEqual(closeEvent.code, 1000); wss.close(done); }); @@ -1685,43 +1576,15 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.close(1000)); }); - it('should assign "true" to wasClean when server closes with code 3000', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('close', (closeEvent) => { - assert.ok(closeEvent.wasClean); - wss.close(done); - }); - }); - - wss.on('connection', (client) => client.close(3000)); - }); - - it('should assign "true" to wasClean when server closes with code 4999', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('close', (closeEvent) => { - assert.ok(closeEvent.wasClean); - wss.close(done); - }); - }); - - wss.on('connection', (client) => client.close(4999)); - }); - it('should receive valid CloseEvent when server closes with code 1001', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { - assert.ok(!closeEvent.wasClean); - assert.strictEqual(closeEvent.code, 1001); + assert.ok(closeEvent.wasClean); assert.strictEqual(closeEvent.reason, 'some daft reason'); + assert.strictEqual(closeEvent.code, 1001); wss.close(done); }); }); @@ -2320,24 +2183,33 @@ describe('WebSocket', function () { it('can be used while data is being processed', function (done) { const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, + perMessageDeflate: true, port: 0 }, () => { const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: { threshold: 0 } - }); + const ws = new WebSocket(`ws://localhost:${port}`); + const messages = []; - wss.on('connection', (client) => { - for (let i = 0; i < 10; i++) { - client.send('hi'); - } - client.send('hi', () => { + ws.on('message', (message) => { + if (messages.push(message) > 1) return; + + process.nextTick(() => { assert.strictEqual(ws._receiver._state, 5); - ws.on('close', () => wss.close(done)); ws.terminate(); }); }); + + ws.on('close', (code, reason) => { + assert.deepStrictEqual(messages, ['', '', '', '']); + assert.strictEqual(code, 1006); + assert.strictEqual(reason, ''); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + const buf = Buffer.from('c10100c10100c10100c10100', 'hex'); + ws._socket.write(buf); }); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index c367d6117..fc8bbe266 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -105,23 +105,6 @@ describe('WebSocketServer', function () { ws.on('open', () => new WebSocket(`ws+unix://${sockPath}`)); }); }); - - it('will not crash when it receives an unhandled opcode', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}/`); - - ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); - }); - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 5'); - wss.close(done); - }); - }); - }); }); describe('#close', function () { @@ -878,6 +861,35 @@ describe('WebSocketServer', function () { client.send(data); }); }); + + it('does not crash when it receives an unhandled opcode', function (done) { + let closed = false; + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}/`); + + ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1006); + assert.strictEqual(reason, ''); + assert.ok(closed); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 5'); + + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1002); + assert.strictEqual(reason, ''); + closed = true; + }); + }); + }); + }); }); describe('client properties', function () { From 85919f2d11ad7fc0aa3bc20c9f35b4737fdc37e3 Mon Sep 17 00:00:00 2001 From: H1Gdev Date: Fri, 15 Dec 2017 15:04:57 +0900 Subject: [PATCH 395/489] [doc] Remove duplicate 'is' (#1252) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 2975958f1..e913d3e5c 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -27,7 +27,7 @@ provided or an error is thrown. If `verifyClient` is not set then the handshake is automatically accepted. If -it is is provided with a single argument then that is: +it is provided with a single argument then that is: - `info` {Object} - `origin` {String} The value in the Origin header indicated by the client. From 6a6ae04ef3514e2f8b1b0890649523d59dcf27f4 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 16 Dec 2017 18:54:35 +0100 Subject: [PATCH 396/489] [minor] Send the close status code only when necessary --- lib/Sender.js | 23 ++++++++++++++++++----- test/Sender.test.js | 2 +- test/WebSocket.test.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 53953d8d2..046a0e1d4 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -12,6 +12,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil'); const ErrorCodes = require('./ErrorCodes'); +const constants = require('./Constants'); const Buffer = safeBuffer.Buffer; @@ -112,14 +113,26 @@ class Sender { * @public */ close (code, data, mask, cb) { - if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { + var buf; + + if (code === undefined) { + code = 1000; + } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { throw new Error('first argument must be a valid error code number'); } - const buf = Buffer.allocUnsafe(2 + (data ? Buffer.byteLength(data) : 0)); - - buf.writeUInt16BE(code || 1000, 0, true); - if (buf.length > 2) buf.write(data, 2); + if (data === undefined || data === '') { + if (code === 1000) { + buf = constants.EMPTY_BUFFER; + } else { + buf = Buffer.allocUnsafe(2); + buf.writeUInt16BE(code, 0, true); + } + } else { + buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); + buf.writeUInt16BE(code, 0, true); + buf.write(data, 2); + } if (this._deflating) { this.enqueue([this.doClose, buf, mask, cb]); diff --git a/test/Sender.test.js b/test/Sender.test.js index 385011476..ac19d1ecd 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -267,7 +267,7 @@ describe('Sender', function () { sender.send('bar', { compress: true, fin: true }); sender.send('baz', { compress: true, fin: true }); - sender.close(1000, null, false, () => { + sender.close(1000, undefined, false, () => { assert.strictEqual(count, 4); done(); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 32a4245b8..f293491b9 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1203,6 +1203,34 @@ describe('WebSocket', function () { }); }); + it('sends the close status code only when necessary', function (done) { + let sent; + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { + ws._socket.once('data', (data) => { + sent = data; + }); + }); + }); + + wss.on('connection', (ws) => { + ws._socket.once('data', (received) => { + assert.ok(received.slice(0, 2).equals(Buffer.from([0x88, 0x80]))); + assert.ok(sent.equals(Buffer.from([0x88, 0x00]))); + + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1000); + assert.strictEqual(reason, ''); + wss.close(done); + }); + }); + ws.close(); + }); + }); + it('works when close reason is not specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; From 157f58a73250674eb57c505a522c7e5fe5fb3bee Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 17 Dec 2017 10:23:10 +0100 Subject: [PATCH 397/489] [dist] 3.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76af690e0..96d0cb3b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.2", + "version": "3.3.3", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 180b388a2ed83fddd667d88970a943d0f47d32ec Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 20 Dec 2017 07:47:27 +0100 Subject: [PATCH 398/489] chore(package): update nyc to version 11.4.1 (#1258) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96d0cb3b7..7b6ea7849 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", - "nyc": "~11.3.0", + "nyc": "~11.4.1", "utf-8-validate": "~4.0.0" } } From cc815a76d111816bbc752c6758a4e7150633339b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 22 Dec 2017 17:56:27 +0100 Subject: [PATCH 399/489] [fix] Make permessage-deflate parameters validation stricter --- lib/PerMessageDeflate.js | 151 ++++++++++++++++----------------- test/PerMessageDeflate.test.js | 32 ++++++- test/WebSocketServer.test.js | 29 +------ 3 files changed, 103 insertions(+), 109 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 6ed12a7b7..ccdb0f70c 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -118,15 +118,11 @@ class PerMessageDeflate { accept (paramsList) { paramsList = this.normalizeParams(paramsList); - var params; - if (this._isServer) { - params = this.acceptAsServer(paramsList); - } else { - params = this.acceptAsClient(paramsList); - } + this.params = this._isServer + ? this.acceptAsServer(paramsList) + : this.acceptAsClient(paramsList); - this.params = params; - return params; + return this.params; } /** @@ -161,52 +157,43 @@ class PerMessageDeflate { * @private */ acceptAsServer (paramsList) { - const accepted = {}; - const result = paramsList.some((params) => { + const opts = this._options; + const accepted = paramsList.find((params) => { if ( - (this._options.serverNoContextTakeover === false && + (opts.serverNoContextTakeover === false && params.server_no_context_takeover) || - (this._options.serverMaxWindowBits === false && - params.server_max_window_bits) || - (typeof this._options.serverMaxWindowBits === 'number' && - typeof params.server_max_window_bits === 'number' && - this._options.serverMaxWindowBits > params.server_max_window_bits) || - (typeof this._options.clientMaxWindowBits === 'number' && + (params.server_max_window_bits && + (opts.serverMaxWindowBits === false || + (typeof opts.serverMaxWindowBits === 'number' && + opts.serverMaxWindowBits > params.server_max_window_bits))) || + (typeof opts.clientMaxWindowBits === 'number' && !params.client_max_window_bits) ) { - return; + return false; } - if ( - this._options.serverNoContextTakeover || - params.server_no_context_takeover - ) { - accepted.server_no_context_takeover = true; - } - if ( - this._options.clientNoContextTakeover || - (this._options.clientNoContextTakeover !== false && - params.client_no_context_takeover) - ) { - accepted.client_no_context_takeover = true; - } - if (typeof this._options.serverMaxWindowBits === 'number') { - accepted.server_max_window_bits = this._options.serverMaxWindowBits; - } else if (typeof params.server_max_window_bits === 'number') { - accepted.server_max_window_bits = params.server_max_window_bits; - } - if (typeof this._options.clientMaxWindowBits === 'number') { - accepted.client_max_window_bits = this._options.clientMaxWindowBits; - } else if ( - this._options.clientMaxWindowBits !== false && - typeof params.client_max_window_bits === 'number' - ) { - accepted.client_max_window_bits = params.client_max_window_bits; - } return true; }); - if (!result) throw new Error("Doesn't support the offered configuration"); + if (!accepted) throw new Error("Doesn't support the offered configuration"); + + if (opts.serverNoContextTakeover) { + accepted.server_no_context_takeover = true; + } + if (opts.clientNoContextTakeover) { + accepted.client_no_context_takeover = true; + } + if (typeof opts.serverMaxWindowBits === 'number') { + accepted.server_max_window_bits = opts.serverMaxWindowBits; + } + if (typeof opts.clientMaxWindowBits === 'number') { + accepted.client_max_window_bits = opts.clientMaxWindowBits; + } else if ( + accepted.client_max_window_bits === true || + opts.clientMaxWindowBits === false + ) { + delete accepted.client_max_window_bits; + } return accepted; } @@ -228,12 +215,14 @@ class PerMessageDeflate { throw new Error('Invalid value for "client_no_context_takeover"'); } - if ( + if (!params.client_max_window_bits) { + if (typeof this._options.clientMaxWindowBits === 'number') { + params.client_max_window_bits = this._options.clientMaxWindowBits; + } + } else if ( + this._options.clientMaxWindowBits === false || (typeof this._options.clientMaxWindowBits === 'number' && - (!params.client_max_window_bits || - params.client_max_window_bits > this._options.clientMaxWindowBits)) || - (this._options.clientMaxWindowBits === false && - params.client_max_window_bits) + params.client_max_window_bits > this._options.clientMaxWindowBits) ) { throw new Error('Invalid value for "client_max_window_bits"'); } @@ -249,46 +238,52 @@ class PerMessageDeflate { * @private */ normalizeParams (paramsList) { - return paramsList.map((params) => { + paramsList.forEach((params) => { Object.keys(params).forEach((key) => { var value = params[key]; + if (value.length > 1) { throw new Error(`Multiple extension parameters for ${key}`); } value = value[0]; - switch (key) { - case 'server_no_context_takeover': - case 'client_no_context_takeover': - if (value !== true) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } - params[key] = true; - break; - case 'server_max_window_bits': - case 'client_max_window_bits': - if (typeof value === 'string') { - value = parseInt(value, 10); - if ( - Number.isNaN(value) || - value < zlib.Z_MIN_WINDOWBITS || - value > zlib.Z_MAX_WINDOWBITS - ) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } + if (key === 'client_max_window_bits') { + if (value !== true) { + value = +value; + if (!Number.isInteger(value) || value < 8 || value > 15) { + throw new Error( + `invalid extension parameter value for ${key} (${value})` + ); } - if (!this._isServer && value === true) { - throw new Error(`Missing extension parameter value for ${key}`); - } - params[key] = value; - break; - default: - throw new Error(`Not defined extension parameter (${key})`); + } else if (!this._isServer) { + throw new Error(`Missing extension parameter value for ${key}`); + } + } else if (key === 'server_max_window_bits') { + value = +value; + if (!Number.isInteger(value) || value < 8 || value > 15) { + throw new Error( + `invalid extension parameter value for ${key} (${value})` + ); + } + } else if ( + key === 'client_no_context_takeover' || + key === 'server_no_context_takeover' + ) { + if (value !== true) { + throw new Error( + `invalid extension parameter value for ${key} (${value})` + ); + } + } else { + throw new Error(`Not defined extension parameter (${key})`); } + + params[key] = value; }); - return params; }); + + return paramsList; } /** diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index bfe25acb7..0065c7bee 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -162,6 +162,16 @@ describe('PerMessageDeflate', function () { assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); + + it('uses the config value if client_max_window_bits is not specified', function () { + const perMessageDeflate = new PerMessageDeflate({ + clientMaxWindowBits: 10 + }); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), { + client_max_window_bits: 10 + }); + }); }); describe('validate parameters', function () { @@ -189,18 +199,34 @@ describe('PerMessageDeflate', function () { }); it('should throw an error if server_max_window_bits has an invalid value', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + const perMessageDeflate = new PerMessageDeflate({}, true); + let extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + + extensions = Extensions.parse('permessage-deflate; server_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + let extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + + extensions = Extensions.parse('permessage-deflate; client_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); + + it('throws an error if a parameter has an invalid name', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate;foo'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Not defined extension parameter \(foo\)$/ + ); + }); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index fc8bbe266..2bfa09cb0 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -943,34 +943,7 @@ describe('WebSocketServer', function () { wss.on('connection', (ws) => wss.close(done)); }); - it('does not accept connections with invalid extension parameters (name)', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; foo=15' - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 400); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); - }); - - it('does not accept connections with invalid extension parameters (value)', function (done) { + it('does not accept connections with invalid extension parameters', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: true, port: 0 From 5678618f191113c17169d5e0a0e920954f1c88fb Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 24 Dec 2017 07:30:31 +0100 Subject: [PATCH 400/489] chore(package): update eslint to version 4.14.0 (#1262) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b6ea7849..205d91589 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.13.0", + "eslint": "~4.14.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From f941a4046481f1aa182aba11bb958e4306fd92fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 29 Dec 2017 08:05:32 +0100 Subject: [PATCH 401/489] chore(package): update mocha to version 4.1.0 (#1265) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 205d91589..af23fd9e0 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~4.0.0", + "mocha": "~4.1.0", "nyc": "~11.4.1", "utf-8-validate": "~4.0.0" } From a31b1f6439936e6de8cc1f7775410455551a2792 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 20 Dec 2017 10:19:25 +0100 Subject: [PATCH 402/489] [fix] Use 1005 status code if close frame payload length is 0 Fixes #1257 --- doc/ws.md | 2 +- lib/Receiver.js | 2 +- lib/Sender.js | 14 ++++---------- lib/WebSocket.js | 6 +++++- test/Receiver.test.js | 4 ++-- test/WebSocket.test.js | 8 ++++---- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index e913d3e5c..b2962b2ff 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -308,7 +308,7 @@ not yet transmitted to the network. Received bytes count. -### websocket.close([code][, reason]) +### websocket.close([code[, reason]]) - `code` {Number} A numeric value indicating the status code explaining why the connection is being closed. diff --git a/lib/Receiver.js b/lib/Receiver.js index 91196706c..9e0d86474 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -402,7 +402,7 @@ class Receiver { controlMessage (data) { if (this._opcode === 0x08) { if (data.length === 0) { - this.onclose(1000, ''); + this.onclose(1005, ''); this._loop = false; this.cleanup(this._cleanupCallback); } else if (data.length === 1) { diff --git a/lib/Sender.js b/lib/Sender.js index 046a0e1d4..39e5a73f5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -116,18 +116,12 @@ class Sender { var buf; if (code === undefined) { - code = 1000; + buf = constants.EMPTY_BUFFER; } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { throw new Error('first argument must be a valid error code number'); - } - - if (data === undefined || data === '') { - if (code === 1000) { - buf = constants.EMPTY_BUFFER; - } else { - buf = Buffer.allocUnsafe(2); - buf.writeUInt16BE(code, 0, true); - } + } else if (data === undefined || data === '') { + buf = Buffer.allocUnsafe(2); + buf.writeUInt16BE(code, 0, true); } else { buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); buf.writeUInt16BE(code, 0, true); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a8c7b99b1..b115b1477 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -148,7 +148,11 @@ class WebSocket extends EventEmitter { this._closeFrameReceived = true; this._closeMessage = reason; this._closeCode = code; - if (!this._finalized) this.close(code, reason); + + if (this._finalized) return; + + if (code === 1005) this.close(); + else this.close(code, reason); }; this._receiver.onerror = (error, code) => { this._closeMessage = ''; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index bb75a8993..cbb61e6fe 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -26,7 +26,7 @@ describe('Receiver', function () { const p = new Receiver(); p.onclose = function (code, data) { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.strictEqual(data, ''); done(); }; @@ -836,7 +836,7 @@ describe('Receiver', function () { assert.strictEqual(p._bufferedBytes, textFrame.length + closeFrame.length); p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello', 'Hello', 1000, '']); + assert.deepStrictEqual(results, ['Hello', 'Hello', 1005, '']); assert.strictEqual(p.onmessage, null); done(); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index f293491b9..ca2d34a36 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1222,7 +1222,7 @@ describe('WebSocket', function () { assert.ok(sent.equals(Buffer.from([0x88, 0x00]))); ws.on('close', (code, reason) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.strictEqual(reason, ''); wss.close(done); }); @@ -1295,7 +1295,7 @@ describe('WebSocket', function () { ws.on('message', (message) => messages.push(message)); ws.on('close', (code) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); wss.close(done); }); @@ -1330,7 +1330,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.strictEqual(ws.readyState, WebSocket.CLOSED); ws.close(); wss.close(done); @@ -2180,7 +2180,7 @@ describe('WebSocket', function () { ws.on('message', (message) => { assert.strictEqual(message, 'hi'); ws.on('close', (code) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); wss.close(done); }); }); From 695c5ea988801ed75c121ad9bc76ed7abd70ccd1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 10:47:40 +0100 Subject: [PATCH 403/489] [major] Improve error messages and use specific error types --- lib/Extensions.js | 26 ++++--- lib/PerMessageDeflate.js | 42 ++++++----- lib/Receiver.js | 87 ++++++++++++++++++----- lib/Sender.js | 2 +- lib/WebSocket.js | 71 +++++++++++++------ lib/WebSocketServer.js | 4 +- test/Extensions.test.js | 12 ++-- test/PerMessageDeflate.test.js | 6 +- test/Receiver.test.js | 126 +++++++++++++++++++++++---------- test/WebSocket.test.js | 78 ++++++++++++++------ test/WebSocketServer.test.js | 7 +- 11 files changed, 322 insertions(+), 139 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 15fee1216..d59d39202 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -67,7 +67,9 @@ function parse (header) { } else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) { if (end === -1 && start !== -1) end = i; } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) { - if (start === -1) throw new Error(`unexpected character at index ${i}`); + if (start === -1) { + throw new SyntaxError(`Unexpected character at index ${i}`); + } if (end === -1) end = i; const name = header.slice(start, end); @@ -80,7 +82,7 @@ function parse (header) { start = end = -1; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } else if (paramName === undefined) { if (end === -1 && tokenChars[code] === 1) { @@ -88,7 +90,9 @@ function parse (header) { } else if (code === 0x20 || code === 0x09) { if (end === -1 && start !== -1) end = i; } else if (code === 0x3b || code === 0x2c) { - if (start === -1) throw new Error(`unexpected character at index ${i}`); + if (start === -1) { + throw new SyntaxError(`Unexpected character at index ${i}`); + } if (end === -1) end = i; push(params, header.slice(start, end), true); @@ -103,7 +107,7 @@ function parse (header) { paramName = header.slice(start, i); start = end = -1; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } else { // @@ -113,7 +117,7 @@ function parse (header) { // if (isEscaping) { if (tokenChars[code] !== 1) { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } if (start === -1) start = i; else if (!mustUnescape) mustUnescape = true; @@ -127,7 +131,7 @@ function parse (header) { } else if (code === 0x5c/* '\' */) { isEscaping = true; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { inQuotes = true; @@ -136,7 +140,9 @@ function parse (header) { } else if (start !== -1 && (code === 0x20 || code === 0x09)) { if (end === -1) end = i; } else if (code === 0x3b || code === 0x2c) { - if (start === -1) throw new Error(`unexpected character at index ${i}`); + if (start === -1) { + throw new SyntaxError(`Unexpected character at index ${i}`); + } if (end === -1) end = i; var value = header.slice(start, end); @@ -154,12 +160,14 @@ function parse (header) { paramName = undefined; start = end = -1; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } } - if (start === -1 || inQuotes) throw new Error('unexpected end of input'); + if (start === -1 || inQuotes) { + throw new SyntaxError('Unexpected end of input'); + } if (end === -1) end = i; const token = header.slice(start, end); diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index ccdb0f70c..b452b574b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -175,7 +175,9 @@ class PerMessageDeflate { return true; }); - if (!accepted) throw new Error("Doesn't support the offered configuration"); + if (!accepted) { + throw new Error('None of the extension offers can be accepted'); + } if (opts.serverNoContextTakeover) { accepted.server_no_context_takeover = true; @@ -212,7 +214,7 @@ class PerMessageDeflate { this._options.clientNoContextTakeover === false && params.client_no_context_takeover ) { - throw new Error('Invalid value for "client_no_context_takeover"'); + throw new Error('Unexpected parameter "client_no_context_takeover"'); } if (!params.client_max_window_bits) { @@ -224,7 +226,9 @@ class PerMessageDeflate { (typeof this._options.clientMaxWindowBits === 'number' && params.client_max_window_bits > this._options.clientMaxWindowBits) ) { - throw new Error('Invalid value for "client_max_window_bits"'); + throw new Error( + 'Unexpected or invalid parameter "client_max_window_bits"' + ); } return params; @@ -243,40 +247,44 @@ class PerMessageDeflate { var value = params[key]; if (value.length > 1) { - throw new Error(`Multiple extension parameters for ${key}`); + throw new Error(`Parameter "${key}" must have only a single value`); } value = value[0]; if (key === 'client_max_window_bits') { if (value !== true) { - value = +value; - if (!Number.isInteger(value) || value < 8 || value > 15) { - throw new Error( - `invalid extension parameter value for ${key} (${value})` + const num = +value; + if (!Number.isInteger(num) || num < 8 || num > 15) { + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` ); } + value = num; } else if (!this._isServer) { - throw new Error(`Missing extension parameter value for ${key}`); + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` + ); } } else if (key === 'server_max_window_bits') { - value = +value; - if (!Number.isInteger(value) || value < 8 || value > 15) { - throw new Error( - `invalid extension parameter value for ${key} (${value})` + const num = +value; + if (!Number.isInteger(num) || num < 8 || num > 15) { + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` ); } + value = num; } else if ( key === 'client_no_context_takeover' || key === 'server_no_context_takeover' ) { if (value !== true) { - throw new Error( - `invalid extension parameter value for ${key} (${value})` + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` ); } } else { - throw new Error(`Not defined extension parameter (${key})`); + throw new Error(`Unknown parameter "${key}"`); } params[key] = value; @@ -480,7 +488,7 @@ function inflateOnData (chunk) { return; } - this[kError] = new Error('max payload size exceeded'); + this[kError] = new RangeError('Max payload size exceeded'); this[kError].closeCode = 1009; this.removeListener('data', inflateOnData); this.reset(); diff --git a/lib/Receiver.js b/lib/Receiver.js index 9e0d86474..126ff9676 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -181,14 +181,20 @@ class Receiver { const buf = this.readBuffer(2); if ((buf[0] & 0x30) !== 0x00) { - this.error(new Error('RSV2 and RSV3 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV2 and RSV3 must be clear'), + 1002 + ); return; } const compressed = (buf[0] & 0x40) === 0x40; if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { - this.error(new Error('RSV1 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV1 must be clear'), + 1002 + ); return; } @@ -198,40 +204,68 @@ class Receiver { if (this._opcode === 0x00) { if (compressed) { - this.error(new Error('RSV1 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV1 must be clear'), + 1002 + ); return; } if (!this._fragmented) { - this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); + this.error( + new RangeError('Invalid WebSocket frame: invalid opcode 0'), + 1002 + ); return; } else { this._opcode = this._fragmented; } } else if (this._opcode === 0x01 || this._opcode === 0x02) { if (this._fragmented) { - this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid opcode ${this._opcode}` + ), + 1002 + ); return; } this._compressed = compressed; } else if (this._opcode > 0x07 && this._opcode < 0x0b) { if (!this._fin) { - this.error(new Error('FIN must be set'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: FIN must be set'), + 1002 + ); return; } if (compressed) { - this.error(new Error('RSV1 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV1 must be clear'), + 1002 + ); return; } if (this._payloadLength > 0x7d) { - this.error(new Error('invalid payload length'), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid payload length ` + + `${this._payloadLength}` + ), + 1002 + ); return; } } else { - this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid opcode ${this._opcode}` + ), + 1002 + ); return; } @@ -272,11 +306,16 @@ class Receiver { // if payload length is greater than this number. // if (num > Math.pow(2, 53 - 32) - 1) { - this.error(new Error('max payload size exceeded'), 1009); + this.error( + new RangeError( + 'Unsupported WebSocket frame: payload length > 2^53 - 1' + ), + 1009 + ); return; } - this._payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true); + this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); this.haveLength(); } @@ -382,7 +421,10 @@ class Receiver { const buf = toBuffer(fragments, messageLength); if (!isValidUTF8(buf)) { - this.error(new Error('invalid utf8 sequence'), 1007); + this.error( + new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), + 1007 + ); return; } @@ -406,19 +448,30 @@ class Receiver { this._loop = false; this.cleanup(this._cleanupCallback); } else if (data.length === 1) { - this.error(new Error('invalid payload length'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: invalid payload length 1'), + 1002 + ); } else { const code = data.readUInt16BE(0, true); if (!ErrorCodes.isValidErrorCode(code)) { - this.error(new Error(`invalid status code: ${code}`), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid status code ${code}` + ), + 1002 + ); return; } const buf = data.slice(2); if (!isValidUTF8(buf)) { - this.error(new Error('invalid utf8 sequence'), 1007); + this.error( + new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), + 1007 + ); return; } @@ -466,7 +519,7 @@ class Receiver { return false; } - this.error(new Error('max payload size exceeded'), 1009); + this.error(new RangeError('Max payload size exceeded'), 1009); return true; } @@ -489,7 +542,7 @@ class Receiver { return true; } - this.error(new Error('max payload size exceeded'), 1009); + this.error(new RangeError('Max payload size exceeded'), 1009); return false; } diff --git a/lib/Sender.js b/lib/Sender.js index 39e5a73f5..b01e4dba5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -118,7 +118,7 @@ class Sender { if (code === undefined) { buf = constants.EMPTY_BUFFER; } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { - throw new Error('first argument must be a valid error code number'); + throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); buf.writeUInt16BE(code, 0, true); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index b115b1477..4b7f044df 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -20,6 +20,7 @@ const constants = require('./Constants'); const Receiver = require('./Receiver'); const Sender = require('./Sender'); +const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const protocolVersions = [8, 13]; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. @@ -229,7 +230,12 @@ class WebSocket extends EventEmitter { * @public */ pause () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this.readyState !== WebSocket.OPEN) { + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); + } this._socket.pause(); } @@ -240,7 +246,12 @@ class WebSocket extends EventEmitter { * @public */ resume () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this.readyState !== WebSocket.OPEN) { + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); + } this._socket.resume(); } @@ -272,7 +283,9 @@ class WebSocket extends EventEmitter { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { this._req.abort(); - this.finalize(new Error('closed before the connection is established')); + this.finalize( + new Error('WebSocket was closed before the connection was established') + ); return; } @@ -314,7 +327,10 @@ class WebSocket extends EventEmitter { ping (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { if (failSilently) return; - throw new Error('not opened'); + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); } if (typeof data === 'number') data = data.toString(); @@ -333,7 +349,10 @@ class WebSocket extends EventEmitter { pong (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { if (failSilently) return; - throw new Error('not opened'); + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); } if (typeof data === 'number') data = data.toString(); @@ -360,8 +379,13 @@ class WebSocket extends EventEmitter { } if (this.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else throw new Error('not opened'); + const err = new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); + + if (cb) cb(err); + else throw err; return; } @@ -390,7 +414,9 @@ class WebSocket extends EventEmitter { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { this._req.abort(); - this.finalize(new Error('closed before the connection is established')); + this.finalize( + new Error('WebSocket was closed before the connection was established') + ); return; } @@ -398,10 +424,9 @@ class WebSocket extends EventEmitter { } } -WebSocket.CONNECTING = 0; -WebSocket.OPEN = 1; -WebSocket.CLOSING = 2; -WebSocket.CLOSED = 3; +readyStates.forEach((readyState, i) => { + WebSocket[readyStates[i]] = i; +}); // // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes. @@ -524,9 +549,9 @@ function initAsClient (address, protocols, options) { }, options); if (protocolVersions.indexOf(options.protocolVersion) === -1) { - throw new Error( - `unsupported protocol version: ${options.protocolVersion} ` + - `(supported versions: ${protocolVersions.join(', ')})` + throw new RangeError( + `Unsupported protocol version: ${options.protocolVersion} ` + + `(supported versions: ${protocolVersions.join(', ')})` ); } @@ -538,7 +563,7 @@ function initAsClient (address, protocols, options) { const isUnixSocket = serverUrl.protocol === 'ws+unix:'; if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) { - throw new Error('invalid url'); + throw new Error(`Invalid URL: ${address}`); } const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; @@ -640,7 +665,7 @@ function initAsClient (address, protocols, options) { if (options.handshakeTimeout) { this._req.setTimeout(options.handshakeTimeout, () => { this._req.abort(); - this.finalize(new Error('opening handshake has timed out')); + this.finalize(new Error('Opening handshake has timed out')); }); } @@ -654,7 +679,7 @@ function initAsClient (address, protocols, options) { this._req.on('response', (res) => { if (!this.emit('unexpected-response', this._req, res)) { this._req.abort(); - this.finalize(new Error(`unexpected server response (${res.statusCode})`)); + this.finalize(new Error(`Unexpected server response: ${res.statusCode}`)); } }); @@ -675,7 +700,7 @@ function initAsClient (address, protocols, options) { if (res.headers['sec-websocket-accept'] !== digest) { socket.destroy(); - return this.finalize(new Error('invalid server key')); + return this.finalize(new Error('Invalid Sec-WebSocket-Accept header')); } const serverProt = res.headers['sec-websocket-protocol']; @@ -683,11 +708,11 @@ function initAsClient (address, protocols, options) { var protError; if (!options.protocol && serverProt) { - protError = 'server sent a subprotocol even though none requested'; + protError = 'Server sent a subprotocol but none was requested'; } else if (options.protocol && !serverProt) { - protError = 'server sent no subprotocol even though requested'; + protError = 'Server sent no subprotocol'; } else if (serverProt && protList.indexOf(serverProt) === -1) { - protError = 'server responded with an invalid protocol'; + protError = 'Server sent an invalid subprotocol'; } if (protError) { @@ -711,7 +736,7 @@ function initAsClient (address, protocols, options) { } } catch (err) { socket.destroy(); - this.finalize(new Error('invalid Sec-WebSocket-Extensions header')); + this.finalize(new Error('Invalid Sec-WebSocket-Extensions header')); return; } } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 8ebd6bf81..b5efb1df7 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -60,7 +60,9 @@ class WebSocketServer extends EventEmitter { }, options); if (options.port == null && !options.server && !options.noServer) { - throw new TypeError('missing or invalid options'); + throw new TypeError( + 'One of the "port", "server", or "noServer" options must be specified' + ); } if (options.port != null) { diff --git a/test/Extensions.test.js b/test/Extensions.test.js index f08ab0852..8f23d05e2 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -49,11 +49,11 @@ describe('Extensions', function () { }); assert.throws( () => Extensions.parse('foo;bar="baz"qux'), - /^Error: unexpected character at index 13$/ + /^SyntaxError: Unexpected character at index 13$/ ); assert.throws( () => Extensions.parse('foo;bar="baz" qux'), - /^Error: unexpected character at index 14$/ + /^SyntaxError: Unexpected character at index 14$/ ); }); @@ -92,7 +92,7 @@ describe('Extensions', function () { ].forEach((element) => { assert.throws( () => Extensions.parse(element[0]), - new RegExp(`^Error: unexpected character at index ${element[1]}$`) + new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); }); @@ -106,7 +106,7 @@ describe('Extensions', function () { ].forEach((element) => { assert.throws( () => Extensions.parse(element[0]), - new RegExp(`^Error: unexpected character at index ${element[1]}$`) + new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); }); @@ -130,7 +130,7 @@ describe('Extensions', function () { ].forEach((element) => { assert.throws( () => Extensions.parse(element[0]), - new RegExp(`^Error: unexpected character at index ${element[1]}$`) + new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); }); @@ -147,7 +147,7 @@ describe('Extensions', function () { ].forEach((header) => { assert.throws( () => Extensions.parse(header), - /^Error: unexpected end of input$/ + /^SyntaxError: Unexpected end of input$/ ); }); }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 0065c7bee..5f74b9d3d 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -224,7 +224,7 @@ describe('PerMessageDeflate', function () { assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), - /^Error: Not defined extension parameter \(foo\)$/ + /^Error: Unknown parameter "foo"$/ ); }); }); @@ -429,8 +429,8 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(data, true, (err) => errors.push(err)); perMessageDeflate._inflate.flush(() => { assert.strictEqual(errors.length, 1); - assert.ok(errors[0] instanceof Error); - assert.strictEqual(errors[0].message, 'max payload size exceeded'); + assert.ok(errors[0] instanceof RangeError); + assert.strictEqual(errors[0].message, 'Max payload size exceeded'); done(); }); }); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index cbb61e6fe..b6affbd00 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -447,8 +447,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV1 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -463,8 +466,11 @@ describe('Receiver', function () { const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV1 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -476,8 +482,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -489,8 +498,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -502,8 +514,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 0'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 0' + ); assert.strictEqual(code, 1002); done(); }; @@ -515,8 +530,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 1'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 1' + ); assert.strictEqual(code, 1002); done(); }; @@ -529,8 +547,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 2'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 2' + ); assert.strictEqual(code, 1002); done(); }; @@ -543,8 +564,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'FIN must be set'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: FIN must be set' + ); assert.strictEqual(code, 1002); done(); }; @@ -559,8 +583,11 @@ describe('Receiver', function () { const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV1 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -572,8 +599,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'FIN must be set'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: FIN must be set' + ); assert.strictEqual(code, 1002); done(); }; @@ -585,8 +615,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid payload length'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid payload length 126' + ); assert.strictEqual(code, 1002); done(); }; @@ -598,8 +631,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Unsupported WebSocket frame: payload length > 2^53 - 1' + ); assert.strictEqual(code, 1009); done(); }; @@ -616,7 +652,10 @@ describe('Receiver', function () { p.onerror = function (err, code) { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid UTF-8 sequence' + ); assert.strictEqual(code, 1007); done(); }; @@ -628,8 +667,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid payload length'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid payload length 1' + ); assert.strictEqual(code, 1002); done(); }; @@ -641,8 +683,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid status code: 0'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid status code 0' + ); assert.strictEqual(code, 1002); done(); }; @@ -655,7 +700,10 @@ describe('Receiver', function () { p.onerror = function (err, code) { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid UTF-8 sequence' + ); assert.strictEqual(code, 1007); done(); }; @@ -678,8 +726,8 @@ describe('Receiver', function () { const frame = Buffer.concat(list); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -702,8 +750,8 @@ describe('Receiver', function () { const frame = Buffer.concat(list); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -719,8 +767,8 @@ describe('Receiver', function () { const buf = Buffer.from('A'.repeat(50)); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -741,8 +789,8 @@ describe('Receiver', function () { const buf = Buffer.from('A'.repeat(15)); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -871,7 +919,7 @@ describe('Receiver', function () { assert.deepStrictEqual(results, [ 'Hello', 'Hello', - 'RSV2 and RSV3 must be clear', + 'Invalid WebSocket frame: RSV2 and RSV3 must be clear', 1002 ]); assert.strictEqual(p.onmessage, null); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index ca2d34a36..aa4fced44 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -26,7 +26,7 @@ describe('WebSocket', function () { it('throws an error when using an invalid url', function () { assert.throws( () => new WebSocket('echo.websocket.org'), - /^Error: invalid url$/ + /^Error: Invalid URL: echo\.websocket\.org$/ ); }); }); @@ -61,7 +61,7 @@ describe('WebSocket', function () { assert.throws( () => new WebSocket('ws://localhost', options), - /^Error: unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ + /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ ); }); @@ -403,7 +403,7 @@ describe('WebSocket', function () { ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid server key'); + assert.strictEqual(err.message, 'Invalid Sec-WebSocket-Accept header'); done(); }); }); @@ -448,7 +448,7 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'unexpected server response (401)'); + assert.strictEqual(err.message, 'Unexpected server response: 401'); done(); }); }); @@ -520,7 +520,7 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'opening handshake has timed out'); + assert.strictEqual(err.message, 'Opening handshake has timed out'); done(); }); }); @@ -546,7 +546,7 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid Sec-WebSocket-Extensions header'); + assert.strictEqual(err.message, 'Invalid Sec-WebSocket-Extensions header'); ws.on('close', () => done()); }); }); @@ -574,7 +574,7 @@ describe('WebSocket', function () { assert.ok(err instanceof Error); assert.strictEqual( err.message, - 'server sent a subprotocol even though none requested' + 'Server sent a subprotocol but none was requested' ); ws.on('close', () => done()); }); @@ -605,13 +605,19 @@ describe('WebSocket', function () { it('throws an error when `readyState` is not `OPEN` (pause)', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - assert.throws(() => ws.pause(), /^Error: not opened$/); + assert.throws( + () => ws.pause(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('throws an error when `readyState` is not `OPEN` (resume)', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - assert.throws(() => ws.resume(), /^Error: not opened$/); + assert.throws( + () => ws.resume(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('pauses the underlying stream', function (done) { @@ -662,7 +668,10 @@ describe('WebSocket', function () { agent: new CustomAgent() }); - assert.throws(() => ws.ping(), /^Error: not opened$/); + assert.throws( + () => ws.ping(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('before connect can silently fail', function () { @@ -729,7 +738,10 @@ describe('WebSocket', function () { agent: new CustomAgent() }); - assert.throws(() => ws.pong(), /^Error: not opened$/); + assert.throws( + () => ws.pong(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('before connect can silently fail', function () { @@ -958,7 +970,10 @@ describe('WebSocket', function () { agent: new CustomAgent() }); - assert.throws(() => ws.send('hi'), /^Error: not opened$/); + assert.throws( + () => ws.send('hi'), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('before connect should pass error through callback, if present', function () { @@ -968,7 +983,10 @@ describe('WebSocket', function () { ws.send('hi', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'not opened'); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 0 (CONNECTING)' + ); }); }); @@ -1088,7 +1106,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.close(1001); @@ -1106,7 +1127,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); setTimeout(() => ws.close(1001), 150); @@ -1133,7 +1157,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.on('headers', () => ws.close()); @@ -1148,7 +1175,7 @@ describe('WebSocket', function () { ws.on('open', () => { assert.throws( () => ws.close('error'), - /^Error: first argument must be a valid error code number$/ + /^TypeError: First argument must be a valid error code number$/ ); wss.close(done); @@ -1164,7 +1191,7 @@ describe('WebSocket', function () { ws.on('open', () => { assert.throws( () => ws.close(1004), - /^Error: first argument must be a valid error code number$/ + /^TypeError: First argument must be a valid error code number$/ ); wss.close(done); @@ -1350,7 +1377,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.terminate(); @@ -1368,7 +1398,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); setTimeout(() => ws.terminate(), 150); @@ -1395,7 +1428,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.on('headers', () => ws.terminate()); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 2bfa09cb0..8e97c9bb0 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -879,8 +879,11 @@ describe('WebSocketServer', function () { wss.on('connection', (ws) => { ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 5'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 5' + ); ws.on('close', (code, reason) => { assert.strictEqual(code, 1002); From 63ce954d8fa1446cbeea555ce18859fc2c4e4cdc Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 14:48:35 +0100 Subject: [PATCH 404/489] [major] Remove ErrorCodes module Move the `isValidErrorCode` function to the Validation module and rename it to `isValidStatusCode`. --- lib/ErrorCodes.js | 28 ---------------------------- lib/Receiver.js | 9 ++++----- lib/Sender.js | 4 ++-- lib/Validation.js | 22 ++++++++++++++++++++-- test/Validation.test.js | 12 ++++++------ 5 files changed, 32 insertions(+), 43 deletions(-) delete mode 100644 lib/ErrorCodes.js diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js deleted file mode 100644 index f51557162..000000000 --- a/lib/ErrorCodes.js +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -module.exports = { - isValidErrorCode: function (code) { - return (code >= 1000 && code <= 1013 && code !== 1004 && code !== 1005 && code !== 1006) || - (code >= 3000 && code <= 4999); - }, - 1000: 'normal', - 1001: 'going away', - 1002: 'protocol error', - 1003: 'unsupported data', - 1004: 'reserved', - 1005: 'reserved for extensions', - 1006: 'reserved for extensions', - 1007: 'inconsistent or invalid data', - 1008: 'policy violation', - 1009: 'message too big', - 1010: 'extension handshake missing', - 1011: 'an unexpected condition prevented the request from being fulfilled', - 1012: 'service restart', - 1013: 'try again later' -}; diff --git a/lib/Receiver.js b/lib/Receiver.js index 126ff9676..546d40cf0 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -9,9 +9,8 @@ const safeBuffer = require('safe-buffer'); const PerMessageDeflate = require('./PerMessageDeflate'); -const isValidUTF8 = require('./Validation'); +const validation = require('./Validation'); const bufferUtil = require('./BufferUtil'); -const ErrorCodes = require('./ErrorCodes'); const constants = require('./Constants'); const Buffer = safeBuffer.Buffer; @@ -420,7 +419,7 @@ class Receiver { } else { const buf = toBuffer(fragments, messageLength); - if (!isValidUTF8(buf)) { + if (!validation.isValidUTF8(buf)) { this.error( new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), 1007 @@ -455,7 +454,7 @@ class Receiver { } else { const code = data.readUInt16BE(0, true); - if (!ErrorCodes.isValidErrorCode(code)) { + if (!validation.isValidStatusCode(code)) { this.error( new RangeError( `Invalid WebSocket frame: invalid status code ${code}` @@ -467,7 +466,7 @@ class Receiver { const buf = data.slice(2); - if (!isValidUTF8(buf)) { + if (!validation.isValidUTF8(buf)) { this.error( new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), 1007 diff --git a/lib/Sender.js b/lib/Sender.js index b01e4dba5..3c19f046f 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -11,7 +11,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil'); -const ErrorCodes = require('./ErrorCodes'); +const validation = require('./Validation'); const constants = require('./Constants'); const Buffer = safeBuffer.Buffer; @@ -117,7 +117,7 @@ class Sender { if (code === undefined) { buf = constants.EMPTY_BUFFER; - } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { + } else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) { throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); diff --git a/lib/Validation.js b/lib/Validation.js index 35c7e4f2a..a8ac2e634 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -9,9 +9,27 @@ try { const isValidUTF8 = require('utf-8-validate'); - module.exports = typeof isValidUTF8 === 'object' + exports.isValidUTF8 = typeof isValidUTF8 === 'object' ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 : isValidUTF8; } catch (e) /* istanbul ignore next */ { - module.exports = () => true; + exports.isValidUTF8 = () => true; } + +/** + * Checks if a status code is allowed in a close frame. + * + * @param {Number} code The status code + * @return {Boolean} `true` if the status code is valid, else `false` + * @public + */ +exports.isValidStatusCode = (code) => { + return ( + (code >= 1000 && + code <= 1013 && + code !== 1004 && + code !== 1005 && + code !== 1006) || + (code >= 3000 && code <= 4999) + ); +}; diff --git a/test/Validation.test.js b/test/Validation.test.js index 88dc37aa5..b2f38d3a1 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -3,7 +3,7 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const isValidUTF8 = require('../lib/Validation'); +const validation = require('../lib/Validation'); const Buffer = safeBuffer.Buffer; @@ -28,7 +28,7 @@ describe('Validation', function () { 'vulputate quis. Morbi ut pulvinar augue.' ); - assert.ok(isValidUTF8(validBuffer)); + assert.ok(validation.isValidUTF8(validBuffer)); }); it('should return false for an erroneous string', function () { @@ -38,16 +38,16 @@ describe('Validation', function () { 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 ]); - assert.ok(!isValidUTF8(invalidBuffer)); + assert.ok(!validation.isValidUTF8(invalidBuffer)); }); it('should return true for valid cases from the autobahn test suite', function () { - assert.ok(isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); - assert.ok(isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); + assert.ok(validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); + assert.ok(validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); }); it('should return false for erroneous autobahn strings', function () { - assert.ok(!isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); + assert.ok(!validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); }); }); }); From 3936f3a8a8b3e37e8d43ebcb9558d226d44fb964 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 15:05:13 +0100 Subject: [PATCH 405/489] [major] Rename modules files --- index.js | 8 +-- lib/{BufferUtil.js => buffer-util.js} | 0 lib/{Constants.js => constants.js} | 0 lib/{EventTarget.js => event-target.js} | 0 lib/{Extensions.js => extension.js} | 0 ...essageDeflate.js => permessage-deflate.js} | 2 +- lib/{Receiver.js => receiver.js} | 8 +-- lib/{Sender.js => sender.js} | 8 +-- lib/{Validation.js => validation.js} | 0 ...WebSocketServer.js => websocket-server.js} | 12 ++--- lib/{WebSocket.js => websocket.js} | 20 +++---- .../{Extensions.test.js => extension.test.js} | 44 ++++++++-------- ...ate.test.js => permessage-deflate.test.js} | 52 +++++++++---------- test/{Receiver.test.js => receiver.test.js} | 6 +-- test/{Sender.test.js => sender.test.js} | 4 +- ...{Validation.test.js => validation.test.js} | 4 +- ...erver.test.js => websocket-server.test.js} | 3 +- ...ntegration.js => websocket.integration.js} | 0 test/{WebSocket.test.js => websocket.test.js} | 2 +- 19 files changed, 86 insertions(+), 87 deletions(-) rename lib/{BufferUtil.js => buffer-util.js} (100%) rename lib/{Constants.js => constants.js} (100%) rename lib/{EventTarget.js => event-target.js} (100%) rename lib/{Extensions.js => extension.js} (100%) rename lib/{PerMessageDeflate.js => permessage-deflate.js} (99%) rename lib/{Receiver.js => receiver.js} (98%) rename lib/{Sender.js => sender.js} (98%) rename lib/{Validation.js => validation.js} (100%) rename lib/{WebSocketServer.js => websocket-server.js} (97%) rename lib/{WebSocket.js => websocket.js} (97%) rename test/{Extensions.test.js => extension.test.js} (75%) rename test/{PerMessageDeflate.test.js => permessage-deflate.test.js} (88%) rename test/{Receiver.test.js => receiver.test.js} (99%) rename test/{Sender.test.js => sender.test.js} (98%) rename test/{Validation.test.js => validation.test.js} (96%) rename test/{WebSocketServer.test.js => websocket-server.test.js} (99%) rename test/{WebSocket.integration.js => websocket.integration.js} (100%) rename test/{WebSocket.test.js => websocket.test.js} (99%) diff --git a/index.js b/index.js index 489e16942..4a7715f4a 100644 --- a/index.js +++ b/index.js @@ -6,10 +6,10 @@ 'use strict'; -const WebSocket = require('./lib/WebSocket'); +const WebSocket = require('./lib/websocket'); -WebSocket.Server = require('./lib/WebSocketServer'); -WebSocket.Receiver = require('./lib/Receiver'); -WebSocket.Sender = require('./lib/Sender'); +WebSocket.Server = require('./lib/websocket-server'); +WebSocket.Receiver = require('./lib/receiver'); +WebSocket.Sender = require('./lib/sender'); module.exports = WebSocket; diff --git a/lib/BufferUtil.js b/lib/buffer-util.js similarity index 100% rename from lib/BufferUtil.js rename to lib/buffer-util.js diff --git a/lib/Constants.js b/lib/constants.js similarity index 100% rename from lib/Constants.js rename to lib/constants.js diff --git a/lib/EventTarget.js b/lib/event-target.js similarity index 100% rename from lib/EventTarget.js rename to lib/event-target.js diff --git a/lib/Extensions.js b/lib/extension.js similarity index 100% rename from lib/Extensions.js rename to lib/extension.js diff --git a/lib/PerMessageDeflate.js b/lib/permessage-deflate.js similarity index 99% rename from lib/PerMessageDeflate.js rename to lib/permessage-deflate.js index b452b574b..5db327f69 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/permessage-deflate.js @@ -4,7 +4,7 @@ const safeBuffer = require('safe-buffer'); const Limiter = require('async-limiter'); const zlib = require('zlib'); -const bufferUtil = require('./BufferUtil'); +const bufferUtil = require('./buffer-util'); const Buffer = safeBuffer.Buffer; diff --git a/lib/Receiver.js b/lib/receiver.js similarity index 98% rename from lib/Receiver.js rename to lib/receiver.js index 546d40cf0..cc761f40d 100644 --- a/lib/Receiver.js +++ b/lib/receiver.js @@ -8,10 +8,10 @@ const safeBuffer = require('safe-buffer'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const validation = require('./Validation'); -const bufferUtil = require('./BufferUtil'); -const constants = require('./Constants'); +const PerMessageDeflate = require('./permessage-deflate'); +const bufferUtil = require('./buffer-util'); +const validation = require('./validation'); +const constants = require('./constants'); const Buffer = safeBuffer.Buffer; diff --git a/lib/Sender.js b/lib/sender.js similarity index 98% rename from lib/Sender.js rename to lib/sender.js index 3c19f046f..4526d2204 100644 --- a/lib/Sender.js +++ b/lib/sender.js @@ -9,10 +9,10 @@ const safeBuffer = require('safe-buffer'); const crypto = require('crypto'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const bufferUtil = require('./BufferUtil'); -const validation = require('./Validation'); -const constants = require('./Constants'); +const PerMessageDeflate = require('./permessage-deflate'); +const bufferUtil = require('./buffer-util'); +const validation = require('./validation'); +const constants = require('./constants'); const Buffer = safeBuffer.Buffer; diff --git a/lib/Validation.js b/lib/validation.js similarity index 100% rename from lib/Validation.js rename to lib/validation.js diff --git a/lib/WebSocketServer.js b/lib/websocket-server.js similarity index 97% rename from lib/WebSocketServer.js rename to lib/websocket-server.js index b5efb1df7..3ff6690e6 100644 --- a/lib/WebSocketServer.js +++ b/lib/websocket-server.js @@ -13,10 +13,10 @@ const Ultron = require('ultron'); const http = require('http'); const url = require('url'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const Extensions = require('./Extensions'); -const constants = require('./Constants'); -const WebSocket = require('./WebSocket'); +const PerMessageDeflate = require('./permessage-deflate'); +const extension = require('./extension'); +const constants = require('./constants'); +const WebSocket = require('./websocket'); const Buffer = safeBuffer.Buffer; @@ -171,7 +171,7 @@ class WebSocketServer extends EventEmitter { ); try { - const offers = Extensions.parse( + const offers = extension.parse( req.headers['sec-websocket-extensions'] ); @@ -261,7 +261,7 @@ class WebSocketServer extends EventEmitter { if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); if (extensions[PerMessageDeflate.extensionName]) { const params = extensions[PerMessageDeflate.extensionName].params; - const value = Extensions.format({ + const value = extension.format({ [PerMessageDeflate.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); diff --git a/lib/WebSocket.js b/lib/websocket.js similarity index 97% rename from lib/WebSocket.js rename to lib/websocket.js index 4b7f044df..31f4ba7a6 100644 --- a/lib/WebSocket.js +++ b/lib/websocket.js @@ -13,12 +13,12 @@ const https = require('https'); const http = require('http'); const url = require('url'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const EventTarget = require('./EventTarget'); -const Extensions = require('./Extensions'); -const constants = require('./Constants'); -const Receiver = require('./Receiver'); -const Sender = require('./Sender'); +const PerMessageDeflate = require('./permessage-deflate'); +const EventTarget = require('./event-target'); +const extension = require('./extension'); +const constants = require('./constants'); +const Receiver = require('./receiver'); +const Sender = require('./sender'); const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const protocolVersions = [8, 13]; @@ -589,7 +589,7 @@ function initAsClient (address, protocols, options) { options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false ); - requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format({ + requestOptions.headers['Sec-WebSocket-Extensions'] = extension.format({ [PerMessageDeflate.extensionName]: perMessageDeflate.offer() }); } @@ -724,13 +724,13 @@ function initAsClient (address, protocols, options) { if (perMessageDeflate) { try { - const serverExtensions = Extensions.parse( + const extensions = extension.parse( res.headers['sec-websocket-extensions'] ); - if (serverExtensions[PerMessageDeflate.extensionName]) { + if (extensions[PerMessageDeflate.extensionName]) { perMessageDeflate.accept( - serverExtensions[PerMessageDeflate.extensionName] + extensions[PerMessageDeflate.extensionName] ); this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } diff --git a/test/Extensions.test.js b/test/extension.test.js similarity index 75% rename from test/Extensions.test.js rename to test/extension.test.js index 8f23d05e2..15d1787a6 100644 --- a/test/Extensions.test.js +++ b/test/extension.test.js @@ -2,23 +2,23 @@ const assert = require('assert'); -const Extensions = require('../lib/Extensions'); +const extension = require('../lib/extension'); -describe('Extensions', function () { +describe('extension', function () { describe('parse', function () { it('returns an empty object if the argument is undefined', function () { - assert.deepStrictEqual(Extensions.parse(), {}); - assert.deepStrictEqual(Extensions.parse(''), {}); + assert.deepStrictEqual(extension.parse(), {}); + assert.deepStrictEqual(extension.parse(''), {}); }); it('parses a single extension', function () { - const extensions = Extensions.parse('foo'); + const extensions = extension.parse('foo'); assert.deepStrictEqual(extensions, { foo: [{}] }); }); it('parses params', function () { - const extensions = Extensions.parse('foo;bar;baz=1;bar=2'); + const extensions = extension.parse('foo;bar;baz=1;bar=2'); assert.deepStrictEqual(extensions, { foo: [{ bar: [true, '2'], baz: ['1'] }] @@ -26,7 +26,7 @@ describe('Extensions', function () { }); it('parses multiple extensions', function () { - const extensions = Extensions.parse('foo,bar;baz,foo;baz'); + const extensions = extension.parse('foo,bar;baz,foo;baz'); assert.deepStrictEqual(extensions, { foo: [{}, { baz: [true] }], @@ -35,30 +35,30 @@ describe('Extensions', function () { }); it('parses quoted params', function () { - assert.deepStrictEqual(Extensions.parse('foo;bar="hi"'), { + assert.deepStrictEqual(extension.parse('foo;bar="hi"'), { foo: [{ bar: ['hi'] }] }); - assert.deepStrictEqual(Extensions.parse('foo;bar="\\0"'), { + assert.deepStrictEqual(extension.parse('foo;bar="\\0"'), { foo: [{ bar: ['0'] }] }); - assert.deepStrictEqual(Extensions.parse('foo;bar="b\\a\\z"'), { + assert.deepStrictEqual(extension.parse('foo;bar="b\\a\\z"'), { foo: [{ bar: ['baz'] }] }); - assert.deepStrictEqual(Extensions.parse('foo;bar="b\\az";bar'), { + assert.deepStrictEqual(extension.parse('foo;bar="b\\az";bar'), { foo: [{ bar: ['baz', true] }] }); assert.throws( - () => Extensions.parse('foo;bar="baz"qux'), + () => extension.parse('foo;bar="baz"qux'), /^SyntaxError: Unexpected character at index 13$/ ); assert.throws( - () => Extensions.parse('foo;bar="baz" qux'), + () => extension.parse('foo;bar="baz" qux'), /^SyntaxError: Unexpected character at index 14$/ ); }); it('works with names that match Object.prototype property names', function () { - const parse = Extensions.parse; + const parse = extension.parse; assert.deepStrictEqual(parse('hasOwnProperty, toString'), { hasOwnProperty: [{}], @@ -72,7 +72,7 @@ describe('Extensions', function () { it('ignores the optional white spaces', function () { const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf '; - assert.deepStrictEqual(Extensions.parse(header), { + assert.deepStrictEqual(extension.parse(header), { foo: [{ bar: [true, '1'], baz: ['1'] }], qux: [{ norf: [true] }] }); @@ -91,7 +91,7 @@ describe('Extensions', function () { ['foo;bar=""', 9] ].forEach((element) => { assert.throws( - () => Extensions.parse(element[0]), + () => extension.parse(element[0]), new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); @@ -105,7 +105,7 @@ describe('Extensions', function () { ['foo;bar= ', 8] ].forEach((element) => { assert.throws( - () => Extensions.parse(element[0]), + () => extension.parse(element[0]), new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); @@ -129,7 +129,7 @@ describe('Extensions', function () { ['foo;bar="\\\\"', 10] ].forEach((element) => { assert.throws( - () => Extensions.parse(element[0]), + () => extension.parse(element[0]), new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); @@ -146,7 +146,7 @@ describe('Extensions', function () { 'foo;bar="1\\' ].forEach((header) => { assert.throws( - () => Extensions.parse(header), + () => extension.parse(header), /^SyntaxError: Unexpected end of input$/ ); }); @@ -155,19 +155,19 @@ describe('Extensions', function () { describe('format', function () { it('formats a single extension', function () { - const extensions = Extensions.format({ foo: {} }); + const extensions = extension.format({ foo: {} }); assert.strictEqual(extensions, 'foo'); }); it('formats params', function () { - const extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); + const extensions = extension.format({ foo: { bar: [true, 2], baz: 1 } }); assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); it('formats multiple extensions', function () { - const extensions = Extensions.format({ + const extensions = extension.format({ foo: [{}, { baz: true }], bar: { baz: true } }); diff --git a/test/PerMessageDeflate.test.js b/test/permessage-deflate.test.js similarity index 88% rename from test/PerMessageDeflate.test.js rename to test/permessage-deflate.test.js index 5f74b9d3d..76dc8aee6 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/permessage-deflate.test.js @@ -3,8 +3,8 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const PerMessageDeflate = require('../lib/PerMessageDeflate'); -const Extensions = require('../lib/Extensions'); +const PerMessageDeflate = require('../lib/permessage-deflate'); +const extension = require('../lib/extension'); const Buffer = safeBuffer.Buffer; @@ -46,7 +46,7 @@ describe('PerMessageDeflate', function () { it('should accept offer', function () { const perMessageDeflate = new PerMessageDeflate({}, true); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' @@ -67,7 +67,7 @@ describe('PerMessageDeflate', function () { serverMaxWindowBits: 12, clientMaxWindowBits: 11 }, true); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=14; client_max_window_bits=13' ); @@ -81,7 +81,7 @@ describe('PerMessageDeflate', function () { it('should fallback', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=10, permessage-deflate' ); @@ -92,28 +92,28 @@ describe('PerMessageDeflate', function () { it('should throw an error if server_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); - const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); + const extensions = extension.parse('permessage-deflate; server_no_context_takeover'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); - const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is less than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported on client', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); - const extensions = Extensions.parse('permessage-deflate'); + const extensions = extension.parse('permessage-deflate'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); @@ -128,7 +128,7 @@ describe('PerMessageDeflate', function () { it('should accept response parameter', function () { const perMessageDeflate = new PerMessageDeflate({}); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' @@ -144,21 +144,21 @@ describe('PerMessageDeflate', function () { it('should throw an error if client_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); - const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); + const extensions = extension.parse('permessage-deflate; client_no_context_takeover'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); - const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); + const extensions = extension.parse('permessage-deflate; client_max_window_bits=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is greater than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); - const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); + const extensions = extension.parse('permessage-deflate; client_max_window_bits=11'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); @@ -177,7 +177,7 @@ describe('PerMessageDeflate', function () { describe('validate parameters', function () { it('should throw an error if a parameter has multiple values', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' ); @@ -186,14 +186,14 @@ describe('PerMessageDeflate', function () { it('should throw an error if server_no_context_takeover has a value', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); + const extensions = extension.parse('permessage-deflate; server_no_context_takeover=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_no_context_takeover has a value', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); + const extensions = extension.parse('permessage-deflate; client_no_context_takeover=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); @@ -201,26 +201,26 @@ describe('PerMessageDeflate', function () { it('should throw an error if server_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate({}, true); - let extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + let extensions = extension.parse('permessage-deflate; server_max_window_bits=7'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - extensions = Extensions.parse('permessage-deflate; server_max_window_bits'); + extensions = extension.parse('permessage-deflate; server_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate(); - let extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + let extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - extensions = Extensions.parse('permessage-deflate; client_max_window_bits'); + extensions = extension.parse('permessage-deflate; client_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('throws an error if a parameter has an invalid name', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate;foo'); + const extensions = extension.parse('permessage-deflate;foo'); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -279,7 +279,7 @@ describe('PerMessageDeflate', function () { memLevel: 5, level: 9 }); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' @@ -317,8 +317,8 @@ describe('PerMessageDeflate', function () { const srcData = 'Some compressible data, it\'s compressible.'; const srcDataBuffer = Buffer.from(srcData, 'utf8'); - perMessageDeflateLev0.accept(Extensions.parse(extensionStr)['permessage-deflate']); - perMessageDeflateLev9.accept(Extensions.parse(extensionStr)['permessage-deflate']); + perMessageDeflateLev0.accept(extension.parse(extensionStr)['permessage-deflate']); + perMessageDeflateLev9.accept(extension.parse(extensionStr)['permessage-deflate']); perMessageDeflateLev0.compress(srcDataBuffer, true, (err, compressed1) => { if (err) return done(err); @@ -349,7 +349,7 @@ describe('PerMessageDeflate', function () { it('should compress/decompress data with no context takeover', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; client_no_context_takeover' ); const buf = Buffer.from('foofoo'); @@ -379,7 +379,7 @@ describe('PerMessageDeflate', function () { it('should compress data between contexts when allowed', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - const extensions = Extensions.parse('permessage-deflate'); + const extensions = extension.parse('permessage-deflate'); const buf = Buffer.from('foofoo'); perMessageDeflate.accept(extensions['permessage-deflate']); diff --git a/test/Receiver.test.js b/test/receiver.test.js similarity index 99% rename from test/Receiver.test.js rename to test/receiver.test.js index b6affbd00..d96528ee2 100644 --- a/test/Receiver.test.js +++ b/test/receiver.test.js @@ -4,9 +4,9 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); -const PerMessageDeflate = require('../lib/PerMessageDeflate'); -const Receiver = require('../lib/Receiver'); -const Sender = require('../lib/Sender'); +const PerMessageDeflate = require('../lib/permessage-deflate'); +const Receiver = require('../lib/receiver'); +const Sender = require('../lib/sender'); const Buffer = safeBuffer.Buffer; diff --git a/test/Sender.test.js b/test/sender.test.js similarity index 98% rename from test/Sender.test.js rename to test/sender.test.js index ac19d1ecd..69d4012e2 100644 --- a/test/Sender.test.js +++ b/test/sender.test.js @@ -3,8 +3,8 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const PerMessageDeflate = require('../lib/PerMessageDeflate'); -const Sender = require('../lib/Sender'); +const PerMessageDeflate = require('../lib/permessage-deflate'); +const Sender = require('../lib/sender'); const Buffer = safeBuffer.Buffer; diff --git a/test/Validation.test.js b/test/validation.test.js similarity index 96% rename from test/Validation.test.js rename to test/validation.test.js index b2f38d3a1..d7dcad28e 100644 --- a/test/Validation.test.js +++ b/test/validation.test.js @@ -3,11 +3,11 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const validation = require('../lib/Validation'); +const validation = require('../lib/validation'); const Buffer = safeBuffer.Buffer; -describe('Validation', function () { +describe('validation', function () { describe('isValidUTF8', function () { it('should return true for a valid utf8 string', function () { const validBuffer = Buffer.from( diff --git a/test/WebSocketServer.test.js b/test/websocket-server.test.js similarity index 99% rename from test/WebSocketServer.test.js rename to test/websocket-server.test.js index 8e97c9bb0..0d8c86737 100644 --- a/test/WebSocketServer.test.js +++ b/test/websocket-server.test.js @@ -277,7 +277,6 @@ describe('WebSocketServer', function () { }); it('maxpayload is passed on to permessage-deflate', function (done) { - const PerMessageDeflate = require('../lib/PerMessageDeflate'); const maxPayload = 20480; const wss = new WebSocket.Server({ perMessageDeflate: true, @@ -290,7 +289,7 @@ describe('WebSocketServer', function () { wss.on('connection', (client) => { assert.strictEqual( - client._receiver._extensions[PerMessageDeflate.extensionName]._maxPayload, + client._receiver._extensions['permessage-deflate']._maxPayload, maxPayload ); wss.close(done); diff --git a/test/WebSocket.integration.js b/test/websocket.integration.js similarity index 100% rename from test/WebSocket.integration.js rename to test/websocket.integration.js diff --git a/test/WebSocket.test.js b/test/websocket.test.js similarity index 99% rename from test/WebSocket.test.js rename to test/websocket.test.js index aa4fced44..5356c7ee8 100644 --- a/test/WebSocket.test.js +++ b/test/websocket.test.js @@ -12,7 +12,7 @@ const net = require('net'); const fs = require('fs'); const os = require('os'); -const constants = require('../lib/Constants'); +const constants = require('../lib/constants'); const WebSocket = require('..'); const Buffer = safeBuffer.Buffer; From 63e275e75f8ab8f4385e7ef104a74c582cd0c793 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 17:54:00 +0100 Subject: [PATCH 406/489] [fix] Pass an `ErrorEvent` to the `onerror` event handler Fixes #1173 --- lib/event-target.js | 27 +++++++++++++++++++++++---- test/websocket.test.js | 37 ++++++++----------------------------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/event-target.js b/lib/event-target.js index c48137b91..574e9080e 100644 --- a/lib/event-target.js +++ b/lib/event-target.js @@ -78,6 +78,27 @@ class OpenEvent extends Event { } } +/** + * Class representing an error event. + * + * @extends Event + * @private + */ +class ErrorEvent extends Event { + /** + * Create a new `ErrorEvent`. + * + * @param {Object} error The error that generated this event + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (error, target) { + super('error', target); + + this.message = error.message; + this.error = error; + } +} + /** * This provides methods for emulating the `EventTarget` interface. It's not * meant to be used directly. @@ -103,10 +124,8 @@ const EventTarget = { listener.call(this, new CloseEvent(code, message, this)); } - function onError (event) { - event.type = 'error'; - event.target = this; - listener.call(this, event); + function onError (error) { + listener.call(this, new ErrorEvent(error, this)); } function onOpen () { diff --git a/test/websocket.test.js b/test/websocket.test.js index 5356c7ee8..634340349 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -1656,52 +1656,31 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.close(1001, 'some daft reason')); }); - it('should have target set on Events', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('open', (openEvent) => { - assert.strictEqual(openEvent.target, ws); - }); - ws.addEventListener('message', (messageEvent) => { - assert.strictEqual(messageEvent.target, ws); - wss.close(); - }); - ws.addEventListener('close', (closeEvent) => { - assert.strictEqual(closeEvent.target, ws); - ws.emit('error', new Error('forced')); - }); - ws.addEventListener('error', (errorEvent) => { - assert.strictEqual(errorEvent.message, 'forced'); - assert.strictEqual(errorEvent.target, ws); - - done(); - }); - }); - - wss.on('connection', (client) => client.send('hi')); - }); - - it('should have type set on Events', function (done) { + it('sets `target` and `type` on events', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { + const err = new Error('forced'); const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', (openEvent) => { assert.strictEqual(openEvent.type, 'open'); + assert.strictEqual(openEvent.target, ws); }); ws.addEventListener('message', (messageEvent) => { assert.strictEqual(messageEvent.type, 'message'); + assert.strictEqual(messageEvent.target, ws); wss.close(); }); ws.addEventListener('close', (closeEvent) => { assert.strictEqual(closeEvent.type, 'close'); - ws.emit('error', new Error('forced')); + assert.strictEqual(closeEvent.target, ws); + ws.emit('error', err); }); ws.addEventListener('error', (errorEvent) => { assert.strictEqual(errorEvent.message, 'forced'); assert.strictEqual(errorEvent.type, 'error'); + assert.strictEqual(errorEvent.target, ws); + assert.strictEqual(errorEvent.error, err); done(); }); From 30c9f715247b2f5ad0c691e535708f894d82ed2b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 24 Dec 2017 10:11:49 +0100 Subject: [PATCH 407/489] [major] Make `WebSocket#p{i,o}ng()` accept an optional callback Fixes #599 --- README.md | 4 ++- doc/ws.md | 14 ++++----- lib/sender.js | 24 ++++++++------- lib/websocket.js | 55 +++++++++++++++++++++++------------ test/websocket.test.js | 66 +++++++++++++++++++++++++----------------- 5 files changed, 101 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index e2cc3bd38..12f06c16c 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,8 @@ const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); +function noop() {} + function heartbeat() { this.isAlive = true; } @@ -312,7 +314,7 @@ const interval = setInterval(function ping() { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; - ws.ping('', false, true); + ws.ping(noop); }); }, 30000); ``` diff --git a/doc/ws.md b/doc/ws.md index b2962b2ff..c9cb973eb 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -355,23 +355,23 @@ receives an `OpenEvent` named "open". Pause the socket. -### websocket.ping([data[, mask[, failSilently]]]) +### websocket.ping([data[, mask]][, callback]) - `data` {Any} The data to send in the ping frame. - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. -- `failSilently` {Boolean} Specifies whether or not to throw an error if the - connection is not open. +- `callback` {Function} An optional callback which is invoked when the ping + frame is written out. Send a ping. -### websocket.pong([data[, mask[, failSilently]]]) +### websocket.pong([data[, mask]][, callback]) - `data` {Any} The data to send in the pong frame. - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. -- `failSilently` {Boolean} Specifies whether or not to throw an error if the - connection is not open. +- `callback` {Function} An optional callback which is invoked when the pong + frame is written out. Send a pong. @@ -404,7 +404,7 @@ Removes an event listener emulating the `EventTarget` interface. Resume the socket. -### websocket.send(data, [options][, callback]) +### websocket.send(data[, options][, callback]) - `data` {Any} The data to send. - `options` {Object} diff --git a/lib/sender.js b/lib/sender.js index 4526d2204..61d73df30 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -158,9 +158,10 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Function} cb Callback * @public */ - ping (data, mask) { + ping (data, mask, cb) { var readOnly = true; if (!Buffer.isBuffer(data)) { @@ -175,9 +176,9 @@ class Sender { } if (this._deflating) { - this.enqueue([this.doPing, data, mask, readOnly]); + this.enqueue([this.doPing, data, mask, readOnly, cb]); } else { - this.doPing(data, mask, readOnly); + this.doPing(data, mask, readOnly, cb); } } @@ -187,16 +188,17 @@ class Sender { * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} readOnly Specifies whether `data` can be modified + * @param {Function} cb Callback * @private */ - doPing (data, mask, readOnly) { + doPing (data, mask, readOnly, cb) { this.sendFrame(Sender.frame(data, { fin: true, rsv1: false, opcode: 0x09, mask, readOnly - })); + }), cb); } /** @@ -204,9 +206,10 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Function} cb Callback * @public */ - pong (data, mask) { + pong (data, mask, cb) { var readOnly = true; if (!Buffer.isBuffer(data)) { @@ -221,9 +224,9 @@ class Sender { } if (this._deflating) { - this.enqueue([this.doPong, data, mask, readOnly]); + this.enqueue([this.doPong, data, mask, readOnly, cb]); } else { - this.doPong(data, mask, readOnly); + this.doPong(data, mask, readOnly, cb); } } @@ -233,16 +236,17 @@ class Sender { * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} readOnly Specifies whether `data` can be modified + * @param {Function} cb Callback * @private */ - doPong (data, mask, readOnly) { + doPong (data, mask, readOnly, cb) { this.sendFrame(Sender.frame(data, { fin: true, rsv1: false, opcode: 0x0a, mask, readOnly - })); + }), cb); } /** diff --git a/lib/websocket.js b/lib/websocket.js index 31f4ba7a6..1a38497c9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -141,7 +141,7 @@ class WebSocket extends EventEmitter { this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { - this.pong(data, !this._isServer, true); + this.pong(data, !this._isServer, constants.NOOP); this.emit('ping', data); }; this._receiver.onpong = (data) => this.emit('pong', data); @@ -317,47 +317,67 @@ class WebSocket extends EventEmitter { } /** - * Send a ping message. + * Send a ping. * - * @param {*} data The message to send + * @param {*} data The data to send * @param {Boolean} mask Indicates whether or not to mask `data` - * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` + * @param {Function} cb Callback which is executed when the ping is sent * @public */ - ping (data, mask, failSilently) { + ping (data, mask, cb) { + if (typeof data === 'function') { + cb = data; + data = mask = undefined; + } else if (typeof mask === 'function') { + cb = mask; + mask = undefined; + } + if (this.readyState !== WebSocket.OPEN) { - if (failSilently) return; - throw new Error( + const err = new Error( `WebSocket is not open: readyState ${this.readyState} ` + `(${readyStates[this.readyState]})` ); + + if (cb) return cb(err); + throw err; } if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; - this._sender.ping(data || constants.EMPTY_BUFFER, mask); + this._sender.ping(data || constants.EMPTY_BUFFER, mask, cb); } /** - * Send a pong message. + * Send a pong. * - * @param {*} data The message to send + * @param {*} data The data to send * @param {Boolean} mask Indicates whether or not to mask `data` - * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` + * @param {Function} cb Callback which is executed when the pong is sent * @public */ - pong (data, mask, failSilently) { + pong (data, mask, cb) { + if (typeof data === 'function') { + cb = data; + data = mask = undefined; + } else if (typeof mask === 'function') { + cb = mask; + mask = undefined; + } + if (this.readyState !== WebSocket.OPEN) { - if (failSilently) return; - throw new Error( + const err = new Error( `WebSocket is not open: readyState ${this.readyState} ` + `(${readyStates[this.readyState]})` ); + + if (cb) return cb(err); + throw err; } if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; - this._sender.pong(data || constants.EMPTY_BUFFER, mask); + this._sender.pong(data || constants.EMPTY_BUFFER, mask, cb); } /** @@ -384,9 +404,8 @@ class WebSocket extends EventEmitter { `(${readyStates[this.readyState]})` ); - if (cb) cb(err); - else throw err; - return; + if (cb) return cb(err); + throw err; } if (typeof data === 'number') data = data.toString(); diff --git a/test/websocket.test.js b/test/websocket.test.js index 634340349..30ad6e9e6 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -663,7 +663,7 @@ describe('WebSocket', function () { }); describe('#ping', function () { - it('before connect should fail', function () { + it('throws an error if `readyState` is not `OPEN`', function (done) { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -672,37 +672,44 @@ describe('WebSocket', function () { () => ws.ping(), /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ ); - }); - it('before connect can silently fail', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() + ws.ping((err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 0 (CONNECTING)' + ); + done(); }); - - assert.doesNotThrow(() => ws.ping('', true, true)); }); - it('without message is successfully transmitted to the server', function (done) { + it('can send a ping with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.ping()); + ws.on('open', () => { + ws.ping(() => ws.ping()); + }); }); wss.on('connection', (ws) => { - ws.on('ping', () => wss.close(done)); + let pings = 0; + ws.on('ping', (data) => { + assert.ok(Buffer.isBuffer(data)); + assert.strictEqual(data.length, 0); + if (++pings === 2) wss.close(done); + }); }); }); - it('with message is successfully transmitted to the server', function (done) { + it('can send a ping with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { - ws.ping('hi', true); - ws.ping('hi'); + ws.ping('hi', () => ws.ping('hi', true)); }); }); @@ -733,7 +740,7 @@ describe('WebSocket', function () { }); describe('#pong', function () { - it('before connect should fail', () => { + it('throws an error if `readyState` is not `OPEN`', (done) => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -742,37 +749,44 @@ describe('WebSocket', function () { () => ws.pong(), /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ ); - }); - it('before connect can silently fail', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() + ws.pong((err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 0 (CONNECTING)' + ); + done(); }); - - assert.doesNotThrow(() => ws.pong('', true, true)); }); - it('without message is successfully transmitted to the server', function (done) { + it('can send a pong with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.pong()); + ws.on('open', () => { + ws.pong(() => ws.pong()); + }); }); wss.on('connection', (ws) => { - ws.on('pong', () => wss.close(done)); + let pongs = 0; + ws.on('pong', (data) => { + assert.ok(Buffer.isBuffer(data)); + assert.strictEqual(data.length, 0); + if (++pongs === 2) wss.close(done); + }); }); }); - it('with message is successfully transmitted to the server', function (done) { + it('can send a pong with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { - ws.pong('hi', true); - ws.pong('hi'); + ws.pong('hi', () => ws.pong('hi', true)); }); }); From 7f8ebc660896abd586d2abcad63a5533e577b9d6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 24 Dec 2017 17:40:13 +0100 Subject: [PATCH 408/489] [major] Remove non-standard `protocolVersion` attribute --- doc/ws.md | 6 ------ lib/websocket-server.js | 16 +++------------- lib/websocket.js | 3 --- test/websocket-server.test.js | 14 -------------- test/websocket.test.js | 8 -------- 5 files changed, 3 insertions(+), 44 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index c9cb973eb..b101d1612 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -381,12 +381,6 @@ Send a pong. The subprotocol selected by the server. -### websocket.protocolVersion - -- {Number} - -The WebSocket protocol version used for this connection, 8 or 13. - ### websocket.readyState - {Number} diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 3ff6690e6..0bfc24b1a 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -210,15 +210,7 @@ class WebSocketServer extends EventEmitter { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - this.completeUpgrade( - protocol, - extensions, - version, - req, - socket, - head, - cb - ); + this.completeUpgrade(protocol, extensions, req, socket, head, cb); }); return; } @@ -226,7 +218,7 @@ class WebSocketServer extends EventEmitter { if (!this.options.verifyClient(info)) return abortConnection(socket, 401); } - this.completeUpgrade(protocol, extensions, version, req, socket, head, cb); + this.completeUpgrade(protocol, extensions, req, socket, head, cb); } /** @@ -234,14 +226,13 @@ class WebSocketServer extends EventEmitter { * * @param {String} protocol The chosen subprotocol * @param {Object} extensions The accepted extensions - * @param {Number} version The WebSocket protocol version * @param {http.IncomingMessage} req The request object * @param {net.Socket} socket The network socket between the server and client * @param {Buffer} head The first packet of the upgraded stream * @param {Function} cb Callback * @private */ - completeUpgrade (protocol, extensions, version, req, socket, head, cb) { + completeUpgrade (protocol, extensions, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // @@ -276,7 +267,6 @@ class WebSocketServer extends EventEmitter { const client = new WebSocket([socket, head], null, { maxPayload: this.options.maxPayload, - protocolVersion: version, extensions, protocol }); diff --git a/lib/websocket.js b/lib/websocket.js index 1a38497c9..f0953d289 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -496,14 +496,12 @@ module.exports = WebSocket; * @param {net.Socket} socket The network socket between the server and client * @param {Buffer} head The first packet of the upgraded stream * @param {Object} options WebSocket attributes - * @param {Number} options.protocolVersion The WebSocket protocol version * @param {Object} options.extensions The negotiated extensions * @param {Number} options.maxPayload The maximum allowed message size * @param {String} options.protocol The chosen subprotocol * @private */ function initAsServerClient (socket, head, options) { - this.protocolVersion = options.protocolVersion; this._maxPayload = options.maxPayload; this.extensions = options.extensions; this.protocol = options.protocol; @@ -574,7 +572,6 @@ function initAsClient (address, protocols, options) { ); } - this.protocolVersion = options.protocolVersion; this._isServer = false; this.url = address; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 0d8c86737..64ed60c77 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -906,20 +906,6 @@ describe('WebSocketServer', function () { wss.close(done); }); }); - - it('protocolVersion is exposed', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - protocolVersion: 8 - }); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client.protocolVersion, 8); - wss.close(done); - }); - }); }); describe('permessage-deflate', function () { diff --git a/test/websocket.test.js b/test/websocket.test.js index 30ad6e9e6..cf69b226c 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -158,14 +158,6 @@ describe('WebSocket', function () { assert.strictEqual(ws.url, url); }); - it('#protocolVersion exposes the protocol version', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() - }); - - assert.strictEqual(ws.protocolVersion, 13); - }); - describe('#bufferedAmount', function () { it('defaults to zero', function () { const ws = new WebSocket('ws://localhost', { From ee9b5f3515f21509242162942d8efb1ec723c836 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 25 Dec 2017 07:43:46 +0100 Subject: [PATCH 409/489] [major] Remove non-standard `bytesReceived` attribute --- doc/ws.md | 6 ------ lib/websocket.js | 6 +----- test/websocket.test.js | 12 ------------ 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index b101d1612..215a3010a 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -302,12 +302,6 @@ of binary protocols transferring large messages with multiple fragments. The number of bytes of data that have been queued using calls to `send()` but not yet transmitted to the network. -### websocket.bytesReceived - -- {Number} - -Received bytes count. - ### websocket.close([code[, reason]]) - `code` {Number} A numeric value indicating the status code explaining why diff --git a/lib/websocket.js b/lib/websocket.js index f0953d289..d0b476084 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -50,7 +50,6 @@ class WebSocket extends EventEmitter { } this.readyState = WebSocket.CONNECTING; - this.bytesReceived = 0; this.extensions = {}; this.protocol = ''; @@ -134,10 +133,7 @@ class WebSocket extends EventEmitter { if (head.length > 0) socket.unshift(head); - this._ultron.on('data', (data) => { - this.bytesReceived += data.length; - this._receiver.add(data); - }); + this._ultron.on('data', (data) => this._receiver.add(data)); this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { diff --git a/test/websocket.test.js b/test/websocket.test.js index cf69b226c..2b9836275 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -139,18 +139,6 @@ describe('WebSocket', function () { }); describe('properties', function () { - it('#bytesReceived exposes number of bytes received', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('message', () => { - assert.strictEqual(ws.bytesReceived, 8); - wss.close(done); - }); - }); - wss.on('connection', (ws) => ws.send('foobar')); - }); - it('#url exposes the server url', function () { const url = 'ws://localhost'; const ws = new WebSocket(url, { agent: new CustomAgent() }); From 46461a937f8fe6d25cd83510f7bfda69c23109be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 25 Dec 2017 11:19:46 +0100 Subject: [PATCH 410/489] [minor] Refactor server client initialization --- lib/websocket-server.js | 22 ++++++++------- lib/websocket.js | 52 ++++++++++------------------------- test/websocket-server.test.js | 13 --------- 3 files changed, 27 insertions(+), 60 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 0bfc24b1a..0654eb0bf 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -249,13 +249,19 @@ class WebSocketServer extends EventEmitter { `Sec-WebSocket-Accept: ${key}` ]; - if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + const ws = new WebSocket(null); + + if (protocol) { + headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + ws.protocol = protocol; + } if (extensions[PerMessageDeflate.extensionName]) { const params = extensions[PerMessageDeflate.extensionName].params; const value = extension.format({ [PerMessageDeflate.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); + ws.extensions = extensions; } // @@ -264,20 +270,16 @@ class WebSocketServer extends EventEmitter { this.emit('headers', headers, req); socket.write(headers.concat('\r\n').join('\r\n')); + socket.removeListener('error', socketError); - const client = new WebSocket([socket, head], null, { - maxPayload: this.options.maxPayload, - extensions, - protocol - }); + ws.setSocket(socket, head, this.options.maxPayload); if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); + this.clients.add(ws); + ws.on('close', () => this.clients.delete(ws)); } - socket.removeListener('error', socketError); - cb(client); + cb(ws); } } diff --git a/lib/websocket.js b/lib/websocket.js index d0b476084..c1954824b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -40,15 +40,6 @@ class WebSocket extends EventEmitter { constructor (address, protocols, options) { super(); - if (!protocols) { - protocols = []; - } else if (typeof protocols === 'string') { - protocols = [protocols]; - } else if (!Array.isArray(protocols)) { - options = protocols; - protocols = []; - } - this.readyState = WebSocket.CONNECTING; this.extensions = {}; this.protocol = ''; @@ -61,14 +52,22 @@ class WebSocket extends EventEmitter { this._closeTimer = null; this._finalized = false; this._closeCode = 1006; + this._isServer = true; this._receiver = null; this._sender = null; this._socket = null; this._ultron = null; - if (Array.isArray(address)) { - initAsServerClient.call(this, address[0], address[1], options); - } else { + if (address !== null) { + if (!protocols) { + protocols = []; + } else if (typeof protocols === 'string') { + protocols = [protocols]; + } else if (!Array.isArray(protocols)) { + options = protocols; + protocols = []; + } + initAsClient.call(this, address, protocols, options); } } @@ -116,13 +115,14 @@ class WebSocket extends EventEmitter { * * @param {net.Socket} socket The network socket between the server and client * @param {Buffer} head The first packet of the upgraded stream + * @param {Number} maxPayload The maximum allowed message size * @private */ - setSocket (socket, head) { + setSocket (socket, head, maxPayload) { socket.setTimeout(0); socket.setNoDelay(); - this._receiver = new Receiver(this.extensions, this._maxPayload, this.binaryType); + this._receiver = new Receiver(this.extensions, maxPayload, this.binaryType); this._sender = new Sender(socket, this.extensions); this._ultron = new Ultron(socket); this._socket = socket; @@ -485,28 +485,6 @@ WebSocket.prototype.removeEventListener = EventTarget.removeEventListener; module.exports = WebSocket; -/** - * Initialize a WebSocket server client. - * - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Object} options WebSocket attributes - * @param {Object} options.extensions The negotiated extensions - * @param {Number} options.maxPayload The maximum allowed message size - * @param {String} options.protocol The chosen subprotocol - * @private - */ -function initAsServerClient (socket, head, options) { - this._maxPayload = options.maxPayload; - this.extensions = options.extensions; - this.protocol = options.protocol; - - this._isServer = true; - - this.setSocket(socket, head); -} - /** * Initialize a WebSocket client. * @@ -753,6 +731,6 @@ function initAsClient (address, protocols, options) { } } - this.setSocket(socket, head); + this.setSocket(socket, head, 0); }); } diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 64ed60c77..ff1b367bc 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -250,19 +250,6 @@ describe('WebSocketServer', function () { }); describe('#maxpayload', function () { - it('maxpayload is passed on to clients', function (done) { - const maxPayload = 20480; - const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client._maxPayload, maxPayload); - wss.close(done); - }); - }); - it('maxpayload is passed on to hybi receivers', function (done) { const maxPayload = 20480; const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { From fdec524555a29875b3a449d597a3432e453e87f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 25 Dec 2017 18:25:47 +0100 Subject: [PATCH 411/489] [fix] Fix `extentions` property type Make `extensions` a getter that returns the negotiated extensions names. Fixes #1244 --- lib/websocket-server.js | 2 +- lib/websocket.js | 47 +++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 0654eb0bf..8bc3b2938 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -261,7 +261,7 @@ class WebSocketServer extends EventEmitter { [PerMessageDeflate.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); - ws.extensions = extensions; + ws._extensions = extensions; } // diff --git a/lib/websocket.js b/lib/websocket.js index c1954824b..907de2117 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -41,7 +41,6 @@ class WebSocket extends EventEmitter { super(); this.readyState = WebSocket.CONNECTING; - this.extensions = {}; this.protocol = ''; this._binaryType = constants.BINARY_TYPES[0]; @@ -52,6 +51,7 @@ class WebSocket extends EventEmitter { this._closeTimer = null; this._finalized = false; this._closeCode = 1006; + this._extensions = {}; this._isServer = true; this._receiver = null; this._sender = null; @@ -77,18 +77,6 @@ class WebSocket extends EventEmitter { get CLOSED () { return WebSocket.CLOSED; } get OPEN () { return WebSocket.OPEN; } - /** - * @type {Number} - */ - get bufferedAmount () { - var amount = 0; - - if (this._socket) { - amount = this._socket.bufferSize + this._sender._bufferedBytes; - } - return amount; - } - /** * This deviates from the WHATWG interface since ws doesn't support the required * default "blob" type (instead we define a custom "nodebuffer" type). @@ -110,6 +98,25 @@ class WebSocket extends EventEmitter { if (this._receiver) this._receiver._binaryType = type; } + /** + * @type {Number} + */ + get bufferedAmount () { + var amount = 0; + + if (this._socket) { + amount = this._socket.bufferSize + this._sender._bufferedBytes; + } + return amount; + } + + /** + * @type {String} + */ + get extensions () { + return Object.keys(this._extensions).join(); + } + /** * Set up the socket and the internal resources. * @@ -122,8 +129,8 @@ class WebSocket extends EventEmitter { socket.setTimeout(0); socket.setNoDelay(); - this._receiver = new Receiver(this.extensions, maxPayload, this.binaryType); - this._sender = new Sender(socket, this.extensions); + this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType); + this._sender = new Sender(socket, this._extensions); this._ultron = new Ultron(socket); this._socket = socket; @@ -211,12 +218,10 @@ class WebSocket extends EventEmitter { this.emit('close', this._closeCode, this._closeMessage); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.extensions[PerMessageDeflate.extensionName].cleanup(); + if (this._extensions[PerMessageDeflate.extensionName]) { + this._extensions[PerMessageDeflate.extensionName].cleanup(); } - this.extensions = null; - this.removeAllListeners(); } @@ -413,7 +418,7 @@ class WebSocket extends EventEmitter { fin: true }, options); - if (!this.extensions[PerMessageDeflate.extensionName]) { + if (!this._extensions[PerMessageDeflate.extensionName]) { opts.compress = false; } @@ -722,7 +727,7 @@ function initAsClient (address, protocols, options) { perMessageDeflate.accept( extensions[PerMessageDeflate.extensionName] ); - this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } } catch (err) { socket.destroy(); From 9bbc9783681160e3ef98f2be47de1ecef228a1a9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 26 Dec 2017 10:59:14 +0100 Subject: [PATCH 412/489] [test] Reorganize tests --- test/extension.test.js | 4 +- test/permessage-deflate.test.js | 289 ++++++----- test/validation.test.js | 53 -- test/websocket-server.test.js | 637 ++++++++--------------- test/websocket.test.js | 863 ++++++++++++++++---------------- 5 files changed, 807 insertions(+), 1039 deletions(-) delete mode 100644 test/validation.test.js diff --git a/test/extension.test.js b/test/extension.test.js index 15d1787a6..7e389811b 100644 --- a/test/extension.test.js +++ b/test/extension.test.js @@ -6,7 +6,7 @@ const extension = require('../lib/extension'); describe('extension', function () { describe('parse', function () { - it('returns an empty object if the argument is undefined', function () { + it('returns an empty object if the argument is `undefined`', function () { assert.deepStrictEqual(extension.parse(), {}); assert.deepStrictEqual(extension.parse(''), {}); }); @@ -57,7 +57,7 @@ describe('extension', function () { ); }); - it('works with names that match Object.prototype property names', function () { + it('works with names that match `Object.prototype` property names', function () { const parse = extension.parse; assert.deepStrictEqual(parse('hasOwnProperty, toString'), { diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 76dc8aee6..929315988 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -10,7 +10,7 @@ const Buffer = safeBuffer.Buffer; describe('PerMessageDeflate', function () { describe('#offer', function () { - it('should create default params', function () { + it('creates an offer', function () { const perMessageDeflate = new PerMessageDeflate(); assert.deepStrictEqual( @@ -19,7 +19,7 @@ describe('PerMessageDeflate', function () { ); }); - it('should create params from options', function () { + it('uses the configuration options', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -37,14 +37,72 @@ describe('PerMessageDeflate', function () { }); describe('#accept', function () { - describe('as server', function () { - it('should accept empty offer', function () { + it('throws an error if a parameter has multiple values', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse( + 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' + ); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Parameter "server_no_context_takeover" must have only a single value$/ + ); + }); + + it('throws an error if a parameter has an invalid name', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse('permessage-deflate;foo'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unknown parameter "foo"$/ + ); + }); + + it('throws an error if client_no_context_takeover has a value', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse('permessage-deflate; client_no_context_takeover=10'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "client_no_context_takeover": 10$/ + ); + }); + + it('throws an error if server_no_context_takeover has a value', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse('permessage-deflate; server_no_context_takeover=10'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "server_no_context_takeover": 10$/ + ); + }); + + it('throws an error if server_max_window_bits has an invalid value', function () { + const perMessageDeflate = new PerMessageDeflate(); + + let extensions = extension.parse('permessage-deflate; server_max_window_bits=7'); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "server_max_window_bits": 7$/ + ); + + extensions = extension.parse('permessage-deflate; server_max_window_bits'); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "server_max_window_bits": true$/ + ); + }); + + describe('As server', function () { + it('accepts an offer with no parameters', function () { const perMessageDeflate = new PerMessageDeflate({}, true); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('should accept offer', function () { + it('accepts an offer with parameters', function () { const perMessageDeflate = new PerMessageDeflate({}, true); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + @@ -60,7 +118,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should prefer configuration than offer', function () { + it('prefers the configuration options', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -79,7 +137,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should fallback', function () { + it('accepts the first supported offer', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=10, permessage-deflate' @@ -90,43 +148,65 @@ describe('PerMessageDeflate', function () { }); }); - it('should throw an error if server_no_context_takeover is unsupported', function () { + it('throws an error if server_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); const extensions = extension.parse('permessage-deflate; server_no_context_takeover'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); }); - it('should throw an error if server_max_window_bits is unsupported', function () { + it('throws an error if server_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); }); - it('should throw an error if server_max_window_bits is less than configuration', function () { + it('throws an error if server_max_window_bits is less than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); }); - it('should throw an error if client_max_window_bits is unsupported on client', function () { + it('throws an error if client_max_window_bits is unsupported on client', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); const extensions = extension.parse('permessage-deflate'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); + }); + + it('throws an error if client_max_window_bits has an invalid value', function () { + const perMessageDeflate = new PerMessageDeflate({}, true); + + const extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ + ); }); }); - describe('as client', function () { - it('should accept empty response', function () { + describe('As client', function () { + it('accepts a response with no parameters', function () { const perMessageDeflate = new PerMessageDeflate({}); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('should accept response parameter', function () { + it('accepts a response with parameters', function () { const perMessageDeflate = new PerMessageDeflate({}); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + @@ -142,112 +222,83 @@ describe('PerMessageDeflate', function () { }); }); - it('should throw an error if client_no_context_takeover is unsupported', function () { + it('throws an error if client_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); const extensions = extension.parse('permessage-deflate; client_no_context_takeover'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unexpected parameter "client_no_context_takeover"$/ + ); }); - it('should throw an error if client_max_window_bits is unsupported', function () { + it('throws an error if client_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); const extensions = extension.parse('permessage-deflate; client_max_window_bits=10'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unexpected or invalid parameter "client_max_window_bits"$/ + ); }); - it('should throw an error if client_max_window_bits is greater than configuration', function () { + it('throws an error if client_max_window_bits is greater than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); const extensions = extension.parse('permessage-deflate; client_max_window_bits=11'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('uses the config value if client_max_window_bits is not specified', function () { - const perMessageDeflate = new PerMessageDeflate({ - clientMaxWindowBits: 10 - }); - - assert.deepStrictEqual(perMessageDeflate.accept([{}]), { - client_max_window_bits: 10 - }); - }); - }); - - describe('validate parameters', function () { - it('should throw an error if a parameter has multiple values', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse( - 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unexpected or invalid parameter "client_max_window_bits"$/ ); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); - it('should throw an error if server_no_context_takeover has a value', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate; server_no_context_takeover=10'); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('should throw an error if client_no_context_takeover has a value', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate; client_no_context_takeover=10'); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('should throw an error if server_max_window_bits has an invalid value', function () { - const perMessageDeflate = new PerMessageDeflate({}, true); - - let extensions = extension.parse('permessage-deflate; server_max_window_bits=7'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - - extensions = extension.parse('permessage-deflate; server_max_window_bits'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('should throw an error if client_max_window_bits has an invalid value', function () { + it('throws an error if client_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate(); let extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ + ); extensions = extension.parse('permessage-deflate; client_max_window_bits'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('throws an error if a parameter has an invalid name', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate;foo'); - assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), - /^Error: Unknown parameter "foo"$/ + /^TypeError: Invalid value for parameter "client_max_window_bits": true$/ ); }); + + it('uses the config value if client_max_window_bits is not specified', function () { + const perMessageDeflate = new PerMessageDeflate({ + clientMaxWindowBits: 10 + }); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), { + client_max_window_bits: 10 + }); + }); }); }); - describe('#compress/#decompress', function () { - it('should compress/decompress data', function (done) { + describe('#compress and #decompress', function () { + it('works with unfragmented messages', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const buf = Buffer.from([1, 2, 3]); perMessageDeflate.accept([{}]); - perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, (err, data) => { + perMessageDeflate.decompress(data, true, (err, data) => { if (err) return done(err); - assert.ok(data.equals(Buffer.from([1, 2, 3]))); + assert.ok(data.equals(buf)); done(); }); }); }); - it('should compress/decompress fragments', function (done) { + it('works with fragmented messages', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const buf = Buffer.from([1, 2, 3, 4]); @@ -265,7 +316,7 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(compressed2, true, (err, data2) => { if (err) return done(err); - assert.ok(Buffer.concat([data1, data2]).equals(Buffer.from([1, 2, 3, 4]))); + assert.ok(Buffer.concat([data1, data2]).equals(buf)); done(); }); }); @@ -273,7 +324,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should compress/decompress data with parameters', function (done) { + it('works with the negotiated parameters', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0, memLevel: 5, @@ -284,62 +335,54 @@ describe('PerMessageDeflate', function () { 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' ); - const srcData = 'Some compressible data, it\'s compressible.'; + const buf = Buffer.from("Some compressible data, it's compressible."); perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(Buffer.from(srcData, 'utf8'), true, (err, compressed) => { + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, (err, data) => { + perMessageDeflate.decompress(data, true, (err, data) => { if (err) return done(err); - assert.ok(data.equals(Buffer.from(srcData, 'utf8'))); + assert.ok(data.equals(buf)); done(); }); }); }); - it('should compress/decompress with level parameter', function (done) { - const perMessageDeflateLev9 = new PerMessageDeflate({ - threshold: 0, - level: 9 - }); - const perMessageDeflateLev0 = new PerMessageDeflate({ - threshold: 0, - level: 0 - }); + it('honors the `level` option', function (done) { + const lev9 = new PerMessageDeflate({ threshold: 0, level: 9 }); + const lev0 = new PerMessageDeflate({ threshold: 0, level: 0 }); const extensionStr = ( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' ); - const srcData = 'Some compressible data, it\'s compressible.'; - const srcDataBuffer = Buffer.from(srcData, 'utf8'); + const buf = Buffer.from("Some compressible data, it's compressible."); - perMessageDeflateLev0.accept(extension.parse(extensionStr)['permessage-deflate']); - perMessageDeflateLev9.accept(extension.parse(extensionStr)['permessage-deflate']); + lev0.accept(extension.parse(extensionStr)['permessage-deflate']); + lev9.accept(extension.parse(extensionStr)['permessage-deflate']); - perMessageDeflateLev0.compress(srcDataBuffer, true, (err, compressed1) => { + lev0.compress(buf, true, (err, compressed1) => { if (err) return done(err); - perMessageDeflateLev0.decompress(compressed1, true, (err, data1) => { + lev0.decompress(compressed1, true, (err, decompressed1) => { if (err) return done(err); - perMessageDeflateLev9.compress(srcDataBuffer, true, (err, compressed2) => { + lev9.compress(buf, true, (err, compressed2) => { if (err) return done(err); - perMessageDeflateLev9.decompress(compressed2, true, (err, data2) => { + lev9.decompress(compressed2, true, (err, decompressed2) => { if (err) return done(err); - // Level 0 compression actually adds a few bytes due to headers - assert.ok(compressed1.length > srcDataBuffer.length); + // Level 0 compression actually adds a few bytes due to headers. + assert.ok(compressed1.length > buf.length); // Level 9 should not, of course. - assert.ok(compressed2.length < compressed1.length); - assert.ok(compressed2.length < srcDataBuffer.length); + assert.ok(compressed2.length < buf.length); // Ensure they both decompress back properly. - assert.ok(data1.equals(srcDataBuffer)); - assert.ok(data2.equals(srcDataBuffer)); + assert.ok(decompressed1.equals(buf)); + assert.ok(decompressed2.equals(buf)); done(); }); }); @@ -347,10 +390,10 @@ describe('PerMessageDeflate', function () { }); }); - it('should compress/decompress data with no context takeover', function (done) { - const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + it("doesn't use contex takeover if not allowed", function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse( - 'permessage-deflate; server_no_context_takeover; client_no_context_takeover' + 'permessage-deflate;server_no_context_takeover' ); const buf = Buffer.from('foofoo'); @@ -362,13 +405,14 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); + assert.ok(data.equals(buf)); perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); + assert.strictEqual(compressed2.length, compressed1.length); perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); - assert.strictEqual(compressed2.length, compressed1.length); assert.ok(data.equals(buf)); done(); }); @@ -377,8 +421,8 @@ describe('PerMessageDeflate', function () { }); }); - it('should compress data between contexts when allowed', function (done) { - const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + it('uses contex takeover if allowed', function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse('permessage-deflate'); const buf = Buffer.from('foofoo'); @@ -390,13 +434,14 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); + assert.ok(data.equals(buf)); perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); + assert.ok(compressed2.length < compressed1.length); perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); - assert.ok(compressed2.length < compressed1.length); assert.ok(data.equals(buf)); done(); }); @@ -405,7 +450,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should call the callback when an error occurs (inflate)', function (done) { + it('calls the callback when an error occurs (inflate)', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const data = Buffer.from('something invalid'); @@ -417,7 +462,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should not call the callback twice when `maxPayload` is exceeded', function (done) { + it("doesn't call the callback twice when `maxPayload` is exceeded", function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, false, 25); const buf = Buffer.from('A'.repeat(50)); const errors = []; diff --git a/test/validation.test.js b/test/validation.test.js deleted file mode 100644 index d7dcad28e..000000000 --- a/test/validation.test.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -const safeBuffer = require('safe-buffer'); -const assert = require('assert'); - -const validation = require('../lib/validation'); - -const Buffer = safeBuffer.Buffer; - -describe('validation', function () { - describe('isValidUTF8', function () { - it('should return true for a valid utf8 string', function () { - const validBuffer = Buffer.from( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + - 'Quisque gravida mattis rhoncus. Donec iaculis, metus ' + - 'quis varius accumsan, erat mauris condimentum diam, et ' + - 'egestas erat enim ut ligula. Praesent sollicitudin tellus ' + - 'eget dolor euismod euismod. Nullam ac augue nec neque ' + - 'varius luctus. Curabitur elit mi, consequat ultricies ' + - 'adipiscing mollis, scelerisque in erat. Phasellus facilisis ' + - ' fermentum ullamcorper. Nulla et sem eu arcu pharetra ' + - 'pellentesque. Praesent consectetur tempor justo, vel ' + - 'iaculis dui ullamcorper sit amet. Integer tristique viverra ' + - 'ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, ' + - 'lacus lectus feugiat libero, non fermentum erat nisi at ' + - 'risus. Lorem ipsum dolor sit amet, consectetur adipiscing ' + - 'elit. Ut pulvinar dignissim tellus, eu dignissim lorem ' + - 'vulputate quis. Morbi ut pulvinar augue.' - ); - - assert.ok(validation.isValidUTF8(validBuffer)); - }); - - it('should return false for an erroneous string', function () { - const invalidBuffer = Buffer.from([ - 0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, - 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, - 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 - ]); - - assert.ok(!validation.isValidUTF8(invalidBuffer)); - }); - - it('should return true for valid cases from the autobahn test suite', function () { - assert.ok(validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); - assert.ok(validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); - }); - - it('should return false for erroneous autobahn strings', function () { - assert.ok(!validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); - }); - }); -}); diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index ff1b367bc..4ba93675f 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -24,6 +24,36 @@ describe('WebSocketServer', function () { assert.throws(() => new WebSocket.Server({})); }); + describe('options', function () { + it('exposes options passed to constructor', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + assert.strictEqual(wss.options.port, 0); + wss.close(done); + }); + }); + + it('accepts the `maxPayload` option', function (done) { + const maxPayload = 20480; + const wss = new WebSocket.Server({ + perMessageDeflate: true, + maxPayload, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (ws) => { + assert.strictEqual(ws._receiver._maxPayload, maxPayload); + assert.strictEqual( + ws._receiver._extensions['permessage-deflate']._maxPayload, + maxPayload + ); + wss.close(done); + }); + }); + }); + it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocket.Server({ port: 0 }, () => { const wss2 = new WebSocket.Server({ @@ -119,6 +149,7 @@ describe('WebSocketServer', function () { }); it('closes all clients', function (done) { + let closes = 0; const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -126,7 +157,7 @@ describe('WebSocketServer', function () { if (++closes === 2) done(); }); }); - let closes = 0; + wss.on('connection', (ws) => { ws.on('close', () => { if (++closes === 2) done(); @@ -135,12 +166,12 @@ describe('WebSocketServer', function () { }); }); - it('does not close a precreated server', function (done) { + it("doesn't close a precreated server", function (done) { const server = http.createServer(); const realClose = server.close; server.close = () => { - throw new Error('must not close pre-created server'); + done(new Error('Must not close pre-created server')); }; const wss = new WebSocket.Server({ server }); @@ -168,9 +199,9 @@ describe('WebSocketServer', function () { server.listen(0, () => { wss.close(() => { - assert.strictEqual(server.listeners('listening').length, 0); - assert.strictEqual(server.listeners('upgrade').length, 0); - assert.strictEqual(server.listeners('error').length, 0); + assert.strictEqual(server.listenerCount('listening'), 0); + assert.strictEqual(server.listenerCount('upgrade'), 0); + assert.strictEqual(server.listenerCount('error'), 0); server.close(done); }); @@ -240,50 +271,6 @@ describe('WebSocketServer', function () { }); }); - describe('#options', function () { - it('exposes options passed to constructor', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - assert.strictEqual(wss.options.port, 0); - wss.close(done); - }); - }); - }); - - describe('#maxpayload', function () { - it('maxpayload is passed on to hybi receivers', function (done) { - const maxPayload = 20480; - const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client._receiver._maxPayload, maxPayload); - wss.close(done); - }); - }); - - it('maxpayload is passed on to permessage-deflate', function (done) { - const maxPayload = 20480; - const wss = new WebSocket.Server({ - perMessageDeflate: true, - maxPayload, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual( - client._receiver._extensions['permessage-deflate']._maxPayload, - maxPayload - ); - wss.close(done); - }); - }); - }); - describe('#shouldHandle', function () { it('returns true when the path matches', function () { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); @@ -291,7 +278,7 @@ describe('WebSocketServer', function () { assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); }); - it('returns false when the path does not match', function () { + it("returns false when the path doesn't match", function () { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); @@ -319,7 +306,7 @@ describe('WebSocketServer', function () { }); }); - it('closes the connection when path does not match', function (done) { + it("closes the connection when path doesn't match", function (done) { const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { const req = http.get({ port: wss._server.address().port, @@ -357,8 +344,8 @@ describe('WebSocketServer', function () { }); }); - describe('connection establishing', function () { - it('does not accept connections with no sec-websocket-key', function (done) { + describe('Connection establishing', function () { + it('fails if the Sec-WebSocket-Key header is invalid', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss._server.address().port, @@ -375,11 +362,11 @@ describe('WebSocketServer', function () { }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('does not accept connections with no sec-websocket-version', function (done) { + it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss._server.address().port, @@ -397,11 +384,11 @@ describe('WebSocketServer', function () { }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('does not accept connections with invalid sec-websocket-version', function (done) { + it('fails is the Sec-WebSocket-Version header is invalid (2/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss._server.address().port, @@ -420,13 +407,13 @@ describe('WebSocketServer', function () { }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('client can be denied', function (done) { + it('fails is the Sec-WebSocket-Extensions header is invalid', function (done) { const wss = new WebSocket.Server({ - verifyClient: (o) => false, + perMessageDeflate: true, port: 0 }, () => { const req = http.get({ @@ -435,221 +422,151 @@ describe('WebSocketServer', function () { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Extensions': + 'permessage-deflate; server_max_window_bits=foo' } }); req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); + assert.strictEqual(res.statusCode, 400); wss.close(done); }); }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('client can be accepted', function (done) { - const wss = new WebSocket.Server({ - port: 0, - verifyClient: (o) => true - }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } - }); - }); - - wss.on('connection', (ws) => wss.close(done)); - }); + describe('`verifyClient`', function () { + it('can reject client synchronously', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info) => false, + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); - it('verifyClient gets client origin', function (done) { - let verifyClientCalled = false; - const wss = new WebSocket.Server({ - verifyClient: (info) => { - assert.strictEqual(info.origin, 'http://foobarbaz.com'); - verifyClientCalled = true; - return false; - }, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - } + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); }); - req.on('response', (res) => { - assert.ok(verifyClientCalled); - wss.close(done); + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); }); }); - }); - it('verifyClient gets original request', function (done) { - let verifyClientCalled = false; - const wss = new WebSocket.Server({ - verifyClient: (info) => { - assert.strictEqual( - info.req.headers['sec-websocket-key'], - 'dGhlIHNhbXBsZSBub25jZQ==' - ); - verifyClientCalled = true; - return false; - }, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } + it('can accept client synchronously', function (done) { + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') }); - req.on('response', (res) => { - assert.ok(verifyClientCalled); - wss.close(done); + const wss = new WebSocket.Server({ + verifyClient: (info) => { + assert.strictEqual(info.origin, 'https://example.com'); + assert.strictEqual( + info.req.headers['sec-websocket-key'], + 'dGhlIHNhbXBsZSBub25jZQ==' + ); + assert.ok(info.secure, true); + return true; + }, + server }); - }); - }); - it('verifyClient has secure:true for ssl connections', function (done) { - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - key: fs.readFileSync('test/fixtures/key.pem') - }); - - let success = false; - const wss = new WebSocket.Server({ - verifyClient: (info) => { - success = info.secure === true; - return true; - }, - server - }); - - wss.on('connection', (ws) => { - assert.ok(success); - wss.close(); - server.close(done); - }); + wss.on('connection', (ws) => { + wss.close(); + server.close(done); + }); - server.listen(0, () => { - const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - rejectUnauthorized: false + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + headers: { + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + Origin: 'https://example.com' + }, + rejectUnauthorized: false + }); }); }); - }); - it('verifyClient has secure:false for non-ssl connections', function (done) { - const server = http.createServer(); - - let success = false; - const wss = new WebSocket.Server({ - server: server, - verifyClient: (info) => { - success = info.secure === false; - return true; - } - }); + it('can accept client asynchronously', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (o, cb) => process.nextTick(cb, true), + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + }); - wss.on('connection', (ws) => { - assert.ok(success); - wss.close(); - server.close(done); + wss.on('connection', (ws) => wss.close(done)); }); - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`); - }); - }); + it('can reject client asynchronously', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info, cb) => process.nextTick(cb, false), + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); - it('client can be denied asynchronously', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (o, cb) => process.nextTick(cb, false), - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 - } + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(done); + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); }); }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); - }); - - it('client can be denied asynchronously with custom response code', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (o, cb) => process.nextTick(cb, false, 404), - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 - } - }); + it('can reject client asynchronously with status code', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info, cb) => process.nextTick(cb, false, 404), + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 404); - wss.close(done); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 404); + wss.close(done); + }); }); - }); - - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); - }); - it('client can be accepted asynchronously', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (o, cb) => process.nextTick(cb, true), - port: 0 - }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); }); }); - - wss.on('connection', (ws) => wss.close(done)); }); - it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) { + it("doesn't emit the 'connection' event if socket is closed prematurely", function (done) { const server = http.createServer(); server.listen(0, () => { @@ -659,10 +576,13 @@ describe('WebSocketServer', function () { }); wss.on('connection', () => { - throw new Error('connection event emitted'); + done(new Error("Unexpected 'connection' event")); }); - const socket = net.connect({ port: server.address().port }, () => { + const socket = net.connect({ + port: server.address().port, + allowHalfOpen: true + }, () => { socket.write([ 'GET / HTTP/1.1', 'Host: localhost', @@ -670,8 +590,7 @@ describe('WebSocketServer', function () { 'Connection: Upgrade', 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version: 13', - '', - '' + '\r\n' ].join('\r\n')); }); @@ -684,7 +603,7 @@ describe('WebSocketServer', function () { }); }); - it('handles messages passed along with the upgrade request (upgrade head)', function (done) { + it('handles data passed along with the upgrade request', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ port: wss._server.address().port, @@ -708,98 +627,45 @@ describe('WebSocketServer', function () { }); }); - it('selects the first protocol by default', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => { - assert.strictEqual(ws.protocol, 'prot1'); - wss.close(done); - }); - }); - }); - - it('selects the last protocol via protocol handler', function (done) { - const handleProtocols = (protocols, request) => { - assert.ok(request instanceof http.IncomingMessage); - assert.strictEqual(request.url, '/'); - return protocols.pop(); - }; - const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => { - assert.strictEqual(ws.protocol, 'prot2'); - wss.close(done); + describe('`handleProtocols`', function () { + it('can select the last protocol', function (done) { + const handleProtocols = (protocols, request) => { + assert.ok(request instanceof http.IncomingMessage); + assert.strictEqual(request.url, '/'); + return protocols.pop(); + }; + const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, ['foo', 'bar']); + + ws.on('open', () => { + assert.strictEqual(ws.protocol, 'bar'); + wss.close(done); + }); }); }); - }); - - it('client detects invalid server protocol', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (ps) => 'prot3', - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => wss.close(done)); - }); - }); - - it('client detects no server protocol', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (ps) => {}, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => wss.close(done)); - }); - }); - it('server detects unauthorized protocol handler', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (ps) => false, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(done); - }); - }); - }); + it('closes the connection if return value is `false`', function (done) { + const wss = new WebSocket.Server({ + handleProtocols: (protocols) => false, + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13 + } + }); - it('accept connections with sec-websocket-extensions', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' - } + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); }); }); - - wss.on('connection', (ws) => wss.close(done)); }); it('emits the `headers` event', function (done) { @@ -822,127 +688,40 @@ describe('WebSocketServer', function () { }); }); - describe('messaging', function () { - it('can send and receive data', function (done) { - let data = new Array(65 * 1024); - - for (let i = 0; i < data.length; ++i) { - data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); - } - data = data.join(''); - + describe('permessage-deflate', function () { + it('is disabled by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('message', (message) => ws.send(message)); - }); - - wss.on('connection', (client) => { - client.on('message', (message) => { - assert.strictEqual(message, data); - wss.close(done); - }); - - client.send(data); - }); - }); - - it('does not crash when it receives an unhandled opcode', function (done) { - let closed = false; - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}/`); - - ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1006); - assert.strictEqual(reason, ''); - assert.ok(closed); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof RangeError); - assert.strictEqual( - err.message, - 'Invalid WebSocket frame: invalid opcode 5' - ); - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1002); - assert.strictEqual(reason, ''); - closed = true; - }); - }); - }); - }); - }); - - describe('client properties', function () { - it('protocol is exposed', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, 'hi'); }); - wss.on('connection', (client) => { - assert.strictEqual(client.protocol, 'hi'); + wss.on('connection', (ws, req) => { + assert.strictEqual( + req.headers['sec-websocket-extensions'], + 'permessage-deflate; client_max_window_bits' + ); + assert.strictEqual(ws.extensions, ''); wss.close(done); }); }); - }); - describe('permessage-deflate', function () { - it('accept connections with permessage-deflate extension', function (done) { + it('uses configuration options', function (done) { const wss = new WebSocket.Server({ - perMessageDeflate: true, + perMessageDeflate: { clientMaxWindowBits: 8 }, port: 0 }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; ' + - 'client_max_window_bits=8; server_max_window_bits=8; ' + - 'client_no_context_takeover; server_no_context_takeover' - } - }); - }); - - wss.on('connection', (ws) => wss.close(done)); - }); + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); - it('does not accept connections with invalid extension parameters', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; server_max_window_bits=foo' - } - }); + ws.on('headers', (headers) => { + assert.strictEqual( + headers['sec-websocket-extensions'], + 'permessage-deflate; client_max_window_bits=8' + ); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 400); wss.close(done); }); }); - - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); }); }); }); diff --git a/test/websocket.test.js b/test/websocket.test.js index 2b9836275..397cb153b 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -29,124 +29,165 @@ describe('WebSocket', function () { /^Error: Invalid URL: echo\.websocket\.org$/ ); }); - }); - describe('options', function () { - it('accepts an `agent` option', function (done) { - const agent = new CustomAgent(); + describe('options', function () { + it('accepts an `agent` option', function (done) { + const agent = new CustomAgent(); - agent.addRequest = () => { - done(); - }; + agent.addRequest = () => { + done(); + }; - const ws = new WebSocket('ws://localhost', { agent }); - }); + const ws = new WebSocket('ws://localhost', { agent }); + }); - it('accepts the `options` object as the 3rd argument', function () { - const agent = new CustomAgent(); - let count = 0; - let ws; + it('accepts the `options` object as 3rd argument', function () { + const agent = new CustomAgent(); + let count = 0; + let ws; - agent.addRequest = (req) => count++; + agent.addRequest = (req) => count++; - ws = new WebSocket('ws://localhost', undefined, { agent }); - ws = new WebSocket('ws://localhost', null, { agent }); - ws = new WebSocket('ws://localhost', [], { agent }); + ws = new WebSocket('ws://localhost', undefined, { agent }); + ws = new WebSocket('ws://localhost', null, { agent }); + ws = new WebSocket('ws://localhost', [], { agent }); - assert.strictEqual(count, 3); - }); + assert.strictEqual(count, 3); + }); - it('throws an error when using an invalid `protocolVersion`', function () { - const options = { agent: new CustomAgent(), protocolVersion: 1000 }; + it('throws an error when using an invalid `protocolVersion`', function () { + const options = { agent: new CustomAgent(), protocolVersion: 1000 }; - assert.throws( - () => new WebSocket('ws://localhost', options), - /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ - ); - }); + assert.throws( + () => new WebSocket('ws://localhost', options), + /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ + ); + }); - it('accepts the localAddress option', function (done) { - const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - localAddress: '127.0.0.2' - }); + it('accepts the `localAddress` option', function (done) { + const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { + localAddress: '127.0.0.2' + }); - ws.on('error', (err) => { - wss.close(() => { - // - // Skip this test on machines where 127.0.0.2 is disabled. - // - if (err.code === 'EADDRNOTAVAIL') return this.skip(); + ws.on('error', (err) => { + wss.close(() => { + // + // Skip this test on machines where 127.0.0.2 is disabled. + // + if (err.code === 'EADDRNOTAVAIL') return this.skip(); - done(err); + done(err); + }); }); }); - }); - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); - wss.close(done); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); + wss.close(done); + }); }); - }); - it('accepts the localAddress option whether it was wrong interface', function () { - const localAddress = '123.456.789.428'; + it('accepts the `family` option', function (done) { + const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; + const ifaces = os.networkInterfaces(); + const hasIPv6 = Object.keys(ifaces).some((name) => { + return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); + }); - assert.throws(() => { - const ws = new WebSocket('ws://localhost', { localAddress }); - }, (err) => { - return err instanceof TypeError && (err.code === 'ERR_INVALID_IP_ADDRESS' || - err.message.includes(`must be a valid IP: ${localAddress}`)); - }); - }); + // + // Skip this test on machines where IPv6 is not supported. + // + if (!hasIPv6) return this.skip(); + + dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { + // + // Skip this test if localhost does not resolve to ::1. + // + if (err) { + return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' + ? this.skip() + : done(err); + } - it('accepts the family option', function (done) { - const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; - const ifaces = os.networkInterfaces(); - const hasIPv6 = Object.keys(ifaces).some((name) => { - return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); - }); + if (!addresses.some((val) => val.address === '::1')) return this.skip(); - // - // Skip this test on machines where IPv6 is not supported. - // - if (!hasIPv6) return this.skip(); + const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + }); - dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { - // - // Skip this test if localhost does not resolve to ::1. - // - if (err) { - return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' - ? this.skip() - : done(err); - } + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '::1'); + wss.close(done); + }); + }); + }); + }); + }); + + describe('Constants', function () { + const readyStates = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3 + }; - if (!addresses.some((val) => val.address === '::1')) return this.skip(); + Object.keys(readyStates).forEach((state) => { + describe(`\`${state}\``, function () { + it('is enumerable property of class', function () { + const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); - const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + assert.strictEqual(propertyDescripter.value, readyStates[state]); + assert.strictEqual(propertyDescripter.enumerable, true); }); - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '::1'); - wss.close(done); + it('is property of instance', function () { + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); + + assert.strictEqual(ws[state], readyStates[state]); }); }); }); }); - describe('properties', function () { - it('#url exposes the server url', function () { - const url = 'ws://localhost'; - const ws = new WebSocket(url, { agent: new CustomAgent() }); + describe('Attributes', function () { + describe('`binaryType`', function () { + it("defaults to 'nodebuffer'", function () { + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); + + assert.strictEqual(ws.binaryType, 'nodebuffer'); + }); - assert.strictEqual(ws.url, url); + it("can be changed to 'arraybuffer' or 'fragments'", function () { + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); + + ws.binaryType = 'arraybuffer'; + assert.strictEqual(ws.binaryType, 'arraybuffer'); + + ws.binaryType = 'foo'; + assert.strictEqual(ws.binaryType, 'arraybuffer'); + + ws.binaryType = 'fragments'; + assert.strictEqual(ws.binaryType, 'fragments'); + + ws.binaryType = ''; + assert.strictEqual(ws.binaryType, 'fragments'); + + ws.binaryType = 'nodebuffer'; + assert.strictEqual(ws.binaryType, 'nodebuffer'); + }); }); - describe('#bufferedAmount', function () { + describe('`bufferedAmount`', function () { it('defaults to zero', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() @@ -209,48 +250,72 @@ describe('WebSocket', function () { }); }); - describe('Custom headers', function () { - const server = http.createServer(); + describe('`extensions`', function () { + it('exposes the negotiated extensions names (1/2)', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); - beforeEach((done) => server.listen(0, done)); - afterEach((done) => server.close(done)); + assert.strictEqual(ws.extensions, ''); - it('request has an authorization header', function (done) { - const wss = new WebSocket.Server({ server }); - const auth = 'test:testpass'; + ws.on('open', () => { + assert.strictEqual(ws.extensions, ''); + ws.on('close', () => wss.close(done)); + }); + }); - server.once('upgrade', (req, socket, head) => { - assert.ok(req.headers.authorization); - assert.strictEqual( - req.headers.authorization, - `Basic ${Buffer.from(auth).toString('base64')}` - ); + wss.on('connection', (ws) => { + assert.strictEqual(ws.extensions, ''); + ws.close(); + }); + }); - wss.close(done); + it('exposes the negotiated extensions names (2/2)', function (done) { + const wss = new WebSocket.Server({ + perMessageDeflate: true, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.extensions, ''); + + ws.on('open', () => { + assert.strictEqual(ws.extensions, 'permessage-deflate'); + ws.on('close', () => wss.close(done)); + }); }); - const port = server.address().port; - const ws = new WebSocket(`ws://${auth}@localhost:${port}`); + wss.on('connection', (ws) => { + assert.strictEqual(ws.extensions, 'permessage-deflate'); + ws.close(); + }); }); + }); - it('accepts custom headers', function (done) { - const wss = new WebSocket.Server({ server }); + describe('`protocol`', function () { + it('exposes the subprotocol selected by the server', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, 'foo'); - server.once('upgrade', (req, socket, head) => { - assert.ok(req.headers.cookie); - assert.strictEqual(req.headers.cookie, 'foo=bar'); + assert.strictEqual(ws.extensions, ''); - wss.close(done); + ws.on('open', () => { + assert.strictEqual(ws.protocol, 'foo'); + ws.on('close', () => wss.close(done)); + }); }); - const ws = new WebSocket(`ws://localhost:${server.address().port}`, { - headers: { 'Cookie': 'foo=bar' } + wss.on('connection', (ws) => { + assert.strictEqual(ws.protocol, 'foo'); + ws.close(); }); }); }); - describe('#readyState', function () { - it('defaults to connecting', function () { + describe('`readyState`', function () { + it('defaults to `CONNECTING`', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -258,7 +323,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.readyState, WebSocket.CONNECTING); }); - it('set to open once connection is established', function (done) { + it('is set to `OPEN` once connection is established', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -272,7 +337,7 @@ describe('WebSocket', function () { }); }); - it('set to closed once connection is closed', function (done) { + it('is set to `CLOSED` once connection is closed', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -286,7 +351,7 @@ describe('WebSocket', function () { }); }); - it('set to closed once connection is terminated', function (done) { + it('is set to `CLOSED` once connection is terminated', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -301,73 +366,81 @@ describe('WebSocket', function () { }); }); - const readyStates = { - CONNECTING: 0, - OPEN: 1, - CLOSING: 2, - CLOSED: 3 - }; + describe('`url`', function () { + it('exposes the server url', function () { + const url = 'ws://localhost'; + const ws = new WebSocket(url, { agent: new CustomAgent() }); - Object.keys(readyStates).forEach((state) => { - describe(`.${state}`, function () { - it('is enumerable property of class', function () { - const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); + assert.strictEqual(ws.url, url); + }); + }); + }); - assert.strictEqual(propertyDescripter.value, readyStates[state]); - assert.strictEqual(propertyDescripter.enumerable, true); - }); + describe('Events', function () { + it("emits an 'error' event if an error occurs", function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); - it('is property of instance', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() - }); + ws.on('error', (err) => { + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 5' + ); - assert.strictEqual(ws[state], readyStates[state]); + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1002); + assert.strictEqual(reason, ''); + wss.close(done); + }); }); }); + + wss.on('connection', (ws) => { + ws._socket.write(Buffer.from([0x85, 0x00])); + }); }); - }); - describe('events', function () { - it('emits a ping event', function (done) { + it("emits a 'headers' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('ping', () => wss.close(done)); + ws.on('headers', (headers, res) => { + assert.strictEqual(headers, res.headers); + wss.close(done); + }); }); - - wss.on('connection', (client) => client.ping()); }); - it('emits a pong event', function (done) { + it("emits a 'ping' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('pong', () => wss.close(done)); + ws.on('ping', () => wss.close(done)); }); - wss.on('connection', (client) => client.pong()); + wss.on('connection', (ws) => ws.ping()); }); - it('emits a headers event', function (done) { + it("emits a 'pong' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('headers', (headers, res) => { - assert.strictEqual(headers, res.headers); - wss.close(done); - }); + ws.on('pong', () => wss.close(done)); }); + + wss.on('connection', (ws) => ws.pong()); }); }); - describe('connection establishing', function () { + describe('Connection establishing', function () { const server = http.createServer(); beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); - it('invalid server key is denied', function (done) { + it('fails if the Sec-WebSocket-Accept header is invalid', function (done) { server.once('upgrade', (req, socket) => { socket.on('end', socket.end); socket.write( @@ -425,7 +498,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'Unexpected server response: 401'); @@ -447,8 +520,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); - ws.on('error', () => done(new Error("unexpected 'error' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', () => done(new Error("Unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -479,8 +552,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); - ws.on('error', () => done(new Error("unexpected 'error' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', () => done(new Error("Unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -497,7 +570,7 @@ describe('WebSocket', function () { handshakeTimeout: 100 }); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'Opening handshake has timed out'); @@ -523,7 +596,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'Invalid Sec-WebSocket-Extensions header'); @@ -532,36 +605,64 @@ describe('WebSocket', function () { }); it('fails if server sends a subprotocol when none was requested', function (done) { - server.once('upgrade', (req, socket) => { - const key = crypto.createHash('sha1') - .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') - .digest('base64'); - - socket.end( - 'HTTP/1.1 101 Switching Protocols\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - `Sec-WebSocket-Accept: ${key}\r\n` + - 'Sec-WebSocket-Protocol: foo\r\n' + - '\r\n' - ); + const wss = new WebSocket.Server({ + handleProtocols: () => 'foo', + server }); const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, 'Server sent a subprotocol but none was requested' ); - ws.on('close', () => done()); + ws.on('close', () => wss.close(done)); + }); + }); + + it('fails if server sends an invalid subprotocol', function (done) { + const wss = new WebSocket.Server({ + handleProtocols: () => 'baz', + server + }); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`, [ + 'foo', + 'bar' + ]); + + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Server sent an invalid subprotocol'); + ws.on('close', () => wss.close(done)); + }); + }); + + it('fails if server sends no subprotocol', function (done) { + const wss = new WebSocket.Server({ + handleProtocols: () => {}, + server + }); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`, [ + 'foo', + 'bar' + ]); + + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Server sent no subprotocol'); + ws.on('close', () => wss.close(done)); }); }); }); - describe('connection with query string', function () { + describe('Connection with query string', function () { it('connects when pathname is not null', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; @@ -797,7 +898,7 @@ describe('WebSocket', function () { }); describe('#send', function () { - it('very long binary data can be sent and received', function (done) { + it('can send a big binary message', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5 * 1024 * 1024); @@ -820,7 +921,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive text data', function (done) { + it('can send text data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -917,7 +1018,7 @@ describe('WebSocket', function () { }); }); - it('ArrayBuffer is auto-detected without binary flag', function (done) { + it('can send an `ArrayBuffer`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5); @@ -940,7 +1041,7 @@ describe('WebSocket', function () { }); }); - it('Buffer is auto-detected without binary flag', function (done) { + it('can send a `Buffer`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); const port = wss._server.address().port; @@ -959,7 +1060,7 @@ describe('WebSocket', function () { }); }); - it('before connect should fail', function () { + it('throws an error if `readyState` is not `OPEN`', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -970,7 +1071,7 @@ describe('WebSocket', function () { ); }); - it('before connect should pass error through callback, if present', function () { + it('passes errors to the callback, if present', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -984,23 +1085,7 @@ describe('WebSocket', function () { }); }); - it('without data should be successful', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => ws.send()); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message) => { - assert.ok(message.equals(Buffer.alloc(0))); - wss.close(done); - }); - }); - }); - - it('calls optional callback when flushed', function (done) { + it('calls the optional callback when data is written out', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1014,28 +1099,28 @@ describe('WebSocket', function () { }); }); - it('with unmasked message is successfully transmitted to the server', function (done) { + it('works when the `data` argument is falsy', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send('hi', { mask: false })); + ws.on('open', () => ws.send()); }); wss.on('connection', (ws) => { ws.on('message', (message) => { - assert.strictEqual(message, 'hi'); + assert.ok(message.equals(Buffer.alloc(0))); wss.close(done); }); }); }); - it('with masked message is successfully transmitted to the server', function (done) { + it('can send text data with `mask` option set to `false`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send('hi', { mask: true })); + ws.on('open', () => ws.send('hi', { mask: false })); }); wss.on('connection', (ws) => { @@ -1046,7 +1131,7 @@ describe('WebSocket', function () { }); }); - it('with unmasked binary message is successfully transmitted to the server', function (done) { + it('can send binary data with `mask` option set to `false`', function (done) { const array = new Float32Array(5); for (let i = 0; i < array.length; ++i) { @@ -1057,29 +1142,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send(array, { mask: false, binary: true })); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message) => { - assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(done); - }); - }); - }); - - it('with masked binary message is successfully transmitted to the server', function (done) { - const array = new Float32Array(5); - - for (let i = 0; i < array.length; ++i) { - array[i] = i / 2; - } - - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => ws.send(array, { mask: true, binary: true })); + ws.on('open', () => ws.send(array, { mask: false })); }); wss.on('connection', (ws) => { @@ -1097,7 +1160,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1118,7 +1181,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1134,7 +1197,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket('ws://localhost:1337'); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1148,7 +1211,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1323,9 +1386,11 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.send('foo'); - ws.send('bar'); - ws.send('baz'); + const callback = (err) => assert.ifError(err); + + ws.send('foo', callback); + ws.send('bar', callback); + ws.send('baz', callback); ws.close(); ws.close(); }); @@ -1345,7 +1410,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1013)); }); - it('does nothing if the connection is already CLOSED', function (done) { + it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1368,7 +1433,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1389,7 +1454,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1405,7 +1470,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket('ws://localhost:1337'); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1419,7 +1484,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1432,7 +1497,7 @@ describe('WebSocket', function () { }); }); - it('does nothing if the connection is already CLOSED', function (done) { + it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1450,7 +1515,7 @@ describe('WebSocket', function () { }); describe('WHATWG API emulation', function () { - it('should not throw errors when getting and setting', function () { + it('supports the `on{close,error,message,open}` attributes', function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1464,65 +1529,29 @@ describe('WebSocket', function () { ws.onclose = listener; ws.onopen = listener; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'arraybuffer'; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - assert.strictEqual(ws.onmessage, listener); assert.strictEqual(ws.onclose, listener); assert.strictEqual(ws.onerror, listener); assert.strictEqual(ws.onopen, listener); }); - it('should ignore when setting an invalid binary type', function () { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'foo'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'arraybuffer'; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = ''; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = 'fragments'; - assert.strictEqual(ws.binaryType, 'fragments'); - ws.binaryType = 'buffer'; - assert.strictEqual(ws.binaryType, 'fragments'); - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - }); - - it('should work the same as the EventEmitter api', function (done) { - const wss = new WebSocket.Server({ - clientTracking: false, - port: 0 - }, () => { + it('works like the `EventEmitter` interface', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - let message = 0; - let close = 0; - let open = 0; ws.onmessage = (messageEvent) => { assert.strictEqual(messageEvent.data, 'foo'); - ++message; + ws.onclose = (closeEvent) => { + assert.strictEqual(closeEvent.wasClean, true); + assert.strictEqual(closeEvent.code, 1005); + assert.strictEqual(closeEvent.reason, ''); + wss.close(done); + }; ws.close(); }; - ws.onopen = () => ++open; - ws.onclose = () => ++close; - - ws.on('open', () => ws.send('foo')); - - ws.on('close', () => { - assert.strictEqual(message, 1); - assert.strictEqual(open, 1); - assert.strictEqual(close, 1); - wss.close(done); - }); + ws.onopen = () => ws.send('foo'); }); wss.on('connection', (ws) => { @@ -1530,7 +1559,7 @@ describe('WebSocket', function () { }); }); - it('doesn\'t return event listeners added with `on`', function () { + it("doesn't return listeners added with `on`", function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1540,7 +1569,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.onopen, undefined); }); - it('doesn\'t remove event listeners added with `on`', function () { + it("doesn't remove listeners added with `on`", function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1562,7 +1591,7 @@ describe('WebSocket', function () { assert.strictEqual(listeners[1]._listener, listener); }); - it('registers listeners for custom events with addEventListener', function () { + it('adds listeners for custom events with `addEventListener`', function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1576,7 +1605,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.listeners('bar').length, 0); }); - it('removes event listeners added with addEventListener', function () { + it('supports the `removeEventListener` method', function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1596,12 +1625,12 @@ describe('WebSocket', function () { ws.removeEventListener('open', listener); ws.removeEventListener('foo', listener); - assert.strictEqual(ws.listeners('message').length, 0); - assert.strictEqual(ws.listeners('open').length, 0); - assert.strictEqual(ws.listeners('foo').length, 0); + assert.strictEqual(ws.listenerCount('message'), 0); + assert.strictEqual(ws.listenerCount('open'), 0); + assert.strictEqual(ws.listenerCount('foo'), 0); }); - it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { + it('wraps text data in a `MessageEvent`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1618,7 +1647,7 @@ describe('WebSocket', function () { }); }); - it('should receive valid CloseEvent when server closes with code 1000', function (done) { + it('receives a `CloseEvent` when server closes (1000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1631,10 +1660,10 @@ describe('WebSocket', function () { }); }); - wss.on('connection', (client) => client.close(1000)); + wss.on('connection', (ws) => ws.close(1000)); }); - it('should receive valid CloseEvent when server closes with code 1001', function (done) { + it('receives a `CloseEvent` when server closes (4000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1642,12 +1671,12 @@ describe('WebSocket', function () { ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); assert.strictEqual(closeEvent.reason, 'some daft reason'); - assert.strictEqual(closeEvent.code, 1001); + assert.strictEqual(closeEvent.code, 4000); wss.close(done); }); }); - wss.on('connection', (client) => client.close(1001, 'some daft reason')); + wss.on('connection', (ws) => ws.close(4000, 'some daft reason')); }); it('sets `target` and `type` on events', function (done) { @@ -1683,7 +1712,7 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.send('hi')); }); - it('should pass binary data as a Node.js Buffer by default', function (done) { + it('passes binary data as a Node.js `Buffer` by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1697,23 +1726,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send(new Uint8Array(4096))); }); - it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.binaryType = 'arraybuffer'; - - ws.onmessage = (evt) => { - assert.ok(evt.data instanceof ArrayBuffer); - wss.close(done); - }; - }); - - wss.on('connection', (ws) => ws.send(new Uint8Array(4096))); - }); - - it('should ignore binaryType for text messages', function (done) { + it('ignores `binaryType` for text messages', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1729,7 +1742,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send('foo')); }); - it('should allow to update binaryType on the fly', function (done) { + it('allows to update `binaryType` on the fly', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1769,8 +1782,8 @@ describe('WebSocket', function () { }); }); - describe('ssl', function () { - it('can connect to secure websocket server', function (done) { + describe('SSL', function () { + it('connects to secure websocket server', function (done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1789,7 +1802,7 @@ describe('WebSocket', function () { }); }); - it('can connect to secure websocket server with client side certificate', function (done) { + it('connects to secure websocket server with client side certificate', function (done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), ca: [fs.readFileSync('test/fixtures/ca1-cert.pem')], @@ -1864,7 +1877,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive very long binary data', function (done) { + it('can send a big binary message', function (done) { this.timeout(4000); const buf = crypto.randomBytes(5 * 1024 * 1024); @@ -1894,7 +1907,36 @@ describe('WebSocket', function () { }); }); - describe('host and origin headers', function () { + describe('Request headers', function () { + it('adds the authorization header if userinfo is present', function (done) { + const agent = new CustomAgent(); + const auth = 'test:testpass'; + + agent.addRequest = (req) => { + assert.strictEqual( + req._headers.authorization, + `Basic ${Buffer.from(auth).toString('base64')}` + ); + done(); + }; + + const ws = new WebSocket(`ws://${auth}@localhost`, { agent }); + }); + + it('adds custom headers', function (done) { + const agent = new CustomAgent(); + + agent.addRequest = (req) => { + assert.strictEqual(req._headers.cookie, 'foo=bar'); + done(); + }; + + const ws = new WebSocket('ws://localhost', { + headers: { 'Cookie': 'foo=bar' }, + agent + }); + }); + it('includes the host header with port number', function (done) { const agent = new CustomAgent(); @@ -1906,7 +1948,30 @@ describe('WebSocket', function () { const ws = new WebSocket('ws://localhost:1337', { agent }); }); - it('lacks default origin header', function (done) { + it('excludes default ports from host header', function () { + const httpsAgent = new https.Agent(); + const httpAgent = new http.Agent(); + const values = []; + let ws; + + httpsAgent.addRequest = httpAgent.addRequest = (req) => { + values.push(req._headers.host); + }; + + ws = new WebSocket('wss://localhost:8443', { agent: httpsAgent }); + ws = new WebSocket('wss://localhost:443', { agent: httpsAgent }); + ws = new WebSocket('ws://localhost:88', { agent: httpAgent }); + ws = new WebSocket('ws://localhost:80', { agent: httpAgent }); + + assert.deepStrictEqual(values, [ + 'localhost:8443', + 'localhost', + 'localhost:88', + 'localhost' + ]); + }); + + it("doesn't add the origin header by default", function (done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1917,7 +1982,7 @@ describe('WebSocket', function () { const ws = new WebSocket('ws://localhost', { agent }); }); - it('honors origin set in options (1/2)', function (done) { + it('honors the `origin` option (1/2)', function (done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1931,7 +1996,7 @@ describe('WebSocket', function () { }); }); - it('honors origin set in options (2/2)', function (done) { + it('honors the `origin` option (2/2)', function (done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1948,99 +2013,60 @@ describe('WebSocket', function () { agent }); }); - - it('excludes default ports from host header', function () { - const httpsAgent = new https.Agent(); - const httpAgent = new http.Agent(); - const values = []; - let ws; - - httpsAgent.addRequest = httpAgent.addRequest = (req) => { - values.push(req._headers.host); - }; - - ws = new WebSocket('wss://localhost:8443', { agent: httpsAgent }); - ws = new WebSocket('wss://localhost:443', { agent: httpsAgent }); - ws = new WebSocket('ws://localhost:88', { agent: httpAgent }); - ws = new WebSocket('ws://localhost:80', { agent: httpAgent }); - - assert.deepStrictEqual(values, [ - 'localhost:8443', - 'localhost', - 'localhost:88', - 'localhost' - ]); - }); }); describe('permessage-deflate', function () { it('is enabled by default', (done) => { - const server = http.createServer(); - const wss = new WebSocket.Server({ server, perMessageDeflate: true }); - - server.on('upgrade', (req, socket, head) => { - assert.ok(req.headers['sec-websocket-extensions'].includes('permessage-deflate')); - }); + const agent = new CustomAgent(); - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`); + agent.addRequest = (req) => { + assert.strictEqual( + req._headers['sec-websocket-extensions'], + 'permessage-deflate; client_max_window_bits' + ); + done(); + }; - ws.on('open', () => { - assert.ok(ws.extensions['permessage-deflate']); - server.close(done); - wss.close(); - }); - }); + const ws = new WebSocket('ws://localhost', { agent }); }); it('can be disabled', function (done) { - const server = http.createServer(); - const wss = new WebSocket.Server({ server, perMessageDeflate: true }); - - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['sec-websocket-extensions'], undefined); - }); + const agent = new CustomAgent(); - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`, { - perMessageDeflate: false - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers['sec-websocket-extensions'], undefined); + done(); + }; - ws.on('open', () => { - server.close(done); - wss.close(); - }); + const ws = new WebSocket('ws://localhost', { + perMessageDeflate: false, + agent }); }); it('can send extension parameters', function (done) { - const server = http.createServer(); - const wss = new WebSocket.Server({ server, perMessageDeflate: true }); - - server.on('upgrade', (req, socket, head) => { - const extensions = req.headers['sec-websocket-extensions']; + const agent = new CustomAgent(); - assert.ok(extensions.includes('permessage-deflate')); - assert.ok(extensions.includes('server_no_context_takeover')); - assert.ok(extensions.includes('client_no_context_takeover')); - assert.ok(extensions.includes('server_max_window_bits=10')); - assert.ok(extensions.includes('client_max_window_bits')); - }); + const value = 'permessage-deflate; server_no_context_takeover;' + + ' client_no_context_takeover; server_max_window_bits=10;' + + ' client_max_window_bits'; - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`, { - perMessageDeflate: { - serverNoContextTakeover: true, - clientNoContextTakeover: true, - serverMaxWindowBits: 10, - clientMaxWindowBits: true - } - }); + agent.addRequest = (req) => { + assert.strictEqual( + req._headers['sec-websocket-extensions'], + value + ); + done(); + }; - ws.on('open', () => { - server.close(done); - wss.close(); - }); + const ws = new WebSocket('ws://localhost', { + perMessageDeflate: { + clientNoContextTakeover: true, + serverNoContextTakeover: true, + clientMaxWindowBits: true, + serverMaxWindowBits: 10 + }, + agent }); }); @@ -2066,7 +2092,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive a typed array', function (done) { + it('can send and receive a `TypedArray`', function (done) { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { @@ -2094,7 +2120,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive ArrayBuffer', function (done) { + it('can send and receive an `ArrayBuffer`', function (done) { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { @@ -2148,7 +2174,7 @@ describe('WebSocket', function () { }); describe('#send', function () { - it('can set the compress option true when perMessageDeflate is disabled', function (done) { + it('ignores the `compress` option if the extension is disabled', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { @@ -2168,37 +2194,8 @@ describe('WebSocket', function () { }); }); - describe('#close', function () { - it('should not raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: { threshold: 0 } - }); - - ws.on('open', () => { - ws.send('hi', (error) => assert.ifError(error)); - ws.close(); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message) => { - assert.strictEqual(message, 'hi'); - ws.on('close', (code) => { - assert.strictEqual(code, 1005); - wss.close(done); - }); - }); - }); - }); - }); - describe('#terminate', function () { - it('will raise error callback, if any, if called during send data', function (done) { + it('can be used while data is being compressed', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, port: 0 @@ -2209,8 +2206,8 @@ describe('WebSocket', function () { }); ws.on('open', () => { - ws.send('hi', (error) => { - assert.ok(error instanceof Error); + ws.send('hi', (err) => { + assert.ok(err instanceof Error); wss.close(done); }); ws.terminate(); @@ -2218,7 +2215,7 @@ describe('WebSocket', function () { }); }); - it('can be used while data is being processed', function (done) { + it('can be used while data is being decompressed', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: true, port: 0 From 1c783c295c9e6bc3f3149c712434b5dff018f586 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 28 Dec 2017 11:24:14 +0100 Subject: [PATCH 413/489] [major] Rename the 'headers' event to 'upgrade' Refs: https://github.com/websockets/ws/pull/1082#issuecomment-295755780 --- doc/ws.md | 17 ++++++++--------- lib/websocket.js | 4 ++-- test/websocket-server.test.js | 4 ++-- test/websocket.test.js | 14 +++++++------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 215a3010a..6844cbeb2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -237,15 +237,6 @@ human-readable string explaining why the connection has been closed. Emitted when an error occurs. Errors from the underlying `net.Socket` are forwarded here. -### Event: 'headers' - -- `headers` {Object} -- `response` {http.IncomingMessage} - -Emitted when response headers are received from the server as part of the -handshake. This allows you to read headers from the server, for example -'set-cookie' headers. - ### Event: 'message' - `data` {String|Buffer|ArrayBuffer|Buffer[]} @@ -278,6 +269,14 @@ response. This event gives the ability to read the response in order to extract useful information. If the server sends an invalid response and there isn't a listener for this event, an error is emitted. +### Event: 'upgrade' + +- `response` {http.IncomingMessage} + +Emitted when response headers are received from the server as part of the +handshake. This allows you to read headers from the server, for example +'set-cookie' headers. + ### websocket.addEventListener(type, listener) - `type` {String} A string representing the event type to listen for. diff --git a/lib/websocket.js b/lib/websocket.js index 907de2117..491a56223 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -679,10 +679,10 @@ function initAsClient (address, protocols, options) { }); this._req.on('upgrade', (res, socket, head) => { - this.emit('headers', res.headers, res); + this.emit('upgrade', res); // - // The user may have closed the connection from a listener of the `headers` + // The user may have closed the connection from a listener of the `upgrade` // event. // if (this.readyState !== WebSocket.CONNECTING) return; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 4ba93675f..14ea76c49 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -713,9 +713,9 @@ describe('WebSocketServer', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('headers', (headers) => { + ws.on('upgrade', (res) => { assert.strictEqual( - headers['sec-websocket-extensions'], + res.headers['sec-websocket-extensions'], 'permessage-deflate; client_max_window_bits=8' ); diff --git a/test/websocket.test.js b/test/websocket.test.js index 397cb153b..eb4b48fe1 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -402,12 +402,12 @@ describe('WebSocket', function () { }); }); - it("emits a 'headers' event", function (done) { + it("emits an 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('headers', (headers, res) => { - assert.strictEqual(headers, res.headers); + ws.on('upgrade', (res) => { + assert.ok(res instanceof http.IncomingMessage); wss.close(done); }); }); @@ -1206,7 +1206,7 @@ describe('WebSocket', function () { }); }); - it('can be called from a listener of the headers event', function (done) { + it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1220,7 +1220,7 @@ describe('WebSocket', function () { ); ws.on('close', () => wss.close(done)); }); - ws.on('headers', () => ws.close()); + ws.on('upgrade', () => ws.close()); }); }); @@ -1479,7 +1479,7 @@ describe('WebSocket', function () { }); }); - it('can be called from a listener of the headers event', function (done) { + it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1493,7 +1493,7 @@ describe('WebSocket', function () { ); ws.on('close', () => wss.close(done)); }); - ws.on('headers', () => ws.terminate()); + ws.on('upgrade', () => ws.terminate()); }); }); From a206e986fcdbf6f71d008c910fdd60f5141ba7ac Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 31 Dec 2017 10:03:14 +0100 Subject: [PATCH 414/489] [major] Remove `WebSocket#pause()` and `WebSocket#resume()` This is in preparation for read backpressure handling when permessage-deflate is enabled. The user should not interfere by pausing/resuming the underlying `net.Socket` stream. --- doc/ws.md | 8 ------ lib/websocket.js | 32 ---------------------- test/websocket.test.js | 61 ------------------------------------------ 3 files changed, 101 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 6844cbeb2..44f1bd8e7 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -344,10 +344,6 @@ listener receives a `MessageEvent` named "message". An event listener to be called when the connection is established. The listener receives an `OpenEvent` named "open". -### websocket.pause() - -Pause the socket. - ### websocket.ping([data[, mask]][, callback]) - `data` {Any} The data to send in the ping frame. @@ -387,10 +383,6 @@ The current state of the connection. This is one of the ready state constants. Removes an event listener emulating the `EventTarget` interface. -### websocket.resume() - -Resume the socket. - ### websocket.send(data[, options][, callback]) - `data` {Any} The data to send. diff --git a/lib/websocket.js b/lib/websocket.js index 491a56223..032d2d43c 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -225,38 +225,6 @@ class WebSocket extends EventEmitter { this.removeAllListeners(); } - /** - * Pause the socket stream. - * - * @public - */ - pause () { - if (this.readyState !== WebSocket.OPEN) { - throw new Error( - `WebSocket is not open: readyState ${this.readyState} ` + - `(${readyStates[this.readyState]})` - ); - } - - this._socket.pause(); - } - - /** - * Resume the socket stream - * - * @public - */ - resume () { - if (this.readyState !== WebSocket.OPEN) { - throw new Error( - `WebSocket is not open: readyState ${this.readyState} ` + - `(${readyStates[this.readyState]})` - ); - } - - this._socket.resume(); - } - /** * Start a closing handshake. * diff --git a/test/websocket.test.js b/test/websocket.test.js index eb4b48fe1..cf41e7055 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -682,67 +682,6 @@ describe('WebSocket', function () { }); }); - describe('#pause and #resume', function () { - it('throws an error when `readyState` is not `OPEN` (pause)', function () { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - assert.throws( - () => ws.pause(), - /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ - ); - }); - - it('throws an error when `readyState` is not `OPEN` (resume)', function () { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - assert.throws( - () => ws.resume(), - /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ - ); - }); - - it('pauses the underlying stream', function (done) { - // this test is sort-of racecondition'y, since an unlikely slow connection - // to localhost can cause the test to succeed even when the stream pausing - // isn't working as intended. that is an extremely unlikely scenario, though - // and an acceptable risk for the test. - let openCount = 0; - let serverClient; - let client; - - const onOpen = () => { - if (++openCount !== 2) return; - - let paused = true; - serverClient.on('message', () => { - assert.ok(!paused); - wss.close(done); - }); - serverClient.pause(); - - setTimeout(() => { - paused = false; - serverClient.resume(); - }, 200); - - client.send('foo'); - }; - - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - serverClient = ws; - serverClient.on('open', onOpen); - }); - - wss.on('connection', (ws) => { - client = ws; - onOpen(); - }); - }); - }); - describe('#ping', function () { it('throws an error if `readyState` is not `OPEN`', function (done) { const ws = new WebSocket('ws://localhost', { From d03ada231a7961ebfbd797c1ea54632dc8078707 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 4 Jan 2018 09:55:38 +0100 Subject: [PATCH 415/489] [minor] Rename some variables for clarity --- lib/extension.js | 24 +++++++++++----------- lib/permessage-deflate.js | 42 +++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/extension.js b/lib/extension.js index d59d39202..3f48d7517 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -188,21 +188,21 @@ function parse (header) { } /** - * Serializes a parsed `Sec-WebSocket-Extensions` header to a string. + * Builds the `Sec-WebSocket-Extensions` header field value. * - * @param {Object} value The object to format - * @return {String} A string representing the given value + * @param {Object} extensions The map of extensions and parameters to format + * @return {String} A string representing the given object * @public */ -function format (value) { - return Object.keys(value).map((token) => { - var paramsList = value[token]; - if (!Array.isArray(paramsList)) paramsList = [paramsList]; - return paramsList.map((params) => { - return [token].concat(Object.keys(params).map((k) => { - var p = params[k]; - if (!Array.isArray(p)) p = [p]; - return p.map((v) => v === true ? k : `${k}=${v}`).join('; '); +function format (extensions) { + return Object.keys(extensions).map((extension) => { + var configurations = extensions[extension]; + if (!Array.isArray(configurations)) configurations = [configurations]; + return configurations.map((params) => { + return [extension].concat(Object.keys(params).map((k) => { + var values = params[k]; + if (!Array.isArray(values)) values = [values]; + return values.map((v) => v === true ? k : `${k}=${v}`).join('; '); })).join('; '); }).join(', '); }).join(', '); diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 5db327f69..63182200e 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -82,7 +82,7 @@ class PerMessageDeflate { } /** - * Create extension parameters offer. + * Create an extension negotiation offer. * * @return {Object} Extension parameters * @public @@ -109,18 +109,18 @@ class PerMessageDeflate { } /** - * Accept extension offer. + * Accept an extension negotiation offer/response. * - * @param {Array} paramsList Extension parameters + * @param {Array} configurations The extension negotiation offers/reponse * @return {Object} Accepted configuration * @public */ - accept (paramsList) { - paramsList = this.normalizeParams(paramsList); + accept (configurations) { + configurations = this.normalizeParams(configurations); this.params = this._isServer - ? this.acceptAsServer(paramsList) - : this.acceptAsClient(paramsList); + ? this.acceptAsServer(configurations) + : this.acceptAsClient(configurations); return this.params; } @@ -150,15 +150,15 @@ class PerMessageDeflate { } /** - * Accept extension offer from client. + * Accept an extension negotiation offer. * - * @param {Array} paramsList Extension parameters + * @param {Array} offers The extension negotiation offers * @return {Object} Accepted configuration * @private */ - acceptAsServer (paramsList) { + acceptAsServer (offers) { const opts = this._options; - const accepted = paramsList.find((params) => { + const accepted = offers.find((params) => { if ( (opts.serverNoContextTakeover === false && params.server_no_context_takeover) || @@ -201,14 +201,14 @@ class PerMessageDeflate { } /** - * Accept extension response from server. + * Accept the extension negotiation response. * - * @param {Array} paramsList Extension parameters + * @param {Array} response The extension negotiation response * @return {Object} Accepted configuration * @private */ - acceptAsClient (paramsList) { - const params = paramsList[0]; + acceptAsClient (response) { + const params = response[0]; if ( this._options.clientNoContextTakeover === false && @@ -235,14 +235,14 @@ class PerMessageDeflate { } /** - * Normalize extensions parameters. + * Normalize parameters. * - * @param {Array} paramsList Extension parameters - * @return {Array} Normalized extensions parameters + * @param {Array} configurations The extension negotiation offers/reponse + * @return {Array} The offers/response with normalized parameters * @private */ - normalizeParams (paramsList) { - paramsList.forEach((params) => { + normalizeParams (configurations) { + configurations.forEach((params) => { Object.keys(params).forEach((key) => { var value = params[key]; @@ -291,7 +291,7 @@ class PerMessageDeflate { }); }); - return paramsList; + return configurations; } /** From a04d9855a5a3d9bab62f9d22bcf5d1ee8185726b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 5 Jan 2018 10:39:18 +0100 Subject: [PATCH 416/489] [dist] 4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af23fd9e0..23f24fca4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.3", + "version": "4.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 02274090a4eeaffc2d3fbc9f927517b8a31b0ee6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 7 Jan 2018 07:18:36 +0100 Subject: [PATCH 417/489] chore(package): update eslint to version 4.15.0 (#1273) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23f24fca4..e81558816 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.14.0", + "eslint": "~4.15.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 435f92370d9a77b9be11c35c41703229c357625c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 8 Jan 2018 16:25:35 +0100 Subject: [PATCH 418/489] [minor] Fix JSDoc comment --- lib/receiver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/receiver.js b/lib/receiver.js index cc761f40d..c18f6cc1f 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -128,6 +128,7 @@ class Receiver { /** * Adds new data to the parser. * + * @param {Buffer} data A chunk of data * @public */ add (data) { From be3717e29dd95c121cebe1f41f213d5c427823fb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 8 Jan 2018 16:09:31 +0100 Subject: [PATCH 419/489] [test] Replace no longer valid test with a new one When an error occurs data can no longer be added to the parser as the socket is destroyed. --- test/receiver.test.js | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/test/receiver.test.js b/test/receiver.test.js index d96528ee2..dcf113163 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -443,6 +443,19 @@ describe('Receiver', function () { assert.deepStrictEqual(data, ['', 'Hello']); }); + it('ignores data received after a close frame', function () { + const results = []; + const push = results.push.bind(results); + const p = new Receiver(); + + p.onclose = p.onmessage = push; + + p.add(Buffer.from('8800', 'hex')); + p.add(Buffer.from('8100', 'hex')); + + assert.deepStrictEqual(results, [1005, '']); + }); + it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { const p = new Receiver(); @@ -810,26 +823,6 @@ describe('Receiver', function () { }); }); - it('doesn\'t crash if data is received after `maxPayload` is exceeded', function (done) { - const p = new Receiver({}, 5); - const buf = crypto.randomBytes(10); - - let gotError = false; - - p.onerror = function (reason, code) { - gotError = true; - assert.strictEqual(code, 1009); - }; - - p.add(Buffer.from([0x82, buf.length])); - - assert.ok(gotError); - assert.strictEqual(p.onerror, null); - - p.add(buf); - done(); - }); - it('consumes all data before calling `cleanup` callback (1/4)', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); From e3660934e8de9891e8b06ce04a6585cc10e1fce4 Mon Sep 17 00:00:00 2001 From: Quentin Barbe Date: Mon, 15 Jan 2018 18:56:25 +0100 Subject: [PATCH 420/489] [minor] Remove license comments (#1278) --- bench/parser.benchmark.js | 6 ------ bench/sender.benchmark.js | 6 ------ index.js | 6 ------ lib/buffer-util.js | 6 ------ lib/receiver.js | 6 ------ lib/sender.js | 6 ------ lib/validation.js | 6 ------ lib/websocket-server.js | 6 ------ lib/websocket.js | 6 ------ 9 files changed, 54 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index b3484b611..ca4282b4e 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 46132c190..89d3be24b 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const benchmark = require('benchmark'); diff --git a/index.js b/index.js index 4a7715f4a..b8d6be1c9 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const WebSocket = require('./lib/websocket'); diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 6a35e8f43..5ab9e289f 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/receiver.js b/lib/receiver.js index c18f6cc1f..396c9020d 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/sender.js b/lib/sender.js index 61d73df30..b3dacbc41 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/validation.js b/lib/validation.js index a8ac2e634..06269fcf1 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; try { diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 8bc3b2938..7ee643cce 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/websocket.js b/lib/websocket.js index 032d2d43c..394f73c9a 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const EventEmitter = require('events'); From 8d364067d4fc99df137b331bcb2c1294d3a71ab7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 17 Jan 2018 18:44:59 +0100 Subject: [PATCH 421/489] [benchmark] Take into account both the incoming and outgoing data --- bench/speed.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index cae60a2bf..75dfd5a80 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -40,6 +40,7 @@ if (cluster.isMaster) { }; const humanSize = (bytes) => { + if (bytes >= 1073741824) return roundPrec(bytes / 1073741824, 2) + ' GiB'; if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; return roundPrec(bytes, 2) + ' B'; @@ -61,7 +62,7 @@ if (cluster.isMaster) { ws.on('error', (err) => { console.error(err.stack); - cluster.worker.kill(); + cluster.worker.disconnect(); }); ws.on('open', () => { time = process.hrtime(); @@ -79,7 +80,7 @@ if (cluster.isMaster) { humanSize(size), useBinary ? 'binary' : 'text', roundPrec(elapsed / 1e9, 1), - humanSize(size * roundtrips / elapsed * 1e9) + '/s' + humanSize(size * 2 * roundtrips / elapsed * 1e9) + '/s' ); ws.close(); @@ -88,7 +89,7 @@ if (cluster.isMaster) { }; (function run () { - if (configs.length === 0) return cluster.worker.kill(); + if (configs.length === 0) return cluster.worker.disconnect(); var config = configs.shift(); config.push(run); runConfig.apply(null, config); From 141b696b0481dfdd8f59e5edf55e99fb2c88b1b6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 18 Jan 2018 07:19:25 +0100 Subject: [PATCH 422/489] chore(package): update mocha to version 5.0.0 (#1283) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e81558816..d01e7da1f 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~4.1.0", + "mocha": "~5.0.0", "nyc": "~11.4.1", "utf-8-validate": "~4.0.0" } From c7b71435460083d1110eba55d6d3567c4ae6d283 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 20 Jan 2018 07:50:23 +0100 Subject: [PATCH 423/489] chore(package): update eslint to version 4.16.0 (#1284) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d01e7da1f..f43108545 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.15.0", + "eslint": "~4.16.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From b8900786c2925a43543751e6dcf8fec671ebe504 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Jan 2018 10:27:17 +0100 Subject: [PATCH 424/489] [minor] Clean up and rename `Receiver#readBuffer()` Rename `Receiver.prototype.readBuffer()` to `consume` and merge `Receiver.prototype.hasBufferedBytes()` into it. --- lib/receiver.js | 87 ++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 51 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index 396c9020d..60b1409f1 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -62,63 +62,48 @@ class Receiver { } /** - * Consumes bytes from the available buffered data. + * Consumes `n` bytes from the buffered data, calls `cleanup` if necessary. * - * @param {Number} bytes The number of bytes to consume - * @return {Buffer} Consumed bytes + * @param {Number} n The number of bytes to consume + * @return {(Buffer|null)} The consumed bytes or `null` if `n` bytes are not + * available * @private */ - readBuffer (bytes) { - var offset = 0; - var dst; - var l; + consume (n) { + if (this._bufferedBytes < n) { + this._loop = false; + if (this._dead) this.cleanup(this._cleanupCallback); + return null; + } - this._bufferedBytes -= bytes; + this._bufferedBytes -= n; - if (bytes === this._buffers[0].length) return this._buffers.shift(); + if (n === this._buffers[0].length) return this._buffers.shift(); - if (bytes < this._buffers[0].length) { - dst = this._buffers[0].slice(0, bytes); - this._buffers[0] = this._buffers[0].slice(bytes); - return dst; + if (n < this._buffers[0].length) { + const buf = this._buffers[0]; + this._buffers[0] = buf.slice(n); + return buf.slice(0, n); } - dst = Buffer.allocUnsafe(bytes); + const dst = Buffer.allocUnsafe(n); - while (bytes > 0) { - l = this._buffers[0].length; + do { + const buf = this._buffers[0]; - if (bytes >= l) { - this._buffers[0].copy(dst, offset); - offset += l; - this._buffers.shift(); + if (n >= buf.length) { + this._buffers.shift().copy(dst, dst.length - n); } else { - this._buffers[0].copy(dst, offset, 0, bytes); - this._buffers[0] = this._buffers[0].slice(bytes); + buf.copy(dst, dst.length - n, 0, n); + this._buffers[0] = buf.slice(n); } - bytes -= l; - } + n -= buf.length; + } while (n > 0); return dst; } - /** - * Checks if the number of buffered bytes is bigger or equal than `n` and - * calls `cleanup` if necessary. - * - * @param {Number} n The number of bytes to check against - * @return {Boolean} `true` if `bufferedBytes >= n`, else `false` - * @private - */ - hasBufferedBytes (n) { - if (this._bufferedBytes >= n) return true; - - this._loop = false; - if (this._dead) this.cleanup(this._cleanupCallback); - return false; - } - /** * Adds new data to the parser. * @@ -170,9 +155,8 @@ class Receiver { * @private */ getInfo () { - if (!this.hasBufferedBytes(2)) return; - - const buf = this.readBuffer(2); + const buf = this.consume(2); + if (buf === null) return; if ((buf[0] & 0x30) !== 0x00) { this.error( @@ -278,9 +262,10 @@ class Receiver { * @private */ getPayloadLength16 () { - if (!this.hasBufferedBytes(2)) return; + const buf = this.consume(2); + if (buf === null) return; - this._payloadLength = this.readBuffer(2).readUInt16BE(0, true); + this._payloadLength = buf.readUInt16BE(0, true); this.haveLength(); } @@ -290,9 +275,9 @@ class Receiver { * @private */ getPayloadLength64 () { - if (!this.hasBufferedBytes(8)) return; + const buf = this.consume(8); + if (buf === null) return; - const buf = this.readBuffer(8); const num = buf.readUInt32BE(0, true); // @@ -333,9 +318,9 @@ class Receiver { * @private */ getMask () { - if (!this.hasBufferedBytes(4)) return; + this._mask = this.consume(4); + if (this._mask === null) return; - this._mask = this.readBuffer(4); this._state = GET_DATA; } @@ -348,9 +333,9 @@ class Receiver { var data = constants.EMPTY_BUFFER; if (this._payloadLength) { - if (!this.hasBufferedBytes(this._payloadLength)) return; + data = this.consume(this._payloadLength); + if (data === null) return; - data = this.readBuffer(this._payloadLength); if (this._masked) bufferUtil.unmask(data, this._mask); } From 5d8ab0eecff0c905150b9fe9a0925bb34d24b5d9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 26 Jan 2018 13:04:25 +0100 Subject: [PATCH 425/489] [minor] Discard any data received after the close frame Remove the `'data'` listener when the close frame is received. --- lib/receiver.js | 18 ++++++------- lib/websocket.js | 59 +++++++++++++++++++------------------------ test/receiver.test.js | 13 ---------- 3 files changed, 34 insertions(+), 56 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index 60b1409f1..c12070851 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -48,10 +48,11 @@ class Receiver { this._fragments = []; this._cleanupCallback = null; + this._isCleaningUp = false; this._hadError = false; - this._dead = false; this._loop = false; + this.add = this.add.bind(this); this.onmessage = null; this.onclose = null; this.onerror = null; @@ -72,7 +73,7 @@ class Receiver { consume (n) { if (this._bufferedBytes < n) { this._loop = false; - if (this._dead) this.cleanup(this._cleanupCallback); + if (this._isCleaningUp) this.cleanup(this._cleanupCallback); return null; } @@ -107,14 +108,12 @@ class Receiver { /** * Adds new data to the parser. * - * @param {Buffer} data A chunk of data + * @param {Buffer} chunk A chunk of data * @public */ - add (data) { - if (this._dead) return; - - this._bufferedBytes += data.length; - this._buffers.push(data); + add (chunk) { + this._bufferedBytes += chunk.length; + this._buffers.push(chunk); this.startLoop(); } @@ -532,10 +531,9 @@ class Receiver { * @public */ cleanup (cb) { - this._dead = true; - if (!this._hadError && (this._loop || this._state === INFLATING)) { this._cleanupCallback = cb; + this._isCleaningUp = true; } else { this._extensions = null; this._fragments = null; diff --git a/lib/websocket.js b/lib/websocket.js index 394f73c9a..1a465fffd 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,7 +2,6 @@ const EventEmitter = require('events'); const crypto = require('crypto'); -const Ultron = require('ultron'); const https = require('https'); const http = require('http'); const url = require('url'); @@ -50,7 +49,6 @@ class WebSocket extends EventEmitter { this._receiver = null; this._sender = null; this._socket = null; - this._ultron = null; if (address !== null) { if (!protocols) { @@ -123,18 +121,17 @@ class WebSocket extends EventEmitter { socket.setTimeout(0); socket.setNoDelay(); + socket.on('close', this._finalize); + socket.on('error', this._finalize); + socket.on('end', this._finalize); + this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType); this._sender = new Sender(socket, this._extensions); - this._ultron = new Ultron(socket); this._socket = socket; - this._ultron.on('close', this._finalize); - this._ultron.on('error', this._finalize); - this._ultron.on('end', this._finalize); - if (head.length > 0) socket.unshift(head); - this._ultron.on('data', (data) => this._receiver.add(data)); + socket.on('data', this._receiver.add); this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { @@ -143,6 +140,11 @@ class WebSocket extends EventEmitter { }; this._receiver.onpong = (data) => this.emit('pong', data); this._receiver.onclose = (code, reason) => { + // + // Discard any additional data that is received on the socket. + // + this._socket.removeListener('data', this._receiver.add); + this._closeFrameReceived = true; this._closeMessage = reason; this._closeCode = code; @@ -182,41 +184,32 @@ class WebSocket extends EventEmitter { this._finalized = true; if (typeof error === 'object') this.emit('error', error); - if (!this._socket) return this.emitClose(); + if (!this._socket) { + this.readyState = WebSocket.CLOSED; + this.emit('close', this._closeCode, this._closeMessage); + return; + } clearTimeout(this._closeTimer); - this._closeTimer = null; - - this._ultron.destroy(); - this._ultron = null; + this._socket.removeListener('data', this._receiver.add); + this._socket.removeListener('close', this._finalize); + this._socket.removeListener('error', this._finalize); + this._socket.removeListener('end', this._finalize); this._socket.on('error', constants.NOOP); if (!error) this._socket.end(); else this._socket.destroy(); - this._socket = null; - this._sender = null; + this._receiver.cleanup(() => { + this.readyState = WebSocket.CLOSED; - this._receiver.cleanup(() => this.emitClose()); - this._receiver = null; - } - - /** - * Emit the `close` event. - * - * @private - */ - emitClose () { - this.readyState = WebSocket.CLOSED; - - this.emit('close', this._closeCode, this._closeMessage); - - if (this._extensions[PerMessageDeflate.extensionName]) { - this._extensions[PerMessageDeflate.extensionName].cleanup(); - } + if (this._extensions[PerMessageDeflate.extensionName]) { + this._extensions[PerMessageDeflate.extensionName].cleanup(); + } - this.removeAllListeners(); + this.emit('close', this._closeCode, this._closeMessage); + }); } /** diff --git a/test/receiver.test.js b/test/receiver.test.js index dcf113163..4335a12c2 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -443,19 +443,6 @@ describe('Receiver', function () { assert.deepStrictEqual(data, ['', 'Hello']); }); - it('ignores data received after a close frame', function () { - const results = []; - const push = results.push.bind(results); - const p = new Receiver(); - - p.onclose = p.onmessage = push; - - p.add(Buffer.from('8800', 'hex')); - p.add(Buffer.from('8100', 'hex')); - - assert.deepStrictEqual(results, [1005, '']); - }); - it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { const p = new Receiver(); From be0b5652b26ffd0eb6e28784f452b782d4b5c87a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 26 Jan 2018 15:13:18 +0100 Subject: [PATCH 426/489] [fix] Handle cases where `socket.bufferSize` is `undefined` --- lib/websocket.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 1a465fffd..66c549090 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -94,12 +94,12 @@ class WebSocket extends EventEmitter { * @type {Number} */ get bufferedAmount () { - var amount = 0; + if (!this._socket) return 0; - if (this._socket) { - amount = this._socket.bufferSize + this._sender._bufferedBytes; - } - return amount; + // + // `socket.bufferSize` is `undefined` if the socket is closed. + // + return (this._socket.bufferSize || 0) + this._sender._bufferedBytes; } /** From 9c39adfc9734073c1e949c62d56614f3c315ebee Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 27 Jan 2018 19:52:17 +0100 Subject: [PATCH 427/489] [minor] Remove ultron dependency --- lib/websocket-server.js | 47 +++++++++++++++++++++++++++++------------ package.json | 3 +-- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 7ee643cce..7c1438e76 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -3,7 +3,6 @@ const safeBuffer = require('safe-buffer'); const EventEmitter = require('events'); const crypto = require('crypto'); -const Ultron = require('ultron'); const http = require('http'); const url = require('url'); @@ -75,13 +74,14 @@ class WebSocketServer extends EventEmitter { } if (this._server) { - this._ultron = new Ultron(this._server); - this._ultron.on('listening', () => this.emit('listening')); - this._ultron.on('error', (err) => this.emit('error', err)); - this._ultron.on('upgrade', (req, socket, head) => { - this.handleUpgrade(req, socket, head, (client) => { - this.emit('connection', client, req); - }); + this._removeListeners = addListeners(this._server, { + listening: this.emit.bind(this, 'listening'), + error: this.emit.bind(this, 'error'), + upgrade: (req, socket, head) => { + this.handleUpgrade(req, socket, head, (ws) => { + this.emit('connection', ws, req); + }); + } }); } @@ -107,8 +107,8 @@ class WebSocketServer extends EventEmitter { const server = this._server; if (server) { - this._ultron.destroy(); - this._ultron = this._server = null; + this._removeListeners(); + this._removeListeners = this._server = null; // // Close the http server if it was internally created. @@ -144,7 +144,7 @@ class WebSocketServer extends EventEmitter { * @public */ handleUpgrade (req, socket, head, cb) { - socket.on('error', socketError); + socket.on('error', socketOnError); const version = +req.headers['sec-websocket-version']; const extensions = {}; @@ -264,7 +264,7 @@ class WebSocketServer extends EventEmitter { this.emit('headers', headers, req); socket.write(headers.concat('\r\n').join('\r\n')); - socket.removeListener('error', socketError); + socket.removeListener('error', socketOnError); ws.setSocket(socket, head, this.options.maxPayload); @@ -279,12 +279,31 @@ class WebSocketServer extends EventEmitter { module.exports = WebSocketServer; +/** + * Add event listeners on an `EventEmitter` using a map of + * pairs. + * + * @param {EventEmitter} server The event emitter + * @param {Object.} map The listeners to add + * @return {Function} A function that will remove the added listeners when called + * @private + */ +function addListeners (server, map) { + for (const event of Object.keys(map)) server.on(event, map[event]); + + return function removeListeners () { + for (const event of Object.keys(map)) { + server.removeListener(event, map[event]); + } + }; +} + /** * Handle premature socket errors. * * @private */ -function socketError () { +function socketOnError () { this.destroy(); } @@ -309,6 +328,6 @@ function abortConnection (socket, code, message) { ); } - socket.removeListener('error', socketError); + socket.removeListener('error', socketOnError); socket.destroy(); } diff --git a/package.json b/package.json index f43108545..1b87bc46f 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,7 @@ }, "dependencies": { "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "safe-buffer": "~5.1.0" }, "devDependencies": { "benchmark": "~2.1.2", From 7e5ed3d8216d2b5220bdfd84b1823912334fc0d5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 31 Jan 2018 14:14:24 +0100 Subject: [PATCH 428/489] [minor] Optimize `Receiver#cleanup()` for the common case --- lib/receiver.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index c12070851..ff424f3a7 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -422,8 +422,8 @@ class Receiver { controlMessage (data) { if (this._opcode === 0x08) { if (data.length === 0) { - this.onclose(1005, ''); this._loop = false; + this.onclose(1005, ''); this.cleanup(this._cleanupCallback); } else if (data.length === 1) { this.error( @@ -453,8 +453,8 @@ class Receiver { return; } - this.onclose(code, buf.toString()); this._loop = false; + this.onclose(code, buf.toString()); this.cleanup(this._cleanupCallback); } @@ -475,9 +475,9 @@ class Receiver { * @private */ error (err, code) { - this.onerror(err, code); this._hadError = true; this._loop = false; + this.onerror(err, code); this.cleanup(this._cleanupCallback); } @@ -531,24 +531,30 @@ class Receiver { * @public */ cleanup (cb) { + if (this._extensions === null) { + if (cb) cb(); + return; + } + if (!this._hadError && (this._loop || this._state === INFLATING)) { this._cleanupCallback = cb; this._isCleaningUp = true; - } else { - this._extensions = null; - this._fragments = null; - this._buffers = null; - this._mask = null; - - this._cleanupCallback = null; - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; - - if (cb) cb(); + return; } + + this._extensions = null; + this._fragments = null; + this._buffers = null; + this._mask = null; + + this._cleanupCallback = null; + this.onmessage = null; + this.onclose = null; + this.onerror = null; + this.onping = null; + this.onpong = null; + + if (cb) cb(); } } From 57c8d29d7139d5bb899fc9e08b58d6b5c27d16f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 31 Jan 2018 17:42:43 +0100 Subject: [PATCH 429/489] [fix] Ensure that the `'error'` event is emitted at most once --- lib/websocket.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 66c549090..20df7a59b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -172,9 +172,10 @@ class WebSocket extends EventEmitter { } /** - * Clean up and release internal resources. + * Clean up internal resources and emit the `'close'` event. * - * @param {(Boolean|Error)} error Indicates whether or not an error occurred + * @param {(Boolean|Error|undefined)} error Indicates whether or not an error + * occurred * @private */ finalize (error) { @@ -183,8 +184,11 @@ class WebSocket extends EventEmitter { this.readyState = WebSocket.CLOSING; this._finalized = true; - if (typeof error === 'object') this.emit('error', error); if (!this._socket) { + // + // `error` is always an `Error` instance in this case. + // + this.emit('error', error); this.readyState = WebSocket.CLOSED; this.emit('close', this._closeCode, this._closeMessage); return; @@ -202,6 +206,14 @@ class WebSocket extends EventEmitter { else this._socket.destroy(); this._receiver.cleanup(() => { + if (typeof error === 'object' && !this._receiver._hadError) { + // + // Re-emit the `net.Socket` error if an `'error'` event has not already + // been emitted. + // + this.emit('error', error); + } + this.readyState = WebSocket.CLOSED; if (this._extensions[PerMessageDeflate.extensionName]) { From 6f32e1715ccbaba0a14e2ecdc88a86833d2a4fe2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 3 Feb 2018 07:47:32 +0100 Subject: [PATCH 430/489] chore(package): update eslint to version 4.17.0 (#1291) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b87bc46f..e2530737f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.16.0", + "eslint": "~4.17.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 8ea9402928f2c2d16ad4e78220984a71e2ec04db Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Feb 2018 11:28:13 +0100 Subject: [PATCH 431/489] [fix] Emit the first error that occurred, not the last --- lib/websocket.js | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 20df7a59b..98051b913 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -49,6 +49,7 @@ class WebSocket extends EventEmitter { this._receiver = null; this._sender = null; this._socket = null; + this._error = null; if (address !== null) { if (!protocols) { @@ -158,13 +159,8 @@ class WebSocket extends EventEmitter { this._closeMessage = ''; this._closeCode = code; - // - // Ensure that the error is emitted even if `WebSocket#finalize()` has - // already been called. - // - this.readyState = WebSocket.CLOSING; - this.emit('error', error); - this.finalize(true); + if (!this._finalized) this.finalize(error); + else if (!this._error) this.emit('error', error); }; this.readyState = WebSocket.OPEN; @@ -174,8 +170,7 @@ class WebSocket extends EventEmitter { /** * Clean up internal resources and emit the `'close'` event. * - * @param {(Boolean|Error|undefined)} error Indicates whether or not an error - * occurred + * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ finalize (error) { @@ -202,16 +197,19 @@ class WebSocket extends EventEmitter { this._socket.removeListener('end', this._finalize); this._socket.on('error', constants.NOOP); - if (!error) this._socket.end(); - else this._socket.destroy(); + if (error) { + if (error !== true) this._error = error; + this._socket.destroy(); + } else { + this._socket.end(); + } this._receiver.cleanup(() => { - if (typeof error === 'object' && !this._receiver._hadError) { - // - // Re-emit the `net.Socket` error if an `'error'` event has not already - // been emitted. - // - this.emit('error', error); + const err = this._error; + + if (err) { + this._error = null; + this.emit('error', err); } this.readyState = WebSocket.CLOSED; From 66b0d553d146cfc9fa17c93f954d2540667b4731 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Feb 2018 12:50:20 +0100 Subject: [PATCH 432/489] [minor] Use `do...while` in `Receiver#startLoop()` --- lib/receiver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index ff424f3a7..ad1187b83 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -125,7 +125,7 @@ class Receiver { startLoop () { this._loop = true; - while (this._loop) { + do { switch (this._state) { case GET_INFO: this.getInfo(); @@ -145,7 +145,7 @@ class Receiver { default: // `INFLATING` this._loop = false; } - } + } while (this._loop); } /** From 8d61fa057ecc509376713ba42ee1fdea4340ff90 Mon Sep 17 00:00:00 2001 From: Tossapon Nuanchuay Date: Tue, 6 Feb 2018 02:48:05 +0700 Subject: [PATCH 433/489] [api] Add `WebSocketServer.prototype.address()` (#1294) --- doc/ws.md | 8 ++++++++ lib/websocket-server.js | 18 ++++++++++++++++++ test/websocket-server.test.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/doc/ws.md b/doc/ws.md index 44f1bd8e7..62de3f844 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -124,6 +124,14 @@ Emitted when the underlying server has been bound. A set that stores all connected clients. Please note that this property is only added when the `clientTracking` is truthy. +### server.address() + +Returns an object with `port`, `family`, and `address` properties specifying +the bound address, the address family name, and port of the server as reported +by the operating system if listening on an IP socket. +If the server is listening on a pipe or UNIX domain socket, the name is +returned as a string. + ### server.close([callback]) Close the server and terminate all clients, calls callback when done. diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 7c1438e76..ee0913fb5 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -90,6 +90,24 @@ class WebSocketServer extends EventEmitter { this.options = options; } + /** + * Returns the bound address, the address family name, and port of the server + * as reported by the operating system if listening on an IP socket. + * If the server is listening on a pipe or UNIX domain socket, the name is + * returned as a string. + * + * @return {(Object|String|null)} The address of the server + * @public + */ + address () { + if (this.options.noServer) { + throw new Error('The server is operating in "noServer" mode'); + } + + if (!this._server) return null; + return this._server.address(); + } + /** * Close the server. * diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 14ea76c49..21f2c3ecc 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -137,6 +137,34 @@ describe('WebSocketServer', function () { }); }); + describe('#address', function () { + it('returns the address of the server', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const addr = wss.address(); + + assert.deepStrictEqual(addr, wss._server.address()); + wss.close(done); + }); + }); + + it('throws an error when operating in "noServer" mode', function () { + const wss = new WebSocket.Server({ noServer: true }); + + assert.throws(() => { + wss.address(); + }, /^Error: The server is operating in "noServer" mode$/); + }); + + it('returns `null` if called after close', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + wss.close(() => { + assert.strictEqual(wss.address(), null); + done(); + }); + }); + }); + }); + describe('#close', function () { it('does not thrown when called twice', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { From 828194044bf247af852b31c49e2800d557fedeff Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 5 Feb 2018 21:01:26 +0100 Subject: [PATCH 434/489] chore(package): update eslint-plugin-node to version 6.0.0 (#1295) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2530737f..db9da5ced 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint": "~4.17.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", - "eslint-plugin-node": "~5.2.0", + "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.0.0", From 98d195578eb36b171ad8ce8c5ee7c9e733db911b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 10 Feb 2018 14:18:41 +0100 Subject: [PATCH 435/489] [doc] Add a link to isomorphic-ws (#1298) `isomorphic-ws` is a tiny wrapper around `ws` (peer dependency) which returns `global.WebSocket` on the browser. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 12f06c16c..ccf013b5c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ Passes the quite extensive Autobahn test suite: [server][server-report], reference to a back end with the role of a client in the WebSocket communication. Browser clients must use the native [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. +To make the same code work seamlessy on Node.js and the browser, you can use +one of the many wrappers available on npm, like +[isomorphic-ws](https://github.com/heineiuo/isomorphic-ws). ## Table of Contents From 91947706f4a2ce6d73d8b0f59f6e05c014554823 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Feb 2018 21:28:09 +0100 Subject: [PATCH 436/489] [doc] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ccf013b5c..83d21dad3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Passes the quite extensive Autobahn test suite: [server][server-report], reference to a back end with the role of a client in the WebSocket communication. Browser clients must use the native [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. -To make the same code work seamlessy on Node.js and the browser, you can use +To make the same code work seamlessly on Node.js and the browser, you can use one of the many wrappers available on npm, like [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws). From c8408c04c472b49ba63eea4d2c1795bcd74b5fd5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Feb 2018 21:31:50 +0100 Subject: [PATCH 437/489] [doc] Fix docs for `onerror` listener --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 62de3f844..da0f285f4 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -336,7 +336,7 @@ a `CloseEvent` named "close". - {Function} An event listener to be called when an error occurs. The listener receives -an `Error` instance. +an `ErrorEvent` named "error". ### websocket.onmessage From 75b63975d1d90a3e366fb4ca40e230113dc40c27 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Feb 2018 16:00:13 +0100 Subject: [PATCH 438/489] [fix] Ensure that the status code is not incorrectly overwritten --- lib/websocket.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 98051b913..a5f606f9f 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -150,17 +150,16 @@ class WebSocket extends EventEmitter { this._closeMessage = reason; this._closeCode = code; - if (this._finalized) return; - if (code === 1005) this.close(); else this.close(code, reason); }; this._receiver.onerror = (error, code) => { - this._closeMessage = ''; + if (this._error) return; + this._closeCode = code; if (!this._finalized) this.finalize(error); - else if (!this._error) this.emit('error', error); + else this.emit('error', error); }; this.readyState = WebSocket.OPEN; From 563edbd6fa14d70aace66f56b613927be05d5d0c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Feb 2018 16:38:00 +0100 Subject: [PATCH 439/489] [test] Use `WebSocketServer#address()` --- test/websocket-server.test.js | 62 +++++------ test/websocket.test.js | 200 ++++++++++++---------------------- 2 files changed, 96 insertions(+), 166 deletions(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 21f2c3ecc..06c3e8c35 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -39,8 +39,7 @@ describe('WebSocketServer', function () { maxPayload, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => { @@ -57,7 +56,7 @@ describe('WebSocketServer', function () { it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocket.Server({ port: 0 }, () => { const wss2 = new WebSocket.Server({ - port: wss1._server.address().port + port: wss1.address().port }); wss2.on('error', () => wss1.close(done)); @@ -96,7 +95,7 @@ describe('WebSocketServer', function () { it('426s for non-Upgrade requests', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - http.get(`http://localhost:${wss._server.address().port}`, (res) => { + http.get(`http://localhost:${wss.address().port}`, (res) => { let body = ''; assert.strictEqual(res.statusCode, 426); @@ -179,8 +178,7 @@ describe('WebSocketServer', function () { it('closes all clients', function (done) { let closes = 0; const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', () => { if (++closes === 2) done(); }); @@ -241,8 +239,7 @@ describe('WebSocketServer', function () { it('returns a list of connected clients', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.clients.size, 0); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => { @@ -254,8 +251,7 @@ describe('WebSocketServer', function () { it('can be disabled', function (done) { const wss = new WebSocket.Server({ port: 0, clientTracking: false }, () => { assert.strictEqual(wss.clients, undefined); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close()); }); @@ -268,8 +264,7 @@ describe('WebSocketServer', function () { it('is updated when client terminates the connection', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.terminate()); }); @@ -284,8 +279,7 @@ describe('WebSocketServer', function () { it('is updated when client closes the connection', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close()); }); @@ -337,7 +331,7 @@ describe('WebSocketServer', function () { it("closes the connection when path doesn't match", function (done) { const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' @@ -354,7 +348,7 @@ describe('WebSocketServer', function () { it('closes the connection when protocol version is Hixie-76', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'WebSocket', @@ -376,7 +370,7 @@ describe('WebSocketServer', function () { it('fails if the Sec-WebSocket-Key header is invalid', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' @@ -397,7 +391,7 @@ describe('WebSocketServer', function () { it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -419,7 +413,7 @@ describe('WebSocketServer', function () { it('fails is the Sec-WebSocket-Version header is invalid (2/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -445,7 +439,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -474,7 +468,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -534,8 +528,7 @@ describe('WebSocketServer', function () { verifyClient: (o, cb) => process.nextTick(cb, true), port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => wss.close(done)); @@ -547,7 +540,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -573,7 +566,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -634,7 +627,7 @@ describe('WebSocketServer', function () { it('handles data passed along with the upgrade request', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -663,8 +656,10 @@ describe('WebSocketServer', function () { return protocols.pop(); }; const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['foo', 'bar']); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, [ + 'foo', + 'bar' + ]); ws.on('open', () => { assert.strictEqual(ws.protocol, 'bar'); @@ -679,7 +674,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -698,8 +693,7 @@ describe('WebSocketServer', function () { it('emits the `headers` event', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); wss.on('headers', (headers, request) => { assert.deepStrictEqual(headers.slice(0, 3), [ @@ -719,8 +713,7 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('is disabled by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws, req) => { @@ -738,8 +731,7 @@ describe('WebSocketServer', function () { perMessageDeflate: { clientMaxWindowBits: 8 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('upgrade', (res) => { assert.strictEqual( diff --git a/test/websocket.test.js b/test/websocket.test.js index cf41e7055..3801fdd9c 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -66,8 +66,7 @@ describe('WebSocket', function () { it('accepts the `localAddress` option', function (done) { const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { localAddress: '127.0.0.2' }); @@ -114,8 +113,9 @@ describe('WebSocket', function () { if (!addresses.some((val) => val.address === '::1')) return this.skip(); const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + family: 6 + }); }); wss.on('connection', (ws, req) => { @@ -198,8 +198,7 @@ describe('WebSocket', function () { it('defaults to zero upon "open"', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.onopen = () => { assert.strictEqual(ws.bufferedAmount, 0); @@ -213,8 +212,7 @@ describe('WebSocket', function () { perMessageDeflate: true, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -233,8 +231,7 @@ describe('WebSocket', function () { it('takes into account the data in the socket queue', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => { @@ -253,8 +250,7 @@ describe('WebSocket', function () { describe('`extensions`', function () { it('exposes the negotiated extensions names (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); assert.strictEqual(ws.extensions, ''); @@ -275,8 +271,7 @@ describe('WebSocket', function () { perMessageDeflate: true, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); assert.strictEqual(ws.extensions, ''); @@ -296,7 +291,7 @@ describe('WebSocket', function () { describe('`protocol`', function () { it('exposes the subprotocol selected by the server', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; + const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}`, 'foo'); assert.strictEqual(ws.extensions, ''); @@ -325,8 +320,7 @@ describe('WebSocket', function () { it('is set to `OPEN` once connection is established', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { assert.strictEqual(ws.readyState, WebSocket.OPEN); @@ -339,8 +333,7 @@ describe('WebSocket', function () { it('is set to `CLOSED` once connection is closed', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', () => { assert.strictEqual(ws.readyState, WebSocket.CLOSED); @@ -353,8 +346,7 @@ describe('WebSocket', function () { it('is set to `CLOSED` once connection is terminated', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', () => { assert.strictEqual(ws.readyState, WebSocket.CLOSED); @@ -379,8 +371,7 @@ describe('WebSocket', function () { describe('Events', function () { it("emits an 'error' event if an error occurs", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('error', (err) => { assert.ok(err instanceof RangeError); @@ -404,8 +395,7 @@ describe('WebSocket', function () { it("emits an 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('upgrade', (res) => { assert.ok(res instanceof http.IncomingMessage); wss.close(done); @@ -415,8 +405,7 @@ describe('WebSocket', function () { it("emits a 'ping' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('ping', () => wss.close(done)); }); @@ -425,8 +414,7 @@ describe('WebSocket', function () { it("emits a 'pong' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('pong', () => wss.close(done)); }); @@ -665,7 +653,7 @@ describe('WebSocket', function () { describe('Connection with query string', function () { it('connects when pathname is not null', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; + const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -674,7 +662,7 @@ describe('WebSocket', function () { it('connects when pathname is null', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; + const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -705,8 +693,7 @@ describe('WebSocket', function () { it('can send a ping with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.ping(() => ws.ping()); @@ -725,8 +712,7 @@ describe('WebSocket', function () { it('can send a ping with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.ping('hi', () => ws.ping('hi', true)); @@ -744,8 +730,7 @@ describe('WebSocket', function () { it('can send numbers as ping payload', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.ping(0)); }); @@ -782,8 +767,7 @@ describe('WebSocket', function () { it('can send a pong with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.pong(() => ws.pong()); @@ -802,8 +786,7 @@ describe('WebSocket', function () { it('can send a pong with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.pong('hi', () => ws.pong('hi', true)); @@ -821,8 +804,7 @@ describe('WebSocket', function () { it('can send numbers as pong payload', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.pong(0)); }); @@ -845,8 +827,7 @@ describe('WebSocket', function () { array[i] = i / 5; } - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(array, { compress: false })); ws.on('message', (msg) => { @@ -862,8 +843,7 @@ describe('WebSocket', function () { it('can send text data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send('hi')); ws.on('message', (message) => { @@ -879,8 +859,7 @@ describe('WebSocket', function () { it('does not override the `fin` option', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.send('fragment', { fin: false }); @@ -898,8 +877,7 @@ describe('WebSocket', function () { it('sends numbers as strings', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(0)); }); @@ -924,8 +902,7 @@ describe('WebSocket', function () { const buf = Buffer.from(partial.buffer) .slice(partial.byteOffset, partial.byteOffset + partial.byteLength); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(partial, { binary: true })); ws.on('message', (message) => { @@ -942,8 +919,7 @@ describe('WebSocket', function () { it('can send binary data as a buffer', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(buf, { binary: true })); ws.on('message', (message) => { @@ -965,8 +941,7 @@ describe('WebSocket', function () { array[i] = i / 2; } - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(array.buffer)); ws.onmessage = (event) => { @@ -983,8 +958,7 @@ describe('WebSocket', function () { it('can send a `Buffer`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(buf)); @@ -1026,8 +1000,7 @@ describe('WebSocket', function () { it('calls the optional callback when data is written out', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.send('hi', (err) => { @@ -1040,8 +1013,7 @@ describe('WebSocket', function () { it('works when the `data` argument is falsy', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send()); }); @@ -1056,8 +1028,7 @@ describe('WebSocket', function () { it('can send text data with `mask` option set to `false`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send('hi', { mask: false })); }); @@ -1078,8 +1049,7 @@ describe('WebSocket', function () { } const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(array, { mask: false })); }); @@ -1096,8 +1066,7 @@ describe('WebSocket', function () { describe('#close', function () { it('closes the connection if called while connecting (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1117,8 +1086,7 @@ describe('WebSocket', function () { verifyClient: (info, cb) => setTimeout(cb, 300, true), port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1147,8 +1115,7 @@ describe('WebSocket', function () { it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1165,8 +1132,7 @@ describe('WebSocket', function () { it('throws an error if the first argument is invalid (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { assert.throws( @@ -1181,8 +1147,7 @@ describe('WebSocket', function () { it('throws an error if the first argument is invalid (2/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { assert.throws( @@ -1197,8 +1162,7 @@ describe('WebSocket', function () { it('emits an error if the close frame can not be sent', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const socket = net.createConnection(port, () => { + const socket = net.createConnection(wss.address().port, () => { socket.write( 'GET / HTTP/1.1\r\n' + 'Host: localhost\r\n' + @@ -1229,8 +1193,7 @@ describe('WebSocket', function () { it('sends the close status code only when necessary', function (done) { let sent; const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws._socket.once('data', (data) => { @@ -1256,8 +1219,7 @@ describe('WebSocket', function () { it('works when close reason is not specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close(1000)); }); @@ -1273,8 +1235,7 @@ describe('WebSocket', function () { it('works when close reason is specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close(1000, 'some reason')); }); @@ -1293,8 +1254,7 @@ describe('WebSocket', function () { clientTracking: false, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.on('close', (code, reason) => { @@ -1312,8 +1272,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); const messages = []; ws.on('message', (message) => messages.push(message)); @@ -1337,8 +1296,7 @@ describe('WebSocket', function () { it('allows close code 1013', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', (code) => { assert.strictEqual(code, 1013); @@ -1351,8 +1309,7 @@ describe('WebSocket', function () { it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', (code) => { assert.strictEqual(code, 1005); @@ -1369,8 +1326,7 @@ describe('WebSocket', function () { describe('#terminate', function () { it('closes the connection if called while connecting (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1390,8 +1346,7 @@ describe('WebSocket', function () { verifyClient: (info, cb) => setTimeout(cb, 300, true), port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1420,8 +1375,7 @@ describe('WebSocket', function () { it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1438,8 +1392,7 @@ describe('WebSocket', function () { it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', (code) => { assert.strictEqual(code, 1006); @@ -1476,8 +1429,7 @@ describe('WebSocket', function () { it('works like the `EventEmitter` interface', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.onmessage = (messageEvent) => { assert.strictEqual(messageEvent.data, 'foo'); @@ -1571,8 +1523,7 @@ describe('WebSocket', function () { it('wraps text data in a `MessageEvent`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('open', () => ws.send('hi')); ws.addEventListener('message', (messageEvent) => { @@ -1588,8 +1539,7 @@ describe('WebSocket', function () { it('receives a `CloseEvent` when server closes (1000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); @@ -1604,8 +1554,7 @@ describe('WebSocket', function () { it('receives a `CloseEvent` when server closes (4000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); @@ -1621,8 +1570,7 @@ describe('WebSocket', function () { it('sets `target` and `type` on events', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const err = new Error('forced'); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('open', (openEvent) => { assert.strictEqual(openEvent.type, 'open'); @@ -1653,8 +1601,7 @@ describe('WebSocket', function () { it('passes binary data as a Node.js `Buffer` by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.onmessage = (evt) => { assert.ok(Buffer.isBuffer(evt.data)); @@ -1667,8 +1614,7 @@ describe('WebSocket', function () { it('ignores `binaryType` for text messages', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.binaryType = 'arraybuffer'; @@ -1683,8 +1629,7 @@ describe('WebSocket', function () { it('allows to update `binaryType` on the fly', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); function testType (binaryType, next) { const buf = Buffer.from(binaryType); @@ -2014,8 +1959,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2042,8 +1986,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2070,8 +2013,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2092,8 +2034,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); const messages = []; ws.on('message', (message) => messages.push(message)); @@ -2115,8 +2056,7 @@ describe('WebSocket', function () { describe('#send', function () { it('ignores the `compress` option if the extension is disabled', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: false }); @@ -2139,8 +2079,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2159,8 +2098,7 @@ describe('WebSocket', function () { perMessageDeflate: true, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); const messages = []; ws.on('message', (message) => { From 36f249df9ca23720f4cd4bbe57ac600b5cf1b0b4 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 17 Feb 2018 07:33:44 +0100 Subject: [PATCH 440/489] chore(package): update eslint to version 4.18.0 (#1304) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db9da5ced..bd27bdbdc 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.17.0", + "eslint": "~4.18.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~6.0.0", From 84fa837dc074baef3d4f81d33120602b58a6c133 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 19 Feb 2018 07:19:29 +0100 Subject: [PATCH 441/489] chore(package): update eslint-config-standard to version 11.0.0 (#1307) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bd27bdbdc..64ff4aedd 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "benchmark": "~2.1.2", "bufferutil": "~3.0.0", "eslint": "~4.18.0", - "eslint-config-standard": "~10.2.0", + "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.6.0", From f2b5105b0389be854c2f09ed1b0ff481eae73f46 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 22 Feb 2018 07:42:00 +0100 Subject: [PATCH 442/489] chore(package): update eslint-plugin-import to version 2.9.0 (#1309) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64ff4aedd..6adac8b8b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.18.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.8.0", + "eslint-plugin-import": "~2.9.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", From 053a3220fa80e800097c2c2e24b6ac1269fa045e Mon Sep 17 00:00:00 2001 From: Samuel Reed Date: Wed, 21 Feb 2018 23:38:51 -0800 Subject: [PATCH 443/489] [minor] Add `zlib{Deflate,Inflate}Options` options (#1306) Useful to adjust other options, and rather than support and document every single one, it is simpler to allow passing on an object wholesale. The `level` and `memLevel` options are still supported but should be removed in 5.x. --- README.md | 44 ++++++++++++++++++++++ doc/ws.md | 5 ++- lib/permessage-deflate.js | 29 +++++++++----- test/permessage-deflate.test.js | 67 ++++++++++++++++++++++++++++++++- 4 files changed, 133 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 83d21dad3..423ed6a33 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,47 @@ The extension is disabled by default on the server and enabled by default on the client. It adds a significant overhead in terms of performance and memory consumption so we suggest to enable it only if it is really needed. +Note that Node.js has a variety of issues with high-performance compression, +where increased concurrency, especially on Linux, can lead to +[catastrophic memory fragmentation][node-zlib-bug] and slow performance. +If you intend to use `permessage-deflate` in production, it is worthwhile +to set up a test representative of your workload and ensure Node.js/zlib will +handle it with acceptable performance and memory usage. + +Tuning of `permessage-deflate` can be done via the options defined below. +You can also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed +directly into the creation of +[raw deflate/inflate streams][node-zlib-deflaterawdocs]. + +See [the docs][ws-server-options] for more options. + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ + port: 8080, + perMessageDeflate: { + zlibDeflateOptions: { // See zlib defaults + chunkSize: 1024, + memLevel: 7, + level: 3, + }, + zlibInflateOptions: { + chunkSize: 10 * 1024 + }, + // Other options settable: + clientNoContextTakeover: true, // defaults to negotiated value + serverNoContextTakeover: true, // defaults to negotiated value + clientMaxWindowBits: 10, // defaults to negotiated value + serverMaxWindowBits: 10, // defaults to negotiated value + // Below options specified as default values + concurrencyLimit: 10, // limits zlib concurrency for perf + threshold: 1024, // Size (in bytes) below which messages + // should not be compressed + } +}); +``` + The client will only use the extension if it is supported and enabled on the server. To always disable the extension on the client set the `perMessageDeflate` option to `false`. @@ -344,3 +385,6 @@ We're using the GitHub [releases][changelog] for changelog entries. [server-report]: http://websockets.github.io/ws/autobahn/servers/ [permessage-deflate]: https://tools.ietf.org/html/rfc7692 [changelog]: https://github.com/websockets/ws/releases +[node-zlib-bug]: https://github.com/nodejs/node/issues/8871 +[node-zlib-deflaterawdocs]: https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options +[ws-server-options]: https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback diff --git a/doc/ws.md b/doc/ws.md index da0f285f4..213af0064 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -71,8 +71,8 @@ provided then that is extension parameters: takeover. - `serverMaxWindowBits` {Number} The value of `windowBits`. - `clientMaxWindowBits` {Number} Request a custom client window size. -- `level` {Number} The value of zlib's `level` param (0-9, default 8). -- `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). +- `zlibOptions` {Object} [Additional options][zlib-options] to pass to zlib + on deflate. - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. - `concurrencyLimit` {Number} The number of concurrent calls to zlib. @@ -420,3 +420,4 @@ The URL of the WebSocket server. Server clients don't have this attribute. [concurrency-limit]: https://github.com/websockets/ws/issues/1202 [permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 +[zlib-options]: https://nodejs.org/api/zlib.html#zlib_class_options diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 63182200e..cabab9710 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -44,8 +44,8 @@ class PerMessageDeflate { * use of a custom server window size * @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support * for, or request, a custom client window size - * @param {Number} options.level The value of zlib's `level` param - * @param {Number} options.memLevel The value of zlib's `memLevel` param + * @param {Object} options.zlibDeflateOptions Options to pass to zlib on deflate + * @param {Object} options.zlibInflateOptions Options to pass to zlib on inflate * @param {Number} options.threshold Size (in bytes) below which messages * should not be compressed * @param {Number} options.concurrencyLimit The number of concurrent calls to @@ -345,7 +345,13 @@ class PerMessageDeflate { ? zlib.Z_DEFAULT_WINDOWBITS : this.params[key]; - this._inflate = zlib.createInflateRaw({ windowBits }); + this._inflate = zlib.createInflateRaw( + Object.assign( + {}, + this._options.zlibInflateOptions, + { windowBits } + ) + ); this._inflate[kTotalLength] = 0; this._inflate[kBuffers] = []; this._inflate[kOwner] = this; @@ -412,12 +418,17 @@ class PerMessageDeflate { ? zlib.Z_DEFAULT_WINDOWBITS : this.params[key]; - this._deflate = zlib.createDeflateRaw({ - memLevel: this._options.memLevel, - level: this._options.level, - flush: zlib.Z_SYNC_FLUSH, - windowBits - }); + this._deflate = zlib.createDeflateRaw( + Object.assign( + // TODO deprecate memLevel/level and recommend zlibDeflateOptions instead + { + memLevel: this._options.memLevel, + level: this._options.level + }, + this._options.zlibDeflateOptions, + { windowBits } + ) + ); this._deflate[kTotalLength] = 0; this._deflate[kBuffers] = []; diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 929315988..038af2df7 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -352,8 +352,8 @@ describe('PerMessageDeflate', function () { }); it('honors the `level` option', function (done) { - const lev9 = new PerMessageDeflate({ threshold: 0, level: 9 }); const lev0 = new PerMessageDeflate({ threshold: 0, level: 0 }); + const lev9 = new PerMessageDeflate({ threshold: 0, level: 9 }); const extensionStr = ( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + @@ -390,6 +390,71 @@ describe('PerMessageDeflate', function () { }); }); + it('honors the `zlib{Deflate,Inflate}Options` option', function (done) { + const lev0 = new PerMessageDeflate({ + threshold: 0, + zlibDeflateOptions: { + level: 0, + chunkSize: 256 + }, + zlibInflateOptions: { + chunkSize: 2048 + } + }); + const lev9 = new PerMessageDeflate({ + threshold: 0, + zlibDeflateOptions: { + level: 9, + chunkSize: 128 + }, + zlibInflateOptions: { + chunkSize: 1024 + } + }); + + // Note no context takeover so we can get a hold of the raw streams after we do the dance + const extensionStr = ( + 'permessage-deflate; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + const buf = Buffer.from("Some compressible data, it's compressible."); + + lev0.accept(extension.parse(extensionStr)['permessage-deflate']); + lev9.accept(extension.parse(extensionStr)['permessage-deflate']); + + lev0.compress(buf, true, (err, compressed1) => { + if (err) return done(err); + + lev0.decompress(compressed1, true, (err, decompressed1) => { + if (err) return done(err); + + lev9.compress(buf, true, (err, compressed2) => { + if (err) return done(err); + + lev9.decompress(compressed2, true, (err, decompressed2) => { + if (err) return done(err); + // Level 0 compression actually adds a few bytes due to headers. + assert.ok(compressed1.length > buf.length); + // Level 9 should not, of course. + assert.ok(compressed2.length < buf.length); + // Ensure they both decompress back properly. + assert.ok(decompressed1.equals(buf)); + assert.ok(decompressed2.equals(buf)); + + // Assert options were set. + assert.ok(lev0._deflate._level === 0); + assert.ok(lev9._deflate._level === 9); + assert.ok(lev0._deflate._chunkSize === 256); + assert.ok(lev9._deflate._chunkSize === 128); + assert.ok(lev0._inflate._chunkSize === 2048); + assert.ok(lev9._inflate._chunkSize === 1024); + done(); + }); + }); + }); + }); + }); + it("doesn't use contex takeover if not allowed", function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse( From d8da1b2a183381e180982548f5e6ecbc0da2d1bb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Feb 2018 08:53:17 +0100 Subject: [PATCH 444/489] [doc] Add missing `zlibInflateOptions` option --- README.md | 29 ++++++++++++++--------------- doc/ws.md | 6 ++++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 423ed6a33..8f94ca303 100644 --- a/README.md +++ b/README.md @@ -84,14 +84,13 @@ consumption so we suggest to enable it only if it is really needed. Note that Node.js has a variety of issues with high-performance compression, where increased concurrency, especially on Linux, can lead to [catastrophic memory fragmentation][node-zlib-bug] and slow performance. -If you intend to use `permessage-deflate` in production, it is worthwhile -to set up a test representative of your workload and ensure Node.js/zlib will -handle it with acceptable performance and memory usage. +If you intend to use permessage-deflate in production, it is worthwhile to set +up a test representative of your workload and ensure Node.js/zlib will handle +it with acceptable performance and memory usage. -Tuning of `permessage-deflate` can be done via the options defined below. -You can also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed -directly into the creation of -[raw deflate/inflate streams][node-zlib-deflaterawdocs]. +Tuning of permessage-deflate can be done via the options defined below. You can +also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly +into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs]. See [the docs][ws-server-options] for more options. @@ -101,7 +100,7 @@ const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080, perMessageDeflate: { - zlibDeflateOptions: { // See zlib defaults + zlibDeflateOptions: { // See zlib defaults. chunkSize: 1024, memLevel: 7, level: 3, @@ -110,14 +109,14 @@ const wss = new WebSocket.Server({ chunkSize: 10 * 1024 }, // Other options settable: - clientNoContextTakeover: true, // defaults to negotiated value - serverNoContextTakeover: true, // defaults to negotiated value - clientMaxWindowBits: 10, // defaults to negotiated value - serverMaxWindowBits: 10, // defaults to negotiated value - // Below options specified as default values - concurrencyLimit: 10, // limits zlib concurrency for perf + clientNoContextTakeover: true, // Defaults to negotiated value. + serverNoContextTakeover: true, // Defaults to negotiated value. + clientMaxWindowBits: 10, // Defaults to negotiated value. + serverMaxWindowBits: 10, // Defaults to negotiated value. + // Below options specified as default values. + concurrencyLimit: 10, // Limits zlib concurrency for perf. threshold: 1024, // Size (in bytes) below which messages - // should not be compressed + // should not be compressed. } }); ``` diff --git a/doc/ws.md b/doc/ws.md index 213af0064..464df38b0 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -71,8 +71,10 @@ provided then that is extension parameters: takeover. - `serverMaxWindowBits` {Number} The value of `windowBits`. - `clientMaxWindowBits` {Number} Request a custom client window size. -- `zlibOptions` {Object} [Additional options][zlib-options] to pass to zlib - on deflate. +- `zlibDeflateOptions` {Object} [Additional options][zlib-options] to pass to + zlib on deflate. +- `zlibInflateOptions` {Object} [Additional options][zlib-options] to pass to + zlib on inflate. - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. - `concurrencyLimit` {Number} The number of concurrent calls to zlib. From a70bd65485fe78f7b4fe56d48c3715f90c372367 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Feb 2018 08:54:42 +0100 Subject: [PATCH 445/489] [test] Fix typo --- test/websocket-server.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 06c3e8c35..1e9438803 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -165,7 +165,7 @@ describe('WebSocketServer', function () { }); describe('#close', function () { - it('does not thrown when called twice', function (done) { + it('does not throw when called twice', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(); wss.close(); From d390dc5b34c269669d5fa4450cd394d957055296 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Feb 2018 09:00:56 +0100 Subject: [PATCH 446/489] [dist] 4.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6adac8b8b..0623d2437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "4.0.0", + "version": "4.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 70fff53e6e3701f3a07fe166331500dad18e8121 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Feb 2018 07:58:29 +0100 Subject: [PATCH 447/489] [minor] Make `Receiver` inherit from `stream.Writable` --- .travis.yml | 2 +- appveyor.yml | 2 +- lib/constants.js | 12 +- lib/permessage-deflate.js | 20 +- lib/receiver.js | 417 +++++++++------------- lib/websocket.js | 378 +++++++++++++------- test/receiver.test.js | 729 ++++++++++++++++---------------------- 7 files changed, 746 insertions(+), 814 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1d63d7d33..e21953a5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,6 @@ node_js: - "8" - "6" - "4" - - "4.2.0" + - "4.2.2" after_success: - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index 2502b8c8f..394bc1e89 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ environment: - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - - nodejs_version: "4.2.0" + - nodejs_version: "4.2.2" platform: - x86 - x64 diff --git a/lib/constants.js b/lib/constants.js index 390441462..613a6766c 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -4,7 +4,11 @@ const safeBuffer = require('safe-buffer'); const Buffer = safeBuffer.Buffer; -exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments']; -exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; -exports.EMPTY_BUFFER = Buffer.alloc(0); -exports.NOOP = () => {}; +module.exports = { + BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'], + GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', + kStatusCode: Symbol('status-code'), + kWebSocket: Symbol('websocket'), + EMPTY_BUFFER: Buffer.alloc(0), + NOOP: () => {} +}; diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index cabab9710..05832f28f 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -5,19 +5,20 @@ const Limiter = require('async-limiter'); const zlib = require('zlib'); const bufferUtil = require('./buffer-util'); +const constants = require('./constants'); const Buffer = safeBuffer.Buffer; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +const kPerMessageDeflate = Symbol('permessage-deflate'); const kWriteInProgress = Symbol('write-in-progress'); const kPendingClose = Symbol('pending-close'); const kTotalLength = Symbol('total-length'); const kCallback = Symbol('callback'); const kBuffers = Symbol('buffers'); const kError = Symbol('error'); -const kOwner = Symbol('owner'); // // We limit zlib concurrency, which prevents severe memory fragmentation @@ -346,15 +347,11 @@ class PerMessageDeflate { : this.params[key]; this._inflate = zlib.createInflateRaw( - Object.assign( - {}, - this._options.zlibInflateOptions, - { windowBits } - ) + Object.assign({}, this._options.zlibInflateOptions, { windowBits }) ); + this._inflate[kPerMessageDeflate] = this; this._inflate[kTotalLength] = 0; this._inflate[kBuffers] = []; - this._inflate[kOwner] = this; this._inflate.on('error', inflateOnError); this._inflate.on('data', inflateOnData); } @@ -492,15 +489,15 @@ function inflateOnData (chunk) { this[kTotalLength] += chunk.length; if ( - this[kOwner]._maxPayload < 1 || - this[kTotalLength] <= this[kOwner]._maxPayload + this[kPerMessageDeflate]._maxPayload < 1 || + this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload ) { this[kBuffers].push(chunk); return; } this[kError] = new RangeError('Max payload size exceeded'); - this[kError].closeCode = 1009; + this[kError][constants.kStatusCode] = 1009; this.removeListener('data', inflateOnData); this.reset(); } @@ -516,6 +513,7 @@ function inflateOnError (err) { // There is no need to call `Zlib#close()` as the handle is automatically // closed when an error is emitted. // - this[kOwner]._inflate = null; + this[kPerMessageDeflate]._inflate = null; + err[constants.kStatusCode] = 1007; this[kCallback](err); } diff --git a/lib/receiver.js b/lib/receiver.js index ad1187b83..a66cc10ee 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -1,6 +1,7 @@ 'use strict'; const safeBuffer = require('safe-buffer'); +const stream = require('stream'); const PerMessageDeflate = require('./permessage-deflate'); const bufferUtil = require('./buffer-util'); @@ -18,8 +19,10 @@ const INFLATING = 5; /** * HyBi Receiver implementation. + * + * @extends stream.Writable */ -class Receiver { +class Receiver extends stream.Writable { /** * Creates a Receiver instance. * @@ -28,7 +31,10 @@ class Receiver { * @param {String} binaryType The type for binary data */ constructor (extensions, maxPayload, binaryType) { + super(); + this._binaryType = binaryType || constants.BINARY_TYPES[0]; + this[constants.kWebSocket] = undefined; this._extensions = extensions || {}; this._maxPayload = maxPayload | 0; @@ -37,46 +43,43 @@ class Receiver { this._compressed = false; this._payloadLength = 0; + this._mask = undefined; this._fragmented = 0; this._masked = false; this._fin = false; - this._mask = null; this._opcode = 0; this._totalPayloadLength = 0; this._messageLength = 0; this._fragments = []; - this._cleanupCallback = null; - this._isCleaningUp = false; - this._hadError = false; + this._state = GET_INFO; this._loop = false; + } - this.add = this.add.bind(this); - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; + /** + * Implements `Writable.prototype._write()`. + * + * @param {Buffer} chunk The chunk of data to write + * @param {String} encoding The character encoding of `chunk` + * @param {Function} cb Callback + */ + _write (chunk, encoding, cb) { + if (this._opcode === 0x08) return cb(); - this._state = GET_INFO; + this._bufferedBytes += chunk.length; + this._buffers.push(chunk); + this.startLoop(cb); } /** - * Consumes `n` bytes from the buffered data, calls `cleanup` if necessary. + * Consumes `n` bytes from the buffered data. * * @param {Number} n The number of bytes to consume - * @return {(Buffer|null)} The consumed bytes or `null` if `n` bytes are not - * available + * @return {Buffer} The consumed bytes * @private */ consume (n) { - if (this._bufferedBytes < n) { - this._loop = false; - if (this._isCleaningUp) this.cleanup(this._cleanupCallback); - return null; - } - this._bufferedBytes -= n; if (n === this._buffers[0].length) return this._buffers.shift(); @@ -105,74 +108,66 @@ class Receiver { return dst; } - /** - * Adds new data to the parser. - * - * @param {Buffer} chunk A chunk of data - * @public - */ - add (chunk) { - this._bufferedBytes += chunk.length; - this._buffers.push(chunk); - this.startLoop(); - } - /** * Starts the parsing loop. * + * @param {Function} cb Callback * @private */ - startLoop () { + startLoop (cb) { + var err; this._loop = true; do { switch (this._state) { case GET_INFO: - this.getInfo(); + err = this.getInfo(); break; case GET_PAYLOAD_LENGTH_16: - this.getPayloadLength16(); + err = this.getPayloadLength16(); break; case GET_PAYLOAD_LENGTH_64: - this.getPayloadLength64(); + err = this.getPayloadLength64(); break; case GET_MASK: this.getMask(); break; case GET_DATA: - this.getData(); + err = this.getData(cb); break; default: // `INFLATING` this._loop = false; + return; } } while (this._loop); + + cb(err); } /** * Reads the first two bytes of a frame. * + * @return {(RangeError|undefined)} A possible error * @private */ getInfo () { + if (this._bufferedBytes < 2) { + this._loop = false; + return; + } + const buf = this.consume(2); - if (buf === null) return; if ((buf[0] & 0x30) !== 0x00) { - this.error( - new RangeError('Invalid WebSocket frame: RSV2 and RSV3 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002); } const compressed = (buf[0] & 0x40) === 0x40; if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { - this.error( - new RangeError('Invalid WebSocket frame: RSV1 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV1 must be clear', true, 1002); } this._fin = (buf[0] & 0x80) === 0x80; @@ -181,102 +176,85 @@ class Receiver { if (this._opcode === 0x00) { if (compressed) { - this.error( - new RangeError('Invalid WebSocket frame: RSV1 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV1 must be clear', true, 1002); } if (!this._fragmented) { - this.error( - new RangeError('Invalid WebSocket frame: invalid opcode 0'), - 1002 - ); - return; - } else { - this._opcode = this._fragmented; + this._loop = false; + return error(RangeError, 'invalid opcode 0', true, 1002); } + + this._opcode = this._fragmented; } else if (this._opcode === 0x01 || this._opcode === 0x02) { if (this._fragmented) { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid opcode ${this._opcode}` - ), - 1002 - ); - return; + this._loop = false; + return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002); } this._compressed = compressed; } else if (this._opcode > 0x07 && this._opcode < 0x0b) { if (!this._fin) { - this.error( - new RangeError('Invalid WebSocket frame: FIN must be set'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'FIN must be set', true, 1002); } if (compressed) { - this.error( - new RangeError('Invalid WebSocket frame: RSV1 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV1 must be clear', true, 1002); } if (this._payloadLength > 0x7d) { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid payload length ` + - `${this._payloadLength}` - ), + this._loop = false; + return error( + RangeError, + `invalid payload length ${this._payloadLength}`, + true, 1002 ); - return; } } else { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid opcode ${this._opcode}` - ), - 1002 - ); - return; + this._loop = false; + return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002); } if (!this._fin && !this._fragmented) this._fragmented = this._opcode; - this._masked = (buf[1] & 0x80) === 0x80; if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16; else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64; - else this.haveLength(); + else return this.haveLength(); } /** * Gets extended payload length (7+16). * + * @return {(RangeError|undefined)} A possible error * @private */ getPayloadLength16 () { - const buf = this.consume(2); - if (buf === null) return; + if (this._bufferedBytes < 2) { + this._loop = false; + return; + } - this._payloadLength = buf.readUInt16BE(0, true); - this.haveLength(); + this._payloadLength = this.consume(2).readUInt16BE(0, true); + return this.haveLength(); } /** * Gets extended payload length (7+64). * + * @return {(RangeError|undefined)} A possible error * @private */ getPayloadLength64 () { - const buf = this.consume(8); - if (buf === null) return; + if (this._bufferedBytes < 8) { + this._loop = false; + return; + } + const buf = this.consume(8); const num = buf.readUInt32BE(0, true); // @@ -284,27 +262,32 @@ class Receiver { // if payload length is greater than this number. // if (num > Math.pow(2, 53 - 32) - 1) { - this.error( - new RangeError( - 'Unsupported WebSocket frame: payload length > 2^53 - 1' - ), + this._loop = false; + return error( + RangeError, + 'Unsupported WebSocket frame: payload length > 2^53 - 1', + false, 1009 ); - return; } this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); - this.haveLength(); + return this.haveLength(); } /** * Payload length has been read. * + * @return {(RangeError|undefined)} A possible error * @private */ haveLength () { - if (this._opcode < 0x08 && this.maxPayloadExceeded(this._payloadLength)) { - return; + if (this._payloadLength && this._opcode < 0x08) { + this._totalPayloadLength += this._payloadLength; + if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) { + this._loop = false; + return error(RangeError, 'Max payload size exceeded', false, 1009); + } } if (this._masked) this._state = GET_MASK; @@ -317,60 +300,88 @@ class Receiver { * @private */ getMask () { - this._mask = this.consume(4); - if (this._mask === null) return; + if (this._bufferedBytes < 4) { + this._loop = false; + return; + } + this._mask = this.consume(4); this._state = GET_DATA; } /** * Reads data bytes. * + * @param {Function} cb Callback + * @return {(Error|RangeError|undefined)} A possible error * @private */ - getData () { + getData (cb) { var data = constants.EMPTY_BUFFER; if (this._payloadLength) { - data = this.consume(this._payloadLength); - if (data === null) return; + if (this._bufferedBytes < this._payloadLength) { + this._loop = false; + return; + } + data = this.consume(this._payloadLength); if (this._masked) bufferUtil.unmask(data, this._mask); } - if (this._opcode > 0x07) { - this.controlMessage(data); - } else if (this._compressed) { + if (this._opcode > 0x07) return this.controlMessage(data); + + if (this._compressed) { this._state = INFLATING; - this.decompress(data); - } else if (this.pushFragment(data)) { - this.dataMessage(); + this.decompress(data, cb); + return; + } + + if (data.length) { + // + // This message is not compressed so its lenght is the sum of the payload + // length of all fragments. + // + this._messageLength = this._totalPayloadLength; + this._fragments.push(data); } + + return this.dataMessage(); } /** * Decompresses data. * * @param {Buffer} data Compressed data + * @param {Function} cb Callback * @private */ - decompress (data) { + decompress (data, cb) { const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; perMessageDeflate.decompress(data, this._fin, (err, buf) => { - if (err) { - this.error(err, err.closeCode === 1009 ? 1009 : 1007); - return; + if (err) return cb(err); + + if (buf.length) { + this._messageLength += buf.length; + if (this._messageLength > this._maxPayload && this._maxPayload > 0) { + return cb(error(RangeError, 'Max payload size exceeded', false, 1009)); + } + + this._fragments.push(buf); } - if (this.pushFragment(buf)) this.dataMessage(); - this.startLoop(); + const er = this.dataMessage(); + if (er) return cb(er); + + this.startLoop(cb); }); } /** * Handles a data message. * + * @return {(Error|undefined)} A possible error * @private */ dataMessage () { @@ -394,19 +405,16 @@ class Receiver { data = fragments; } - this.onmessage(data); + this.emit('message', data); } else { const buf = toBuffer(fragments, messageLength); if (!validation.isValidUTF8(buf)) { - this.error( - new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), - 1007 - ); - return; + this._loop = false; + return error(Error, 'invalid UTF-8 sequence', true, 1007); } - this.onmessage(buf.toString()); + this.emit('message', buf.toString()); } } @@ -417,149 +425,68 @@ class Receiver { * Handles a control message. * * @param {Buffer} data Data to handle + * @return {(Error|RangeError|undefined)} A possible error * @private */ controlMessage (data) { if (this._opcode === 0x08) { + this._loop = false; + if (data.length === 0) { - this._loop = false; - this.onclose(1005, ''); - this.cleanup(this._cleanupCallback); + this.emit('close', 1005, ''); + this.end(); } else if (data.length === 1) { - this.error( - new RangeError('Invalid WebSocket frame: invalid payload length 1'), - 1002 - ); + return error(RangeError, 'invalid payload length 1', true, 1002); } else { const code = data.readUInt16BE(0, true); if (!validation.isValidStatusCode(code)) { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid status code ${code}` - ), - 1002 - ); - return; + return error(RangeError, `invalid status code ${code}`, true, 1002); } const buf = data.slice(2); if (!validation.isValidUTF8(buf)) { - this.error( - new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), - 1007 - ); - return; + return error(Error, 'invalid UTF-8 sequence', true, 1007); } - this._loop = false; - this.onclose(code, buf.toString()); - this.cleanup(this._cleanupCallback); + this.emit('close', code, buf.toString()); + this.end(); } return; } - if (this._opcode === 0x09) this.onping(data); - else this.onpong(data); + if (this._opcode === 0x09) this.emit('ping', data); + else this.emit('pong', data); this._state = GET_INFO; } - - /** - * Handles an error. - * - * @param {Error} err The error - * @param {Number} code Close code - * @private - */ - error (err, code) { - this._hadError = true; - this._loop = false; - this.onerror(err, code); - this.cleanup(this._cleanupCallback); - } - - /** - * Checks payload size, disconnects socket when it exceeds `maxPayload`. - * - * @param {Number} length Payload length - * @private - */ - maxPayloadExceeded (length) { - if (length === 0 || this._maxPayload < 1) return false; - - const fullLength = this._totalPayloadLength + length; - - if (fullLength <= this._maxPayload) { - this._totalPayloadLength = fullLength; - return false; - } - - this.error(new RangeError('Max payload size exceeded'), 1009); - return true; - } - - /** - * Appends a fragment in the fragments array after checking that the sum of - * fragment lengths does not exceed `maxPayload`. - * - * @param {Buffer} fragment The fragment to add - * @return {Boolean} `true` if `maxPayload` is not exceeded, else `false` - * @private - */ - pushFragment (fragment) { - if (fragment.length === 0) return true; - - const totalLength = this._messageLength + fragment.length; - - if (this._maxPayload < 1 || totalLength <= this._maxPayload) { - this._messageLength = totalLength; - this._fragments.push(fragment); - return true; - } - - this.error(new RangeError('Max payload size exceeded'), 1009); - return false; - } - - /** - * Releases resources used by the receiver. - * - * @param {Function} cb Callback - * @public - */ - cleanup (cb) { - if (this._extensions === null) { - if (cb) cb(); - return; - } - - if (!this._hadError && (this._loop || this._state === INFLATING)) { - this._cleanupCallback = cb; - this._isCleaningUp = true; - return; - } - - this._extensions = null; - this._fragments = null; - this._buffers = null; - this._mask = null; - - this._cleanupCallback = null; - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; - - if (cb) cb(); - } } module.exports = Receiver; +/** + * Builds an error object. + * + * @param {(Error|RangeError)} ErrorCtor The error constructor + * @param {String} message The error message + * @param {Boolean} prefix Specifies whether or not to add a default prefix to + * `message` + * @param {Number} statusCode The status code + * @return {(Error|RangeError)} The error + * @private + */ +function error (ErrorCtor, message, prefix, statusCode) { + const err = new ErrorCtor( + prefix ? `Invalid WebSocket frame: ${message}` : message + ); + + Error.captureStackTrace(err, error); + err[constants.kStatusCode] = statusCode; + return err; +} + /** * Makes a buffer from a list of fragments. * diff --git a/lib/websocket.js b/lib/websocket.js index a5f606f9f..dee00c3cc 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -14,8 +14,10 @@ const Receiver = require('./receiver'); const Sender = require('./sender'); const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; +const kWebSocket = constants.kWebSocket; const protocolVersions = [8, 13]; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. +const NOOP = constants.NOOP; /** * Class representing a WebSocket. @@ -37,12 +39,10 @@ class WebSocket extends EventEmitter { this.protocol = ''; this._binaryType = constants.BINARY_TYPES[0]; - this._finalize = this.finalize.bind(this); this._closeFrameReceived = false; this._closeFrameSent = false; this._closeMessage = ''; this._closeTimer = null; - this._finalized = false; this._closeCode = 1006; this._extensions = {}; this._isServer = true; @@ -119,126 +119,81 @@ class WebSocket extends EventEmitter { * @private */ setSocket (socket, head, maxPayload) { - socket.setTimeout(0); - socket.setNoDelay(); - - socket.on('close', this._finalize); - socket.on('error', this._finalize); - socket.on('end', this._finalize); + const receiver = new Receiver( + this._extensions, + maxPayload, + this._binaryType + ); - this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType); this._sender = new Sender(socket, this._extensions); + this._receiver = receiver; this._socket = socket; - if (head.length > 0) socket.unshift(head); - - socket.on('data', this._receiver.add); - - this._receiver.onmessage = (data) => this.emit('message', data); - this._receiver.onping = (data) => { - this.pong(data, !this._isServer, constants.NOOP); - this.emit('ping', data); - }; - this._receiver.onpong = (data) => this.emit('pong', data); - this._receiver.onclose = (code, reason) => { - // - // Discard any additional data that is received on the socket. - // - this._socket.removeListener('data', this._receiver.add); + receiver[kWebSocket] = this; + socket[kWebSocket] = this; - this._closeFrameReceived = true; - this._closeMessage = reason; - this._closeCode = code; + socket.on('close', socketOnClose); + socket.on('end', socketOnEnd); + socket.on('error', socketOnError); + socket.on('error', NOOP); - if (code === 1005) this.close(); - else this.close(code, reason); - }; - this._receiver.onerror = (error, code) => { - if (this._error) return; + receiver.on('message', receiverOnMessage); + receiver.on('close', receiverOnClose); + receiver.on('error', receiverOnError); + receiver.on('ping', receiverOnPing); + receiver.on('pong', receiverOnPong); - this._closeCode = code; + socket.setTimeout(0); + socket.setNoDelay(); - if (!this._finalized) this.finalize(error); - else this.emit('error', error); - }; + if (head.length > 0) socket.unshift(head); + socket.pipe(receiver); this.readyState = WebSocket.OPEN; this.emit('open'); } /** - * Clean up internal resources and emit the `'close'` event. + * Emit the `'close'` event. * - * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ - finalize (error) { - if (this._finalized) return; - - this.readyState = WebSocket.CLOSING; - this._finalized = true; + emitClose () { + this.readyState = WebSocket.CLOSED; if (!this._socket) { - // - // `error` is always an `Error` instance in this case. - // - this.emit('error', error); - this.readyState = WebSocket.CLOSED; this.emit('close', this._closeCode, this._closeMessage); return; } - clearTimeout(this._closeTimer); - - this._socket.removeListener('data', this._receiver.add); - this._socket.removeListener('close', this._finalize); - this._socket.removeListener('error', this._finalize); - this._socket.removeListener('end', this._finalize); - this._socket.on('error', constants.NOOP); + const err = this._error; - if (error) { - if (error !== true) this._error = error; - this._socket.destroy(); - } else { - this._socket.end(); + if (err) { + this._error = null; + this.emit('error', err); } - this._receiver.cleanup(() => { - const err = this._error; - - if (err) { - this._error = null; - this.emit('error', err); - } - - this.readyState = WebSocket.CLOSED; - - if (this._extensions[PerMessageDeflate.extensionName]) { - this._extensions[PerMessageDeflate.extensionName].cleanup(); - } + if (this._extensions[PerMessageDeflate.extensionName]) { + this._extensions[PerMessageDeflate.extensionName].cleanup(); + } - this.emit('close', this._closeCode, this._closeMessage); - }); + this.emit('close', this._closeCode, this._closeMessage); } /** * Start a closing handshake. * - * +----------+ +-----------+ +----------+ - * + - - -|ws.close()|---->|close frame|-->|ws.close()|- - - - - * +----------+ +-----------+ +----------+ | - * | +----------+ +-----------+ | - * |ws.close()|<----|close frame|<--------+ | - * +----------+ +-----------+ | - * CLOSING | +---+ | CLOSING - * | +---|fin|<------------+ - * | | | +---+ | - * | | +---+ +-------------+ - * | +----------+-->|fin|----->|ws.finalize()| - - + - * | +---+ +-------------+ - * | +-------------+ | - * - - -|ws.finalize()|<--+ - * +-------------+ + * +----------+ +-----------+ +----------+ + * - - -|ws.close()|-->|close frame|-->|ws.close()|- - - + * | +----------+ +-----------+ +----------+ | + * +----------+ +-----------+ | + * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING + * +----------+ +-----------+ | + * | | | +---+ | + * +------------------------+-->|fin| - - - - + * | +---+ | +---+ + * - - - - -|fin|<---------------------+ + * +---+ * * @param {Number} code Status code explaining why the connection is closing * @param {String} data A string explaining why the connection is closing @@ -247,11 +202,8 @@ class WebSocket extends EventEmitter { close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.finalize( - new Error('WebSocket was closed before the connection was established') - ); - return; + const msg = 'WebSocket was closed before the connection was established'; + return abortHandshake(this, this._req, msg); } if (this.readyState === WebSocket.CLOSING) { @@ -269,14 +221,17 @@ class WebSocket extends EventEmitter { this._closeFrameSent = true; - if (!this._finalized) { + if (this._socket.writable) { if (this._closeFrameReceived) this._socket.end(); // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. + // Ensure that the connection is closed even if the closing handshake + // fails. // - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + this._closeTimer = setTimeout( + this._socket.destroy.bind(this._socket), + closeTimeout + ); } }); } @@ -397,14 +352,15 @@ class WebSocket extends EventEmitter { terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.finalize( - new Error('WebSocket was closed before the connection was established') - ); - return; + const msg = 'WebSocket was closed before the connection was established'; + return abortHandshake(this, this._req, msg); } - this.finalize(true); + if (this._socket) { + this.readyState = WebSocket.CLOSING; + this._socket.removeListener('error', socketOnError); + this._socket.destroy(); + } } } @@ -619,30 +575,31 @@ function initAsClient (address, protocols, options) { if (agent) requestOptions.agent = agent; - this._req = httpObj.get(requestOptions); + var req = this._req = httpObj.get(requestOptions); if (options.handshakeTimeout) { - this._req.setTimeout(options.handshakeTimeout, () => { - this._req.abort(); - this.finalize(new Error('Opening handshake has timed out')); - }); + req.setTimeout( + options.handshakeTimeout, + abortHandshake.bind(null, this, req, 'Opening handshake has timed out') + ); } - this._req.on('error', (error) => { + req.on('error', (err) => { if (this._req.aborted) return; - this._req = null; - this.finalize(error); + req = this._req = null; + this.readyState = WebSocket.CLOSING; + this.emit('error', err); + this.emitClose(); }); - this._req.on('response', (res) => { - if (!this.emit('unexpected-response', this._req, res)) { - this._req.abort(); - this.finalize(new Error(`Unexpected server response: ${res.statusCode}`)); - } + req.on('response', (res) => { + if (this.emit('unexpected-response', req, res)) return; + + abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`); }); - this._req.on('upgrade', (res, socket, head) => { + req.on('upgrade', (res, socket, head) => { this.emit('upgrade', res); // @@ -651,15 +608,15 @@ function initAsClient (address, protocols, options) { // if (this.readyState !== WebSocket.CONNECTING) return; - this._req = null; + req = this._req = null; const digest = crypto.createHash('sha1') .update(key + constants.GUID, 'binary') .digest('base64'); if (res.headers['sec-websocket-accept'] !== digest) { - socket.destroy(); - return this.finalize(new Error('Invalid Sec-WebSocket-Accept header')); + abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header'); + return; } const serverProt = res.headers['sec-websocket-protocol']; @@ -675,8 +632,8 @@ function initAsClient (address, protocols, options) { } if (protError) { - socket.destroy(); - return this.finalize(new Error(protError)); + abortHandshake(this, socket, protError); + return; } if (serverProt) this.protocol = serverProt; @@ -694,8 +651,7 @@ function initAsClient (address, protocols, options) { this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } } catch (err) { - socket.destroy(); - this.finalize(new Error('Invalid Sec-WebSocket-Extensions header')); + abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header'); return; } } @@ -703,3 +659,173 @@ function initAsClient (address, protocols, options) { this.setSocket(socket, head, 0); }); } + +/** + * Abort the handshake and emit an error. + * + * @param {WebSocket} websocket The WebSocket instance + * @param {(http.ClientRequest|net.Socket)} stream The request to abort or the + * socket to destroy + * @param {String} message The error message + * @private + */ +function abortHandshake (websocket, stream, message) { + websocket.readyState = WebSocket.CLOSING; + + const err = new Error(message); + Error.captureStackTrace(err, abortHandshake); + + if (stream.setHeader) { + stream.abort(); + stream.once('abort', websocket.emitClose.bind(websocket)); + websocket.emit('error', err); + } else { + stream.destroy(err); + stream.once('error', websocket.emit.bind(websocket, 'error')); + stream.once('close', websocket.emitClose.bind(websocket)); + } +} + +/** + * The listener of the `Receiver` `'close'` event. + * + * @param {Number} code The status code + * @param {String} reason The reason for closing + * @private + */ +function receiverOnClose (code, reason) { + const websocket = this[kWebSocket]; + + websocket._socket.unpipe(websocket._receiver); + websocket._socket.resume(); + + websocket._closeFrameReceived = true; + websocket._closeMessage = reason; + websocket._closeCode = code; + + if (code === 1005) websocket.close(); + else websocket.close(code, reason); +} + +/** + * The listener of the `Receiver` `'error'` event. + * + * @param {(RangeError|Error)} err The emitted error + * @private + */ +function receiverOnError (err) { + const websocket = this[kWebSocket]; + + if (websocket._error) return; + + websocket.readyState = WebSocket.CLOSING; + websocket._socket.removeListener('error', socketOnError); + websocket._closeCode = err[constants.kStatusCode]; + websocket._closeMessage = ''; + websocket.emit('error', err); + websocket._socket.destroy(); +} + +/** + * The listener of the `Receiver` `'message'` event. + * + * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message + * @private + */ +function receiverOnMessage (data) { + this[kWebSocket].emit('message', data); +} + +/** + * The listener of the `Receiver` `'ping'` event. + * + * @param {Buffer} data The data included in the ping frame + * @private + */ +function receiverOnPing (data) { + const websocket = this[kWebSocket]; + + websocket.pong(data, !websocket._isServer, NOOP); + websocket.emit('ping', data); +} + +/** + * The listener of the `Receiver` `'pong'` event. + * + * @param {Buffer} data The data included in the pong frame + * @private + */ +function receiverOnPong (data) { + this[kWebSocket].emit('pong', data); +} + +/** + * The listener of the `net.Socket` `'error'` event. + * + * @param {Error} err The emitted error + * @private + */ +function socketOnError (err) { + const websocket = this[kWebSocket]; + + websocket.readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + + // + // There might be valid buffered data in the socket waiting to be read so we + // can't re-emit this error immediately. + // + websocket._error = err; +} + +/** + * The listener of the `net.Socket` `'end'` event. + * + * @private + */ +function socketOnEnd () { + this[kWebSocket].readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + this.end(); +} + +/** + * The listener of the `net.Socket` `'close'` event. + * + * @private + */ +function socketOnClose () { + const websocket = this[kWebSocket]; + + this.removeListener('error', socketOnError); + this.removeListener('end', socketOnEnd); + this[kWebSocket] = undefined; + + websocket.readyState = WebSocket.CLOSING; + + // + // The close frame might not have been received or the `'end'` event emitted, + // for example, if the socket was destroyed due to an error. Ensure that the + // `receiver` stream is closed after writing any remaining buffered data to + // it. + // + websocket._socket.read(); + websocket._receiver.end(); + + clearTimeout(websocket._closeTimer); + + if ( + websocket._receiver._writableState.finished || + websocket._receiver._writableState.errorEmitted + ) { + websocket.emitClose(); + } else { + const emitClose = () => { + websocket._receiver.removeAllListeners(); + websocket.emitClose(); + }; + + websocket._receiver.on('error', emitClose); + websocket._receiver.on('finish', emitClose); + } +} diff --git a/test/receiver.test.js b/test/receiver.test.js index 4335a12c2..f235719ea 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -5,48 +5,52 @@ const assert = require('assert'); const crypto = require('crypto'); const PerMessageDeflate = require('../lib/permessage-deflate'); +const constants = require('../lib/constants'); const Receiver = require('../lib/receiver'); const Sender = require('../lib/sender'); +const kStatusCode = constants.kStatusCode; const Buffer = safeBuffer.Buffer; describe('Receiver', function () { - it('can parse unmasked text message', function (done) { - const p = new Receiver(); + it('parses an unmasked text message', function (done) { + const receiver = new Receiver(); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, 'Hello'); done(); - }; + }); - p.add(Buffer.from('810548656c6c6f', 'hex')); + receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('can parse close message', function (done) { - const p = new Receiver(); + it('parses a close message', function (done) { + const receiver = new Receiver(); - p.onclose = function (code, data) { + receiver.on('close', (code, data) => { assert.strictEqual(code, 1005); assert.strictEqual(data, ''); done(); - }; + }); - p.add(Buffer.from('8800', 'hex')); + receiver.write(Buffer.from('8800', 'hex')); }); - it('can parse masked text message', function (done) { - const p = new Receiver(); + it('parses a masked text message', function (done) { + const receiver = new Receiver(); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, '5:::{"name":"echo"}'); done(); - }; + }); - p.add(Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex')); + receiver.write( + Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex') + ); }); - it('can parse a masked text message longer than 125 B', function (done) { - const p = new Receiver(); + it('parses a masked text message longer than 125 B', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(200); const list = Sender.frame(Buffer.from(msg), { @@ -59,17 +63,17 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); done(); - }; + }); - p.add(frame.slice(0, 2)); - setImmediate(() => p.add(frame.slice(2))); + receiver.write(frame.slice(0, 2)); + setImmediate(() => receiver.write(frame.slice(2))); }); - it('can parse a really long masked text message', function (done) { - const p = new Receiver(); + it('parses a really long masked text message', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(64 * 1024); const list = Sender.frame(Buffer.from(msg), { @@ -82,16 +86,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a fragmented masked text message of 300 B', function (done) { - const p = new Receiver(); + it('parses a 300 B fragmented masked text message', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(300); const fragment1 = msg.substr(0, 150); @@ -108,17 +112,17 @@ describe('Receiver', function () { Object.assign({ fin: true, opcode: 0x00 }, options) )); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); done(); - }; + }); - p.add(frame1); - p.add(frame2); + receiver.write(frame1); + receiver.write(frame2); }); - it('can parse a ping message', function (done) { - const p = new Receiver(); + it('parses a ping message', function (done) { + const receiver = new Receiver(); const msg = 'Hello'; const list = Sender.frame(Buffer.from(msg), { @@ -131,27 +135,27 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onping = function (data) { + receiver.on('ping', (data) => { assert.strictEqual(data.toString(), msg); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a ping with no data', function (done) { - const p = new Receiver(); + it('parses a ping message with no data', function (done) { + const receiver = new Receiver(); - p.onping = function (data) { + receiver.on('ping', (data) => { assert.ok(data.equals(Buffer.alloc(0))); done(); - }; + }); - p.add(Buffer.from('8900', 'hex')); + receiver.write(Buffer.from('8900', 'hex')); }); - it('can parse a fragmented masked text message of 300 B with a ping in the middle (1/2)', function (done) { - const p = new Receiver(); + it('parses a 300 B fragmented masked text message with a ping in the middle (1/2)', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -175,23 +179,23 @@ describe('Receiver', function () { let gotPing = false; - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); assert.ok(gotPing); done(); - }; - p.onping = function (data) { + }); + receiver.on('ping', (data) => { gotPing = true; assert.strictEqual(data.toString(), pingMessage); - }; + }); - p.add(frame1); - p.add(frame2); - p.add(frame3); + receiver.write(frame1); + receiver.write(frame2); + receiver.write(frame3); }); - it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { - const p = new Receiver(); + it('parses a 300 B fragmented masked text message with a ping in the middle (2/2)', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -225,23 +229,23 @@ describe('Receiver', function () { let gotPing = false; - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); assert.ok(gotPing); done(); - }; - p.onping = function (data) { + }); + receiver.on('ping', (data) => { gotPing = true; assert.strictEqual(data.toString(), pingMessage); - }; + }); for (let i = 0; i < chunks.length; ++i) { - p.add(chunks[i]); + receiver.write(chunks[i]); } }); - it('can parse a 100 B long masked binary message', function (done) { - const p = new Receiver(); + it('parses a 100 B masked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(100); const list = Sender.frame(msg, { @@ -254,16 +258,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a 256 B long masked binary message', function (done) { - const p = new Receiver(); + it('parses a 256 B masked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(256); const list = Sender.frame(msg, { @@ -276,16 +280,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a 200 KiB long masked binary message', function (done) { - const p = new Receiver(); + it('parses a 200 KiB masked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -298,16 +302,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a 200 KiB long unmasked binary message', function (done) { - const p = new Receiver(); + it('parses a 200 KiB unmasked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -320,63 +324,63 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse compressed message', function (done) { + it('parses a compressed message', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, 'Hello'); done(); - }; + }); - perMessageDeflate.compress(buf, true, function (err, compressed) { + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - p.add(Buffer.from([0xc1, compressed.length])); - p.add(compressed); + receiver.write(Buffer.from([0xc1, data.length])); + receiver.write(data); }); }); - it('can parse compressed fragments', function (done) { + it('parses a compressed and fragmented message', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf1 = Buffer.from('foo'); const buf2 = Buffer.from('bar'); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, 'foobar'); done(); - }; + }); - perMessageDeflate.compress(buf1, false, function (err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, fragment1) { if (err) return done(err); - p.add(Buffer.from([0x41, compressed1.length])); - p.add(compressed1); + receiver.write(Buffer.from([0x41, fragment1.length])); + receiver.write(fragment1); - perMessageDeflate.compress(buf2, true, function (err, compressed2) { + perMessageDeflate.compress(buf2, true, function (err, fragment2) { if (err) return done(err); - p.add(Buffer.from([0x80, compressed2.length])); - p.add(compressed2); + receiver.write(Buffer.from([0x80, fragment2.length])); + receiver.write(fragment2); }); }); }); - it('can parse a buffer with thousands of frames', function (done) { + it('parses a buffer with thousands of frames', function (done) { const buf = Buffer.allocUnsafe(40000); for (let i = 0; i < buf.length; i += 2) { @@ -384,335 +388,357 @@ describe('Receiver', function () { buf[i + 1] = 0x00; } - const p = new Receiver(); + const receiver = new Receiver(); let counter = 0; - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, ''); if (++counter === 20000) done(); - }; + }); - p.add(buf); + receiver.write(buf); }); - it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { - const p = new Receiver({}, 10); - let message; + it('resets `totalPayloadLength` only on final frame (unfragmented)', function (done) { + const receiver = new Receiver({}, 10); - p.onmessage = function (msg) { - message = msg; - }; + receiver.on('message', (data) => { + assert.strictEqual(receiver._totalPayloadLength, 0); + assert.strictEqual(data, 'Hello'); + done(); + }); - assert.strictEqual(p._totalPayloadLength, 0); - p.add(Buffer.from('810548656c6c6f', 'hex')); - assert.strictEqual(p._totalPayloadLength, 0); - assert.strictEqual(message, 'Hello'); + assert.strictEqual(receiver._totalPayloadLength, 0); + receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented)', function () { - const p = new Receiver({}, 10); - let message; + it('resets `totalPayloadLength` only on final frame (fragmented)', function (done) { + const receiver = new Receiver({}, 10); - p.onmessage = function (msg) { - message = msg; - }; + receiver.on('message', (data) => { + assert.strictEqual(receiver._totalPayloadLength, 0); + assert.strictEqual(data, 'Hello'); + done(); + }); - assert.strictEqual(p._totalPayloadLength, 0); - p.add(Buffer.from('01024865', 'hex')); - assert.strictEqual(p._totalPayloadLength, 2); - p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p._totalPayloadLength, 0); - assert.strictEqual(message, 'Hello'); + assert.strictEqual(receiver._totalPayloadLength, 0); + receiver.write(Buffer.from('01024865', 'hex')); + assert.strictEqual(receiver._totalPayloadLength, 2); + receiver.write(Buffer.from('80036c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function () { - const p = new Receiver({}, 10); - const data = []; + it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function (done) { + const receiver = new Receiver({}, 10); + let data; - p.onmessage = p.onping = function (buf) { - data.push(buf.toString()); - }; + receiver.on('ping', (buf) => { + assert.strictEqual(receiver._totalPayloadLength, 2); + data = buf.toString(); + }); + receiver.on('message', (buf) => { + assert.strictEqual(receiver._totalPayloadLength, 0); + assert.strictEqual(data, ''); + assert.strictEqual(buf.toString(), 'Hello'); + done(); + }); + + assert.strictEqual(receiver._totalPayloadLength, 0); + receiver.write(Buffer.from('02024865', 'hex')); + receiver.write(Buffer.from('8900', 'hex')); + receiver.write(Buffer.from('80036c6c6f', 'hex')); + }); + + it('ignores any data after a close frame', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const results = []; + const push = results.push.bind(results); - assert.strictEqual(p._totalPayloadLength, 0); - p.add(Buffer.from('02024865', 'hex')); - assert.strictEqual(p._totalPayloadLength, 2); - p.add(Buffer.from('8900', 'hex')); - assert.strictEqual(p._totalPayloadLength, 2); - p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p._totalPayloadLength, 0); - assert.deepStrictEqual(data, ['', 'Hello']); + receiver.on('close', push).on('message', push); + receiver.on('finish', () => { + assert.deepStrictEqual(results, ['', 1005, '']); + done(); + }); + + receiver.write(Buffer.from([0xc1, 0x01, 0x00])); + receiver.write(Buffer.from([0x88, 0x00])); + receiver.write(Buffer.from([0x81, 0x00])); }); - it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { - const p = new Receiver(); + it('emits an error if RSV1 is on and permessage-deflate is disabled', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); + receiver.write(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); }); - it('raises an error when RSV1 is on and opcode is 0', function (done) { + it('emits an error if RSV1 is on and opcode is 0', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x40, 0x00])); + receiver.write(Buffer.from([0x40, 0x00])); }); - it('raises an error when RSV2 is on', function (done) { - const p = new Receiver(); + it('emits an error if RSV2 is on', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0xa2, 0x00])); + receiver.write(Buffer.from([0xa2, 0x00])); }); - it('raises an error when RSV3 is on', function (done) { - const p = new Receiver(); + it('emits an error if RSV3 is on', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x92, 0x00])); + receiver.write(Buffer.from([0x92, 0x00])); }); - it('raises an error if the first frame in a fragmented message has opcode 0', function (done) { - const p = new Receiver(); + it('emits an error if the first frame in a fragmented message has opcode 0', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 0' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x00, 0x00])); + receiver.write(Buffer.from([0x00, 0x00])); }); - it('raises an error if a frame has opcode 1 in the middle of a fragmented message', function (done) { - const p = new Receiver(); + it('emits an error if a frame has opcode 1 in the middle of a fragmented message', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 1' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x01, 0x00])); - p.add(Buffer.from([0x01, 0x00])); + receiver.write(Buffer.from([0x01, 0x00])); + receiver.write(Buffer.from([0x01, 0x00])); }); - it('raises an error if a frame has opcode 2 in the middle of a fragmented message', function (done) { - const p = new Receiver(); + it('emits an error if a frame has opcode 2 in the middle of a fragmented message', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 2' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x01, 0x00])); - p.add(Buffer.from([0x02, 0x00])); + receiver.write(Buffer.from([0x01, 0x00])); + receiver.write(Buffer.from([0x02, 0x00])); }); - it('raises an error when a control frame has the FIN bit off', function (done) { - const p = new Receiver(); + it('emits an error if a control frame has the FIN bit off', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: FIN must be set' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x09, 0x00])); + receiver.write(Buffer.from([0x09, 0x00])); }); - it('raises an error when a control frame has the RSV1 bit on', function (done) { + it('emits an error if a control frame has the RSV1 bit on', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0xc9, 0x00])); + receiver.write(Buffer.from([0xc9, 0x00])); }); - it('raises an error when a control frame has the FIN bit off', function (done) { - const p = new Receiver(); + it('emits an error if a control frame has the FIN bit off', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: FIN must be set' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x09, 0x00])); + receiver.write(Buffer.from([0x09, 0x00])); }); - it('raises an error when a control frame has a payload bigger than 125 B', function (done) { - const p = new Receiver(); + it('emits an error if a control frame has a payload bigger than 125 B', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid payload length 126' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x89, 0x7e])); + receiver.write(Buffer.from([0x89, 0x7e])); }); - it('raises an error when a data frame has a payload bigger than 2^53 - 1 B', function (done) { - const p = new Receiver(); + it('emits an error if a data frame has a payload bigger than 2^53 - 1 B', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Unsupported WebSocket frame: payload length > 2^53 - 1' ); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; + }); - p.add(Buffer.from([0x82, 0x7f])); - setImmediate(() => p.add(Buffer.from([ + receiver.write(Buffer.from([0x82, 0x7f])); + setImmediate(() => receiver.write(Buffer.from([ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]))); }); - it('raises an error if a text frame contains invalid UTF-8 data', function (done) { - const p = new Receiver(); + it('emits an error if a text frame contains invalid UTF-8 data', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid UTF-8 sequence' ); - assert.strictEqual(code, 1007); + assert.strictEqual(err[kStatusCode], 1007); done(); - }; + }); - p.add(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); + receiver.write(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); }); - it('raises an error if a close frame has a payload of 1 B', function (done) { - const p = new Receiver(); + it('emits an error if a close frame has a payload of 1 B', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid payload length 1' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x88, 0x01, 0x00])); + receiver.write(Buffer.from([0x88, 0x01, 0x00])); }); - it('raises an error if a close frame contains an invalid close code', function (done) { - const p = new Receiver(); + it('emits an error if a close frame contains an invalid close code', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid status code 0' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x88, 0x02, 0x00, 0x00])); + receiver.write(Buffer.from([0x88, 0x02, 0x00, 0x00])); }); - it('raises an error if a close frame contains invalid UTF-8 data', function (done) { - const p = new Receiver(); + it('emits an error if a close frame contains invalid UTF-8 data', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid UTF-8 sequence' ); - assert.strictEqual(code, 1007); + assert.strictEqual(err[kStatusCode], 1007); done(); - }; + }); - p.add(Buffer.from([0x88, 0x06, 0x03, 0xef, 0xce, 0xba, 0xe1, 0xbd])); + receiver.write( + Buffer.from([0x88, 0x06, 0x03, 0xef, 0xce, 0xba, 0xe1, 0xbd]) + ); }); - it('raises an error on a 200 KiB long masked binary message when `maxPayload` is 20 KiB', function (done) { - const p = new Receiver({}, 20 * 1024); + it('emits an error if a frame payload length is bigger than `maxPayload`', function (done) { + const receiver = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -725,221 +751,73 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; - - p.add(frame); - }); - - it('raises an error on a 200 KiB long unmasked binary message when `maxPayload` is 20 KiB', function (done) { - const p = new Receiver({}, 20 * 1024); - const msg = crypto.randomBytes(200 * 1024); - - const list = Sender.frame(msg, { - fin: true, - rsv1: false, - opcode: 0x02, - mask: false, - readOnly: true }); - const frame = Buffer.concat(list); - - p.onerror = function (err, code) { - assert.ok(err instanceof RangeError); - assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); - done(); - }; - - p.add(frame); + receiver.write(frame); }); - it('raises an error on a compressed message that exceeds `maxPayload`', function (done) { + it('emits an error if the message length exceeds `maxPayload`', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const receiver = new Receiver({ + 'permessage-deflate': perMessageDeflate + }, 25); const buf = Buffer.from('A'.repeat(50)); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; + }); perMessageDeflate.compress(buf, true, function (err, data) { if (err) return done(err); - p.add(Buffer.from([0xc1, data.length])); - p.add(data); + receiver.write(Buffer.from([0xc1, data.length])); + receiver.write(data); }); }); - it('raises an error if the sum of fragment lengths exceeds `maxPayload`', function (done) { + it('emits an error if the sum of fragment lengths exceeds `maxPayload`', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const receiver = new Receiver({ + 'permessage-deflate': perMessageDeflate + }, 25); const buf = Buffer.from('A'.repeat(15)); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; + }); perMessageDeflate.compress(buf, false, function (err, fragment1) { if (err) return done(err); - p.add(Buffer.from([0x41, fragment1.length])); - p.add(fragment1); + receiver.write(Buffer.from([0x41, fragment1.length])); + receiver.write(fragment1); perMessageDeflate.compress(buf, true, function (err, fragment2) { if (err) return done(err); - p.add(Buffer.from([0x80, fragment2.length])); - p.add(fragment2); - }); - }); - }); - - it('consumes all data before calling `cleanup` callback (1/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const frame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - - p.add(frame); - p.add(frame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, frame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello', 'Hello']); - assert.strictEqual(p.onmessage, null); - done(); - }); - }); - }); - - it('consumes all data before calling `cleanup` callback (2/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onclose = (code, reason) => results.push(code, reason); - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - const closeFrame = Buffer.from([0x88, 0x00]); - - p.add(textFrame); - p.add(textFrame); - p.add(closeFrame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, textFrame.length + closeFrame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello', 'Hello', 1005, '']); - assert.strictEqual(p.onmessage, null); - done(); - }); - }); - }); - - it('consumes all data before calling `cleanup` callback (3/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onerror = (err, code) => results.push(err.message, code); - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - const invalidFrame = Buffer.from([0xa0, 0x00]); - - p.add(textFrame); - p.add(textFrame); - p.add(invalidFrame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, textFrame.length + invalidFrame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, [ - 'Hello', - 'Hello', - 'Invalid WebSocket frame: RSV2 and RSV3 must be clear', - 1002 - ]); - assert.strictEqual(p.onmessage, null); - done(); + receiver.write(Buffer.from([0x80, fragment2.length])); + receiver.write(fragment2); }); }); }); - it('consumes all data before calling `cleanup` callback (4/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - const incompleteFrame = Buffer.from([0x82, 0x0a, 0x00, 0x00]); - - p.add(textFrame); - p.add(incompleteFrame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, incompleteFrame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello']); - assert.strictEqual(p.onmessage, null); - done(); - }); - }); - }); - - it('can emit nodebuffer of fragmented binary message', function (done) { - const p = new Receiver(); + it("honors the 'nodebuffer' binary type", function (done) { + const receiver = new Receiver(); const frags = [ crypto.randomBytes(7321), crypto.randomBytes(137), @@ -947,12 +825,11 @@ describe('Receiver', function () { crypto.randomBytes(3) ]; - p.binaryType = 'nodebuffer'; - p.onmessage = (data) => { + receiver.on('message', (data) => { assert.ok(Buffer.isBuffer(data)); assert.ok(data.equals(Buffer.concat(frags))); done(); - }; + }); frags.forEach((frag, i) => { Sender.frame(frag, { @@ -961,24 +838,24 @@ describe('Receiver', function () { readOnly: true, mask: false, rsv1: false - }).forEach((buf) => p.add(buf)); + }).forEach((buf) => receiver.write(buf)); }); }); - it('can emit arraybuffer of fragmented binary message', function (done) { - const p = new Receiver(); + it("honors the 'arraybuffer' binary type", function (done) { + const receiver = new Receiver(); const frags = [ crypto.randomBytes(19221), crypto.randomBytes(954), crypto.randomBytes(623987) ]; - p._binaryType = 'arraybuffer'; - p.onmessage = (data) => { + receiver._binaryType = 'arraybuffer'; + receiver.on('message', (data) => { assert.ok(data instanceof ArrayBuffer); assert.ok(Buffer.from(data).equals(Buffer.concat(frags))); done(); - }; + }); frags.forEach((frag, i) => { Sender.frame(frag, { @@ -987,12 +864,12 @@ describe('Receiver', function () { readOnly: true, mask: false, rsv1: false - }).forEach((buf) => p.add(buf)); + }).forEach((buf) => receiver.write(buf)); }); }); - it('can emit fragments of fragmented binary message', function (done) { - const p = new Receiver(); + it("honors the 'fragments' binary type", function (done) { + const receiver = new Receiver(); const frags = [ crypto.randomBytes(17), crypto.randomBytes(419872), @@ -1001,11 +878,11 @@ describe('Receiver', function () { crypto.randomBytes(1) ]; - p._binaryType = 'fragments'; - p.onmessage = (data) => { + receiver._binaryType = 'fragments'; + receiver.on('message', (data) => { assert.deepStrictEqual(data, frags); done(); - }; + }); frags.forEach((frag, i) => { Sender.frame(frag, { @@ -1014,7 +891,7 @@ describe('Receiver', function () { readOnly: true, mask: false, rsv1: false - }).forEach((buf) => p.add(buf)); + }).forEach((buf) => receiver.write(buf)); }); }); }); From aa2c423e3ea232afd5b3d184a806fdcd878efb3e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Feb 2018 18:15:37 +0100 Subject: [PATCH 448/489] [minor] Do not use `Readable.prototype.pipe()` --- lib/websocket.js | 100 +++++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index dee00c3cc..672268868 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -132,13 +132,9 @@ class WebSocket extends EventEmitter { receiver[kWebSocket] = this; socket[kWebSocket] = this; - socket.on('close', socketOnClose); - socket.on('end', socketOnEnd); - socket.on('error', socketOnError); - socket.on('error', NOOP); - receiver.on('message', receiverOnMessage); receiver.on('close', receiverOnClose); + receiver.on('drain', receiverOnDrain); receiver.on('error', receiverOnError); receiver.on('ping', receiverOnPing); receiver.on('pong', receiverOnPong); @@ -147,7 +143,12 @@ class WebSocket extends EventEmitter { socket.setNoDelay(); if (head.length > 0) socket.unshift(head); - socket.pipe(receiver); + + socket.on('close', socketOnClose); + socket.on('data', socketOnData); + socket.on('end', socketOnEnd); + socket.on('error', socketOnError); + socket.on('error', NOOP); this.readyState = WebSocket.OPEN; this.emit('open'); @@ -696,7 +697,7 @@ function abortHandshake (websocket, stream, message) { function receiverOnClose (code, reason) { const websocket = this[kWebSocket]; - websocket._socket.unpipe(websocket._receiver); + websocket._socket.removeListener('data', socketOnData); websocket._socket.resume(); websocket._closeFrameReceived = true; @@ -707,6 +708,15 @@ function receiverOnClose (code, reason) { else websocket.close(code, reason); } +/** + * The listener of the `Receiver` `'drain'` event. + * + * @private + */ +function receiverOnDrain () { + this[kWebSocket]._socket.resume(); +} + /** * The listener of the `Receiver` `'error'` event. * @@ -721,7 +731,6 @@ function receiverOnError (err) { websocket.readyState = WebSocket.CLOSING; websocket._socket.removeListener('error', socketOnError); websocket._closeCode = err[constants.kStatusCode]; - websocket._closeMessage = ''; websocket.emit('error', err); websocket._socket.destroy(); } @@ -759,36 +768,6 @@ function receiverOnPong (data) { this[kWebSocket].emit('pong', data); } -/** - * The listener of the `net.Socket` `'error'` event. - * - * @param {Error} err The emitted error - * @private - */ -function socketOnError (err) { - const websocket = this[kWebSocket]; - - websocket.readyState = WebSocket.CLOSING; - this.removeListener('error', socketOnError); - - // - // There might be valid buffered data in the socket waiting to be read so we - // can't re-emit this error immediately. - // - websocket._error = err; -} - -/** - * The listener of the `net.Socket` `'end'` event. - * - * @private - */ -function socketOnEnd () { - this[kWebSocket].readyState = WebSocket.CLOSING; - this.removeListener('error', socketOnError); - this.end(); -} - /** * The listener of the `net.Socket` `'close'` event. * @@ -829,3 +808,48 @@ function socketOnClose () { websocket._receiver.on('finish', emitClose); } } + +/** + * The listener of the `net.Socket` `'data'` event. + * + * @param {Buffer} chunk A chunk of data + * @private + */ +function socketOnData (chunk) { + if (!this[kWebSocket]._receiver.write(chunk)) { + this.pause(); + } +} + +/** + * The listener of the `net.Socket` `'end'` event. + * + * @private + */ +function socketOnEnd () { + const websocket = this[kWebSocket]; + + websocket.readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + websocket._receiver.end(); + this.end(); +} + +/** + * The listener of the `net.Socket` `'error'` event. + * + * @param {Error} err The emitted error + * @private + */ +function socketOnError (err) { + const websocket = this[kWebSocket]; + + websocket.readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + + // + // There might be valid buffered data in the socket waiting to be read so we + // can't re-emit this error immediately. + // + websocket._error = err; +} From a4050db3ad981db1c49daf5ba756ec4f4f71bc44 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Feb 2018 13:54:29 +0100 Subject: [PATCH 449/489] [major] Do not re-emit `net.Socket` errors --- doc/ws.md | 3 +-- lib/websocket.js | 55 +++++++++++++++++------------------------- test/websocket.test.js | 53 +++++++++++++++++----------------------- 3 files changed, 45 insertions(+), 66 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 464df38b0..f505ed5e2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -244,8 +244,7 @@ human-readable string explaining why the connection has been closed. - `error` {Error} -Emitted when an error occurs. Errors from the underlying `net.Socket` are -forwarded here. +Emitted when an error occurs. ### Event: 'message' diff --git a/lib/websocket.js b/lib/websocket.js index 672268868..f3b460e30 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -17,7 +17,6 @@ const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const kWebSocket = constants.kWebSocket; const protocolVersions = [8, 13]; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. -const NOOP = constants.NOOP; /** * Class representing a WebSocket. @@ -49,7 +48,6 @@ class WebSocket extends EventEmitter { this._receiver = null; this._sender = null; this._socket = null; - this._error = null; if (address !== null) { if (!protocols) { @@ -132,10 +130,10 @@ class WebSocket extends EventEmitter { receiver[kWebSocket] = this; socket[kWebSocket] = this; - receiver.on('message', receiverOnMessage); receiver.on('close', receiverOnClose); receiver.on('drain', receiverOnDrain); receiver.on('error', receiverOnError); + receiver.on('message', receiverOnMessage); receiver.on('ping', receiverOnPing); receiver.on('pong', receiverOnPong); @@ -148,7 +146,6 @@ class WebSocket extends EventEmitter { socket.on('data', socketOnData); socket.on('end', socketOnEnd); socket.on('error', socketOnError); - socket.on('error', NOOP); this.readyState = WebSocket.OPEN; this.emit('open'); @@ -167,17 +164,11 @@ class WebSocket extends EventEmitter { return; } - const err = this._error; - - if (err) { - this._error = null; - this.emit('error', err); - } - if (this._extensions[PerMessageDeflate.extensionName]) { this._extensions[PerMessageDeflate.extensionName].cleanup(); } + this._receiver.removeAllListeners(); this.emit('close', this._closeCode, this._closeMessage); } @@ -359,7 +350,6 @@ class WebSocket extends EventEmitter { if (this._socket) { this.readyState = WebSocket.CLOSING; - this._socket.removeListener('error', socketOnError); this._socket.destroy(); } } @@ -726,15 +716,21 @@ function receiverOnDrain () { function receiverOnError (err) { const websocket = this[kWebSocket]; - if (websocket._error) return; - websocket.readyState = WebSocket.CLOSING; - websocket._socket.removeListener('error', socketOnError); websocket._closeCode = err[constants.kStatusCode]; websocket.emit('error', err); websocket._socket.destroy(); } +/** + * The listener of the `Receiver` `'finish'` event. + * + * @private + */ +function receiverOnFinish () { + this[kWebSocket].emitClose(); +} + /** * The listener of the `Receiver` `'message'` event. * @@ -754,7 +750,7 @@ function receiverOnMessage (data) { function receiverOnPing (data) { const websocket = this[kWebSocket]; - websocket.pong(data, !websocket._isServer, NOOP); + websocket.pong(data, !websocket._isServer, constants.NOOP); websocket.emit('ping', data); } @@ -776,7 +772,8 @@ function receiverOnPong (data) { function socketOnClose () { const websocket = this[kWebSocket]; - this.removeListener('error', socketOnError); + this.removeListener('close', socketOnClose); + this.removeListener('data', socketOnData); this.removeListener('end', socketOnEnd); this[kWebSocket] = undefined; @@ -799,13 +796,8 @@ function socketOnClose () { ) { websocket.emitClose(); } else { - const emitClose = () => { - websocket._receiver.removeAllListeners(); - websocket.emitClose(); - }; - - websocket._receiver.on('error', emitClose); - websocket._receiver.on('finish', emitClose); + websocket._receiver.on('error', receiverOnFinish); + websocket._receiver.on('finish', receiverOnFinish); } } @@ -830,7 +822,6 @@ function socketOnEnd () { const websocket = this[kWebSocket]; websocket.readyState = WebSocket.CLOSING; - this.removeListener('error', socketOnError); websocket._receiver.end(); this.end(); } @@ -838,18 +829,16 @@ function socketOnEnd () { /** * The listener of the `net.Socket` `'error'` event. * - * @param {Error} err The emitted error * @private */ -function socketOnError (err) { +function socketOnError () { const websocket = this[kWebSocket]; - websocket.readyState = WebSocket.CLOSING; this.removeListener('error', socketOnError); + this.on('error', constants.NOOP); - // - // There might be valid buffered data in the socket waiting to be read so we - // can't re-emit this error immediately. - // - websocket._error = err; + if (websocket) { + websocket.readyState = WebSocket.CLOSING; + this.destroy(); + } } diff --git a/test/websocket.test.js b/test/websocket.test.js index 3801fdd9c..66e59acd5 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -8,7 +8,6 @@ const crypto = require('crypto'); const https = require('https'); const http = require('http'); const dns = require('dns'); -const net = require('net'); const fs = require('fs'); const os = require('os'); @@ -393,6 +392,28 @@ describe('WebSocket', function () { }); }); + it('does not re-emit `net.Socket` errors', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + + ws.on('open', () => { + ws._socket.on('error', (err) => { + assert.ok(err instanceof Error); + assert.ok(err.message.startsWith('write E')); + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + + for (const client of wss.clients) client.terminate(); + ws.send('foo'); + ws.send('bar'); + }); + }); + }); + it("emits an 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1160,36 +1181,6 @@ describe('WebSocket', function () { }); }); - it('emits an error if the close frame can not be sent', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const socket = net.createConnection(wss.address().port, () => { - socket.write( - 'GET / HTTP/1.1\r\n' + - 'Host: localhost\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - 'Sec-WebSocket-Key: qqFVFwaCnSMXiqfezY/AZQ==\r\n' + - 'Sec-WebSocket-Version: 13\r\n' + - '\r\n' - ); - socket.destroy(); - }); - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.ok(err.message.startsWith('write E')); - ws.on('close', (code, message) => { - assert.strictEqual(message, ''); - assert.strictEqual(code, 1006); - wss.close(done); - }); - }); - ws.close(); - }); - }); - }); - it('sends the close status code only when necessary', function (done) { let sent; const wss = new WebSocket.Server({ port: 0 }, () => { From 3913c594b403513feb670ba9834f7e1e9636c2c6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 2 Mar 2018 11:57:20 +0100 Subject: [PATCH 450/489] [benchmark] Fix parser benchmark --- bench/parser.benchmark.js | 61 +++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index ca4282b4e..78a2888fa 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -10,14 +10,6 @@ const Receiver = WebSocket.Receiver; const Sender = WebSocket.Sender; const Buffer = safeBuffer.Buffer; -// -// Override the `cleanup` method to make the "close message" test work as -// expected. -// -Receiver.prototype.cleanup = function () { - this._state = 0; -}; - const options = { fin: true, rsv1: false, @@ -41,7 +33,6 @@ const pingFrame1 = Buffer.concat(Sender.frame( const textFrame = Buffer.from('819461616161' + '61'.repeat(20), 'hex'); const pingFrame2 = Buffer.from('8900', 'hex'); -const closeFrame = Buffer.from('8800', 'hex'); const binaryFrame1 = createBinaryFrame(125); const binaryFrame2 = createBinaryFrame(65535); const binaryFrame3 = createBinaryFrame(200 * 1024); @@ -50,16 +41,48 @@ const binaryFrame4 = createBinaryFrame(1024 * 1024); const suite = new benchmark.Suite(); const receiver = new Receiver(); -receiver.onmessage = receiver.onclose = receiver.onping = () => {}; - -suite.add('ping frame (5 bytes payload)', () => receiver.add(pingFrame1)); -suite.add('ping frame (no payload)', () => receiver.add(pingFrame2)); -suite.add('close frame (no payload)', () => receiver.add(closeFrame)); -suite.add('text frame (20 bytes payload)', () => receiver.add(textFrame)); -suite.add('binary frame (125 bytes payload)', () => receiver.add(binaryFrame1)); -suite.add('binary frame (65535 bytes payload)', () => receiver.add(binaryFrame2)); -suite.add('binary frame (200 KiB payload)', () => receiver.add(binaryFrame3)); -suite.add('binary frame (1 MiB payload)', () => receiver.add(binaryFrame4)); +suite.add('ping frame (5 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(pingFrame1, deferred.resolve.bind(deferred)); + } +}); +suite.add('ping frame (no payload)', { + defer: true, + fn: (deferred) => { + receiver.write(pingFrame2, deferred.resolve.bind(deferred)); + } +}); +suite.add('text frame (20 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(textFrame, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (125 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame1, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (65535 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame2, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (200 KiB payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame3, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (1 MiB payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame4, deferred.resolve.bind(deferred)); + } +}); suite.on('cycle', (e) => console.log(e.target.toString())); From 14538dbf965e73e040d71069d39fdefb9a19fcba Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Mar 2018 07:27:20 +0100 Subject: [PATCH 451/489] [major] Fix subprotocol handling (#1312) Do not close the connection if the server does not agree to any of the client's requested subprotocols. Fixes #1296 --- doc/ws.md | 13 +++++++----- lib/websocket-server.js | 38 ++++++++++++++++++----------------- test/websocket-server.test.js | 24 +--------------------- test/websocket.test.js | 7 ++++--- 4 files changed, 33 insertions(+), 49 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index f505ed5e2..18e707ecf 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -50,16 +50,19 @@ if `verifyClient` is provided with two arguments then those are: reason phrase. -If `handleProtocols` is not set then the handshake is automatically accepted, -otherwise the function takes two arguments: +`handleProtocols` takes two arguments: - `protocols` {Array} The list of WebSocket subprotocols indicated by the client in the `Sec-WebSocket-Protocol` header. - `request` {http.IncomingMessage} The client HTTP GET request. -If returned value is `false` then the handshake is rejected with the HTTP 401 -status code, otherwise the returned value sets the value of the -`Sec-WebSocket-Protocol` header in the HTTP 101 response. +The returned value sets the value of the `Sec-WebSocket-Protocol` header in the +HTTP 101 response. If returned value is `false` the header is not added in the +response. + +If `handleProtocols` is not set then the first of the client's requested +subprotocols is used. + `perMessageDeflate` can be used to control the behavior of [permessage-deflate extension][permessage-deflate]. diff --git a/lib/websocket-server.js b/lib/websocket-server.js index ee0913fb5..e1da56e6d 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -196,18 +196,6 @@ class WebSocketServer extends EventEmitter { } } - var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); - - // - // Optionally call external protocol selection handler. - // - if (this.options.handleProtocols) { - protocol = this.options.handleProtocols(protocol, req); - if (protocol === false) return abortConnection(socket, 401); - } else { - protocol = protocol[0]; - } - // // Optionally call external client verification handler. // @@ -222,7 +210,7 @@ class WebSocketServer extends EventEmitter { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - this.completeUpgrade(protocol, extensions, req, socket, head, cb); + this.completeUpgrade(extensions, req, socket, head, cb); }); return; } @@ -230,13 +218,12 @@ class WebSocketServer extends EventEmitter { if (!this.options.verifyClient(info)) return abortConnection(socket, 401); } - this.completeUpgrade(protocol, extensions, req, socket, head, cb); + this.completeUpgrade(extensions, req, socket, head, cb); } /** * Upgrade the connection to WebSocket. * - * @param {String} protocol The chosen subprotocol * @param {Object} extensions The accepted extensions * @param {http.IncomingMessage} req The request object * @param {net.Socket} socket The network socket between the server and client @@ -244,7 +231,7 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @private */ - completeUpgrade (protocol, extensions, req, socket, head, cb) { + completeUpgrade (extensions, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // @@ -262,11 +249,26 @@ class WebSocketServer extends EventEmitter { ]; const ws = new WebSocket(null); + var protocol = req.headers['sec-websocket-protocol']; if (protocol) { - headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - ws.protocol = protocol; + protocol = protocol.trim().split(/ *, */); + + // + // Optionally call external protocol selection handler. + // + if (this.options.handleProtocols) { + protocol = this.options.handleProtocols(protocol, req); + } else { + protocol = protocol[0]; + } + + if (protocol) { + headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + ws.protocol = protocol; + } } + if (extensions[PerMessageDeflate.extensionName]) { const params = extensions[PerMessageDeflate.extensionName].params; const value = extension.format({ diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 1e9438803..9ef7f2ec3 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -649,7 +649,7 @@ describe('WebSocketServer', function () { }); describe('`handleProtocols`', function () { - it('can select the last protocol', function (done) { + it('allows to select a subprotocol', function (done) { const handleProtocols = (protocols, request) => { assert.ok(request instanceof http.IncomingMessage); assert.strictEqual(request.url, '/'); @@ -667,28 +667,6 @@ describe('WebSocketServer', function () { }); }); }); - - it('closes the connection if return value is `false`', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (protocols) => false, - port: 0 - }, () => { - const req = http.get({ - port: wss.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(done); - }); - }); - }); }); it('emits the `headers` event', function (done) { diff --git a/test/websocket.test.js b/test/websocket.test.js index 66e59acd5..6c91bfa19 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -614,9 +614,10 @@ describe('WebSocket', function () { }); it('fails if server sends a subprotocol when none was requested', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: () => 'foo', - server + const wss = new WebSocket.Server({ server }); + + wss.on('headers', (headers) => { + headers.push('Sec-WebSocket-Protocol: foo'); }); const ws = new WebSocket(`ws://localhost:${server.address().port}`); From 3f80ab717c3bf1a7a6fd175a1c3bfb8e76b22976 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 4 Mar 2018 13:36:53 +0100 Subject: [PATCH 452/489] [major] Drop support for Node.js < 4.5.0 (#1313) --- .travis.yml | 1 - appveyor.yml | 1 - bench/parser.benchmark.js | 2 -- bench/speed.js | 2 -- lib/buffer-util.js | 4 ---- lib/constants.js | 4 ---- lib/permessage-deflate.js | 3 --- lib/receiver.js | 3 --- lib/sender.js | 3 --- lib/websocket-server.js | 3 --- package.json | 3 +-- test/permessage-deflate.test.js | 3 --- test/receiver.test.js | 2 -- test/sender.test.js | 3 --- test/websocket-server.test.js | 3 --- test/websocket.test.js | 3 --- 16 files changed, 1 insertion(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index e21953a5a..668010445 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,5 @@ node_js: - "8" - "6" - "4" - - "4.2.2" after_success: - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index 394bc1e89..ce931fe9f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,6 @@ environment: - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - - nodejs_version: "4.2.2" platform: - x86 - x64 diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 78a2888fa..43cc1ea4e 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const benchmark = require('benchmark'); const crypto = require('crypto'); @@ -8,7 +7,6 @@ const WebSocket = require('..'); const Receiver = WebSocket.Receiver; const Sender = WebSocket.Sender; -const Buffer = safeBuffer.Buffer; const options = { fin: true, diff --git a/bench/speed.js b/bench/speed.js index 75dfd5a80..e5d1591dd 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -1,11 +1,9 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const cluster = require('cluster'); const WebSocket = require('..'); -const Buffer = safeBuffer.Buffer; const port = 8181; if (cluster.isMaster) { diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 5ab9e289f..38e16df3f 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -1,9 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); - -const Buffer = safeBuffer.Buffer; - /** * Merges an array of buffers into a new buffer. * diff --git a/lib/constants.js b/lib/constants.js index 613a6766c..4082981f8 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,9 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); - -const Buffer = safeBuffer.Buffer; - module.exports = { BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'], GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 05832f28f..0d54f035b 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -1,14 +1,11 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const Limiter = require('async-limiter'); const zlib = require('zlib'); const bufferUtil = require('./buffer-util'); const constants = require('./constants'); -const Buffer = safeBuffer.Buffer; - const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); diff --git a/lib/receiver.js b/lib/receiver.js index a66cc10ee..da9b1a1a5 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const stream = require('stream'); const PerMessageDeflate = require('./permessage-deflate'); @@ -8,8 +7,6 @@ const bufferUtil = require('./buffer-util'); const validation = require('./validation'); const constants = require('./constants'); -const Buffer = safeBuffer.Buffer; - const GET_INFO = 0; const GET_PAYLOAD_LENGTH_16 = 1; const GET_PAYLOAD_LENGTH_64 = 2; diff --git a/lib/sender.js b/lib/sender.js index b3dacbc41..42cbb890f 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const crypto = require('crypto'); const PerMessageDeflate = require('./permessage-deflate'); @@ -8,8 +7,6 @@ const bufferUtil = require('./buffer-util'); const validation = require('./validation'); const constants = require('./constants'); -const Buffer = safeBuffer.Buffer; - /** * HyBi Sender implementation. */ diff --git a/lib/websocket-server.js b/lib/websocket-server.js index e1da56e6d..ce1d6c242 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const EventEmitter = require('events'); const crypto = require('crypto'); const http = require('http'); @@ -11,8 +10,6 @@ const extension = require('./extension'); const constants = require('./constants'); const WebSocket = require('./websocket'); -const Buffer = safeBuffer.Buffer; - /** * Class representing a WebSocket server. * diff --git a/package.json b/package.json index 0623d2437..e82afba69 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,7 @@ "lint": "eslint ." }, "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0" + "async-limiter": "~1.0.0" }, "devDependencies": { "benchmark": "~2.1.2", diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 038af2df7..ab2a9ab11 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -1,13 +1,10 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const PerMessageDeflate = require('../lib/permessage-deflate'); const extension = require('../lib/extension'); -const Buffer = safeBuffer.Buffer; - describe('PerMessageDeflate', function () { describe('#offer', function () { it('creates an offer', function () { diff --git a/test/receiver.test.js b/test/receiver.test.js index f235719ea..0b7c87a57 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); @@ -10,7 +9,6 @@ const Receiver = require('../lib/receiver'); const Sender = require('../lib/sender'); const kStatusCode = constants.kStatusCode; -const Buffer = safeBuffer.Buffer; describe('Receiver', function () { it('parses an unmasked text message', function (done) { diff --git a/test/sender.test.js b/test/sender.test.js index 69d4012e2..623ce5954 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -1,13 +1,10 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const PerMessageDeflate = require('../lib/permessage-deflate'); const Sender = require('../lib/sender'); -const Buffer = safeBuffer.Buffer; - describe('Sender', function () { describe('.frame', function () { it('does not mutate the input buffer if data is `readOnly`', function () { diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 9ef7f2ec3..1e949c29f 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -2,7 +2,6 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); @@ -12,8 +11,6 @@ const fs = require('fs'); const WebSocket = require('..'); -const Buffer = safeBuffer.Buffer; - describe('WebSocketServer', function () { describe('#ctor', function () { it('throws an error if no option object is passed', function () { diff --git a/test/websocket.test.js b/test/websocket.test.js index 6c91bfa19..b5edea86e 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -2,7 +2,6 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); @@ -14,8 +13,6 @@ const os = require('os'); const constants = require('../lib/constants'); const WebSocket = require('..'); -const Buffer = safeBuffer.Buffer; - class CustomAgent extends http.Agent { addRequest () {} } From fb05059620609d5ff71cee87c5d85e5ed10534ea Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 07:58:05 +0100 Subject: [PATCH 453/489] [test] Fix faulty test --- test/websocket-server.test.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 1e949c29f..402cd827b 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -494,10 +494,7 @@ describe('WebSocketServer', function () { const wss = new WebSocket.Server({ verifyClient: (info) => { assert.strictEqual(info.origin, 'https://example.com'); - assert.strictEqual( - info.req.headers['sec-websocket-key'], - 'dGhlIHNhbXBsZSBub25jZQ==' - ); + assert.strictEqual(info.req.headers.foo, 'bar'); assert.ok(info.secure, true); return true; }, @@ -511,10 +508,7 @@ describe('WebSocketServer', function () { server.listen(0, () => { const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - headers: { - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - Origin: 'https://example.com' - }, + headers: { Origin: 'https://example.com', foo: 'bar' }, rejectUnauthorized: false }); }); From 7fb82a3adaa5dcaf5bf4e244ef406f55c7eac4d2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 10:22:06 +0100 Subject: [PATCH 454/489] [fix] Rename the `'close'` event to `'conclude'` Avoid collisions with the `stream.Writable` `'close'` event. --- lib/receiver.js | 8 ++++---- lib/websocket.js | 10 +++++----- test/receiver.test.js | 36 +++++++++++++++++++++++------------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index da9b1a1a5..ab2d68c8d 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -23,11 +23,11 @@ class Receiver extends stream.Writable { /** * Creates a Receiver instance. * + * @param {String} binaryType The type for binary data * @param {Object} extensions An object containing the negotiated extensions * @param {Number} maxPayload The maximum allowed message length - * @param {String} binaryType The type for binary data */ - constructor (extensions, maxPayload, binaryType) { + constructor (binaryType, extensions, maxPayload) { super(); this._binaryType = binaryType || constants.BINARY_TYPES[0]; @@ -430,7 +430,7 @@ class Receiver extends stream.Writable { this._loop = false; if (data.length === 0) { - this.emit('close', 1005, ''); + this.emit('conclude', 1005, ''); this.end(); } else if (data.length === 1) { return error(RangeError, 'invalid payload length 1', true, 1002); @@ -447,7 +447,7 @@ class Receiver extends stream.Writable { return error(Error, 'invalid UTF-8 sequence', true, 1007); } - this.emit('close', code, buf.toString()); + this.emit('conclude', code, buf.toString()); this.end(); } diff --git a/lib/websocket.js b/lib/websocket.js index f3b460e30..87f3606fb 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -118,9 +118,9 @@ class WebSocket extends EventEmitter { */ setSocket (socket, head, maxPayload) { const receiver = new Receiver( + this._binaryType, this._extensions, - maxPayload, - this._binaryType + maxPayload ); this._sender = new Sender(socket, this._extensions); @@ -130,7 +130,7 @@ class WebSocket extends EventEmitter { receiver[kWebSocket] = this; socket[kWebSocket] = this; - receiver.on('close', receiverOnClose); + receiver.on('conclude', receiverOnConclude); receiver.on('drain', receiverOnDrain); receiver.on('error', receiverOnError); receiver.on('message', receiverOnMessage); @@ -678,13 +678,13 @@ function abortHandshake (websocket, stream, message) { } /** - * The listener of the `Receiver` `'close'` event. + * The listener of the `Receiver` `'conclude'` event. * * @param {Number} code The status code * @param {String} reason The reason for closing * @private */ -function receiverOnClose (code, reason) { +function receiverOnConclude (code, reason) { const websocket = this[kWebSocket]; websocket._socket.removeListener('data', socketOnData); diff --git a/test/receiver.test.js b/test/receiver.test.js index 0b7c87a57..21497e1ca 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -25,7 +25,7 @@ describe('Receiver', function () { it('parses a close message', function (done) { const receiver = new Receiver(); - receiver.on('close', (code, data) => { + receiver.on('conclude', (code, data) => { assert.strictEqual(code, 1005); assert.strictEqual(data, ''); done(); @@ -334,7 +334,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); const buf = Buffer.from('Hello'); receiver.on('message', (data) => { @@ -354,7 +356,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); const buf1 = Buffer.from('foo'); const buf2 = Buffer.from('bar'); @@ -398,7 +402,7 @@ describe('Receiver', function () { }); it('resets `totalPayloadLength` only on final frame (unfragmented)', function (done) { - const receiver = new Receiver({}, 10); + const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { assert.strictEqual(receiver._totalPayloadLength, 0); @@ -411,7 +415,7 @@ describe('Receiver', function () { }); it('resets `totalPayloadLength` only on final frame (fragmented)', function (done) { - const receiver = new Receiver({}, 10); + const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { assert.strictEqual(receiver._totalPayloadLength, 0); @@ -426,7 +430,7 @@ describe('Receiver', function () { }); it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function (done) { - const receiver = new Receiver({}, 10); + const receiver = new Receiver(undefined, {}, 10); let data; receiver.on('ping', (buf) => { @@ -450,11 +454,13 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); const results = []; const push = results.push.bind(results); - receiver.on('close', push).on('message', push); + receiver.on('conclude', push).on('message', push); receiver.on('finish', () => { assert.deepStrictEqual(results, ['', 1005, '']); done(); @@ -485,7 +491,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); receiver.on('error', (err) => { assert.ok(err instanceof RangeError); @@ -602,7 +610,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); receiver.on('error', (err) => { assert.ok(err instanceof RangeError); @@ -736,7 +746,7 @@ describe('Receiver', function () { }); it('emits an error if a frame payload length is bigger than `maxPayload`', function (done) { - const receiver = new Receiver({}, 20 * 1024); + const receiver = new Receiver(undefined, {}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -763,7 +773,7 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ + const receiver = new Receiver(undefined, { 'permessage-deflate': perMessageDeflate }, 25); const buf = Buffer.from('A'.repeat(50)); @@ -787,7 +797,7 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ + const receiver = new Receiver(undefined, { 'permessage-deflate': perMessageDeflate }, 25); const buf = Buffer.from('A'.repeat(15)); From 7abe823874f713d8d60a315a7c0ea877b6f544a8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 11:41:51 +0100 Subject: [PATCH 455/489] [minor] Rename `abortConnection` to `abortHandshake` --- lib/websocket-server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index ce1d6c242..72618d028 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -169,7 +169,7 @@ class WebSocketServer extends EventEmitter { !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) || !this.shouldHandle(req) ) { - return abortConnection(socket, 400); + return abortHandshake(socket, 400); } if (this.options.perMessageDeflate) { @@ -189,7 +189,7 @@ class WebSocketServer extends EventEmitter { extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } } catch (err) { - return abortConnection(socket, 400); + return abortHandshake(socket, 400); } } @@ -205,14 +205,14 @@ class WebSocketServer extends EventEmitter { if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, (verified, code, message) => { - if (!verified) return abortConnection(socket, code || 401, message); + if (!verified) return abortHandshake(socket, code || 401, message); this.completeUpgrade(extensions, req, socket, head, cb); }); return; } - if (!this.options.verifyClient(info)) return abortConnection(socket, 401); + if (!this.options.verifyClient(info)) return abortHandshake(socket, 401); } this.completeUpgrade(extensions, req, socket, head, cb); @@ -332,7 +332,7 @@ function socketOnError () { * @param {String} [message] The HTTP response body * @private */ -function abortConnection (socket, code, message) { +function abortHandshake (socket, code, message) { if (socket.writable) { message = message || http.STATUS_CODES[code]; socket.write( From d3af50627de62b0d8b9c42d915e8c6a426238363 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 15:11:56 +0100 Subject: [PATCH 456/489] [dist] 5.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e82afba69..36a267567 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "4.1.0", + "version": "5.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 2a55ddae7c6274d87d4bd7e26a9d0289778ef45c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 8 Mar 2018 21:09:04 +0100 Subject: [PATCH 457/489] chore(package): update eslint-plugin-promise to version 3.7.0 (#1321) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36a267567..8093d140e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.9.0", "eslint-plugin-node": "~6.0.0", - "eslint-plugin-promise": "~3.6.0", + "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.0.0", "nyc": "~11.4.1", From 23f5957d908d08569fb5eee688bc34935556f392 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sun, 11 Mar 2018 07:54:23 +0100 Subject: [PATCH 458/489] [fix] Remove buffer `noAssert` argument (#1324) The `noAssert` argument is not supported in Node.js 10.x. Refs: https://github.com/nodejs/node/pull/18395 --- lib/receiver.js | 8 ++++---- lib/sender.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index ab2d68c8d..81dc0bf8a 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -235,7 +235,7 @@ class Receiver extends stream.Writable { return; } - this._payloadLength = this.consume(2).readUInt16BE(0, true); + this._payloadLength = this.consume(2).readUInt16BE(0); return this.haveLength(); } @@ -252,7 +252,7 @@ class Receiver extends stream.Writable { } const buf = this.consume(8); - const num = buf.readUInt32BE(0, true); + const num = buf.readUInt32BE(0); // // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned @@ -268,7 +268,7 @@ class Receiver extends stream.Writable { ); } - this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); + this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4); return this.haveLength(); } @@ -435,7 +435,7 @@ class Receiver extends stream.Writable { } else if (data.length === 1) { return error(RangeError, 'invalid payload length 1', true, 1002); } else { - const code = data.readUInt16BE(0, true); + const code = data.readUInt16BE(0); if (!validation.isValidStatusCode(code)) { return error(RangeError, `invalid status code ${code}`, true, 1002); diff --git a/lib/sender.js b/lib/sender.js index 42cbb890f..060e55392 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -61,10 +61,10 @@ class Sender { if (options.rsv1) target[0] |= 0x40; if (payloadLength === 126) { - target.writeUInt16BE(data.length, 2, true); + target.writeUInt16BE(data.length, 2); } else if (payloadLength === 127) { - target.writeUInt32BE(0, 2, true); - target.writeUInt32BE(data.length, 6, true); + target.writeUInt32BE(0, 2); + target.writeUInt32BE(data.length, 6); } if (!options.mask) { @@ -112,10 +112,10 @@ class Sender { throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); - buf.writeUInt16BE(code, 0, true); + buf.writeUInt16BE(code, 0); } else { buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); - buf.writeUInt16BE(code, 0, true); + buf.writeUInt16BE(code, 0); buf.write(data, 2); } From 57d3dbb9e1b6d81132bbf2c18a35f09eab783a87 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 13 Mar 2018 08:13:49 +0100 Subject: [PATCH 459/489] chore(package): update nyc to version 11.6.0 (#1326) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8093d140e..3bdb75b39 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.0.0", - "nyc": "~11.4.1", + "nyc": "~11.6.0", "utf-8-validate": "~4.0.0" } } From 1504d6713138431dacd67407c115290c0985899c Mon Sep 17 00:00:00 2001 From: Arnout Kazemier <3rd-Eden@users.noreply.github.com> Date: Fri, 16 Mar 2018 15:02:25 +0100 Subject: [PATCH 460/489] [fix] Allow URL instances as URL in WebSocket constructor (#1329) Fixes #1143 --- doc/ws.md | 2 +- lib/websocket.js | 39 +++++++++++++++++++---------------- test/websocket.test.js | 46 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 18e707ecf..cc634ad98 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -183,7 +183,7 @@ This class represents a WebSocket. It extends the `EventEmitter`. ### new WebSocket(address[, protocols][, options]) -- `address` {String} The URL to which to connect. +- `address` {String|url.Url|url.URL} The URL to which to connect. - `protocols` {String|Array} The list of subprotocols. - `options` {Object} - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header. diff --git a/lib/websocket.js b/lib/websocket.js index 87f3606fb..53aa1ebc9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -27,7 +27,7 @@ class WebSocket extends EventEmitter { /** * Create a new `WebSocket`. * - * @param {String} address The URL to which to connect + * @param {(String|url.Url|url.URL)} address The URL to which to connect * @param {(String|String[])} protocols The subprotocols * @param {Object} options Connection options */ @@ -404,7 +404,7 @@ module.exports = WebSocket; /** * Initialize a WebSocket client. * - * @param {String} address The URL to which to connect + * @param {(String|url.Url|url.URL)} address The URL to which to connect * @param {String[]} protocols The list of subprotocols * @param {Object} options Connection options * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header @@ -463,24 +463,35 @@ function initAsClient (address, protocols, options) { } this._isServer = false; - this.url = address; - const serverUrl = url.parse(address); + var serverUrl; + + if (typeof address === 'object' && address.href !== undefined) { + serverUrl = address; + this.url = address.href; + } else { + serverUrl = url.parse(address); + this.url = address; + } + const isUnixSocket = serverUrl.protocol === 'ws+unix:'; - if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) { - throw new Error(`Invalid URL: ${address}`); + if (!serverUrl.host && (!isUnixSocket || !serverUrl.pathname)) { + throw new Error(`Invalid URL: ${this.url}`); } const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; + const path = serverUrl.search + ? `${serverUrl.pathname || '/'}${serverUrl.search}` + : serverUrl.pathname || '/'; var perMessageDeflate; const requestOptions = { port: serverUrl.port || (isSecure ? 443 : 80), host: serverUrl.hostname, - path: '/', + path: path, headers: { 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key, @@ -511,24 +522,18 @@ function initAsClient (address, protocols, options) { } if (options.host) requestOptions.headers.Host = options.host; if (serverUrl.auth) requestOptions.auth = serverUrl.auth; + else if (serverUrl.username || serverUrl.password) { + requestOptions.auth = `${serverUrl.username}:${serverUrl.password}`; + } if (options.localAddress) requestOptions.localAddress = options.localAddress; if (options.family) requestOptions.family = options.family; if (isUnixSocket) { - const parts = serverUrl.path.split(':'); + const parts = path.split(':'); requestOptions.socketPath = parts[0]; requestOptions.path = parts[1]; - } else if (serverUrl.path) { - // - // Make sure that path starts with `/`. - // - if (serverUrl.path.charAt(0) !== '/') { - requestOptions.path = `/${serverUrl.path}`; - } else { - requestOptions.path = serverUrl.path; - } } var agent = options.agent; diff --git a/test/websocket.test.js b/test/websocket.test.js index b5edea86e..79d0addec 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -7,6 +7,7 @@ const crypto = require('crypto'); const https = require('https'); const http = require('http'); const dns = require('dns'); +const url = require('url'); const fs = require('fs'); const os = require('os'); @@ -26,6 +27,30 @@ describe('WebSocket', function () { ); }); + it('accepts `url.Url` objects as url', function (done) { + const agent = new CustomAgent(); + + agent.addRequest = (req) => { + assert.strictEqual(req.path, '/'); + done(); + }; + + const ws = new WebSocket(url.parse('ws://localhost'), { agent }); + }); + + it('accepts `url.URL` objects as url', function (done) { + if (!url.URL) return this.skip(); + + const agent = new CustomAgent(); + + agent.addRequest = (req) => { + assert.strictEqual(req.path, '/'); + done(); + }; + + const ws = new WebSocket(new url.URL('ws://localhost'), { agent }); + }); + describe('options', function () { it('accepts an `agent` option', function (done) { const agent = new CustomAgent(); @@ -1781,7 +1806,7 @@ describe('WebSocket', function () { }); describe('Request headers', function () { - it('adds the authorization header if userinfo is present', function (done) { + it('adds the authorization header if the url has userinfo (1/2)', function (done) { const agent = new CustomAgent(); const auth = 'test:testpass'; @@ -1796,6 +1821,25 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://${auth}@localhost`, { agent }); }); + it('adds the authorization header if the url has userinfo (2/2)', function (done) { + if (!url.URL) return this.skip(); + + const agent = new CustomAgent(); + const auth = 'test:testpass'; + + agent.addRequest = (req) => { + assert.strictEqual( + req._headers.authorization, + `Basic ${Buffer.from(auth).toString('base64')}` + ); + done(); + }; + + const ws = new WebSocket(new url.URL(`ws://${auth}@localhost`), { + agent + }); + }); + it('adds custom headers', function (done) { const agent = new CustomAgent(); From 938cddedc09d01e58a2ba7353666e577f47275ba Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 17 Mar 2018 07:27:46 +0100 Subject: [PATCH 461/489] chore(package): update eslint to version 4.19.0 (#1330) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bdb75b39..f0bb08c11 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.18.0", + "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.9.0", "eslint-plugin-node": "~6.0.0", From 9e152f920a1818d8a23e0a113b4625c83f90d30d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Mar 2018 14:04:19 +0100 Subject: [PATCH 462/489] [feature] Allow all options accepted by `http{,s}.request()` (#1332) Do not use an agent by default and add ability to use all options allowed in `http.request()` and `https.request()`. --- doc/ws.md | 20 +--- lib/websocket.js | 203 +++++++++++++++++------------------------ test/websocket.test.js | 73 --------------- 3 files changed, 89 insertions(+), 207 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index cc634ad98..6f1546bb2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -186,28 +186,12 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `address` {String|url.Url|url.URL} The URL to which to connect. - `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. - - `headers` {Object} An object with custom headers to send along with the - request. - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header depending on the `protocolVersion`. - - `agent` {http.Agent|https.Agent} Use the specified Agent. - - `host` {String} Value of the `Host` header. - - `family` {Number} IP address family to use during hostname lookup (4 or 6). - - `checkServerIdentity` {Function} A function to validate the server hostname. - - `rejectUnauthorized` {Boolean} Verify or not the server certificate. - - `passphrase` {String} The passphrase for the private key or pfx. - - `ecdhCurve` {String} A named curve or a colon separated list of curve NIDs - or names to use for ECDH key agreement. - - `ciphers` {String} The ciphers to use or exclude - - `cert` {String|Array|Buffer} The certificate key. - - `key` {String|Array|Buffer} The private key. - - `pfx` {String|Buffer} The private key, certificate, and CA certs. - - `ca` {Array} Trusted certificates. + - Any other option allowed in [http.request()][] or [https.request()][]. `perMessageDeflate` default value is `true`. When using an object, parameters are the same of the server. The only difference is the direction of requests. @@ -425,3 +409,5 @@ The URL of the WebSocket server. Server clients don't have this attribute. [concurrency-limit]: https://github.com/websockets/ws/issues/1202 [permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 [zlib-options]: https://nodejs.org/api/zlib.html#zlib_class_options +[http.request()]: https://nodejs.org/api/http.html#http_http_request_options_callback +[https.request()]: https://nodejs.org/api/https.html#https_https_request_options_callback diff --git a/lib/websocket.js b/lib/websocket.js index 53aa1ebc9..c603675a9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -4,6 +4,8 @@ const EventEmitter = require('events'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); +const net = require('net'); +const tls = require('tls'); const url = require('url'); const PerMessageDeflate = require('./permessage-deflate'); @@ -50,13 +52,11 @@ class WebSocket extends EventEmitter { this._socket = null; if (address !== null) { - if (!protocols) { - protocols = []; - } else if (typeof protocols === 'string') { - protocols = [protocols]; - } else if (!Array.isArray(protocols)) { + if (Array.isArray(protocols)) { + protocols = protocols.join(', '); + } else if (typeof protocols === 'object' && protocols !== null) { options = protocols; - protocols = []; + protocols = undefined; } initAsClient.call(this, address, protocols, options); @@ -405,55 +405,30 @@ module.exports = WebSocket; * Initialize a WebSocket client. * * @param {(String|url.Url|url.URL)} address The URL to which to connect - * @param {String[]} protocols The list of subprotocols + * @param {String} protocols The subprotocols * @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 * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header - * @param {http.Agent} options.agent Use the specified Agent - * @param {String} options.host Value of the `Host` header - * @param {Number} options.family IP address family to use during hostname lookup (4 or 6). - * @param {Function} options.checkServerIdentity A function to validate the server hostname - * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate - * @param {String} options.passphrase The passphrase for the private key or pfx - * @param {String} options.ciphers The ciphers to use or exclude - * @param {String} options.ecdhCurve The curves for ECDH key agreement to use or exclude - * @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key - * @param {(String|String[]|Buffer|Buffer[])} options.key The private key - * @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs - * @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates * @private */ function initAsClient (address, protocols, options) { options = Object.assign({ protocolVersion: protocolVersions[1], - protocol: protocols.join(','), - perMessageDeflate: true, - handshakeTimeout: null, - localAddress: null, - headers: null, - family: null, - origin: null, - agent: null, - host: null, - - // - // SSL options. - // - checkServerIdentity: null, - rejectUnauthorized: null, - passphrase: null, - ciphers: null, - ecdhCurve: null, - cert: null, - key: null, - pfx: null, - ca: null - }, options); + perMessageDeflate: true + }, options, { + createConnection: undefined, + socketPath: undefined, + hostname: undefined, + protocol: undefined, + timeout: undefined, + method: undefined, + auth: undefined, + host: undefined, + path: undefined, + port: undefined + }); if (protocolVersions.indexOf(options.protocolVersion) === -1) { throw new RangeError( @@ -464,114 +439,84 @@ function initAsClient (address, protocols, options) { this._isServer = false; - var serverUrl; + var parsedUrl; if (typeof address === 'object' && address.href !== undefined) { - serverUrl = address; + parsedUrl = address; this.url = address.href; } else { - serverUrl = url.parse(address); + parsedUrl = url.parse(address); this.url = address; } - const isUnixSocket = serverUrl.protocol === 'ws+unix:'; + const isUnixSocket = parsedUrl.protocol === 'ws+unix:'; - if (!serverUrl.host && (!isUnixSocket || !serverUrl.pathname)) { + if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) { throw new Error(`Invalid URL: ${this.url}`); } - const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; + const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; - const path = serverUrl.search - ? `${serverUrl.pathname || '/'}${serverUrl.search}` - : serverUrl.pathname || '/'; + const path = parsedUrl.search + ? `${parsedUrl.pathname || '/'}${parsedUrl.search}` + : parsedUrl.pathname || '/'; var perMessageDeflate; - const requestOptions = { - port: serverUrl.port || (isSecure ? 443 : 80), - host: serverUrl.hostname, - path: path, - headers: { - 'Sec-WebSocket-Version': options.protocolVersion, - 'Sec-WebSocket-Key': key, - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' - } - }; + options.createConnection = isSecure ? tlsConnect : netConnect; + options.port = parsedUrl.port || (isSecure ? 443 : 80); + options.host = parsedUrl.hostname; + options.headers = Object.assign({ + 'Sec-WebSocket-Version': options.protocolVersion, + 'Sec-WebSocket-Key': key, + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' + }, options.headers); + options.path = path; - if (options.headers) Object.assign(requestOptions.headers, options.headers); if (options.perMessageDeflate) { perMessageDeflate = new PerMessageDeflate( options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false ); - requestOptions.headers['Sec-WebSocket-Extensions'] = extension.format({ + options.headers['Sec-WebSocket-Extensions'] = extension.format({ [PerMessageDeflate.extensionName]: perMessageDeflate.offer() }); } - if (options.protocol) { - requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; + if (protocols) { + options.headers['Sec-WebSocket-Protocol'] = protocols; } if (options.origin) { if (options.protocolVersion < 13) { - requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; + options.headers['Sec-WebSocket-Origin'] = options.origin; } else { - requestOptions.headers.Origin = options.origin; + options.headers.Origin = options.origin; } } - if (options.host) requestOptions.headers.Host = options.host; - if (serverUrl.auth) requestOptions.auth = serverUrl.auth; - else if (serverUrl.username || serverUrl.password) { - requestOptions.auth = `${serverUrl.username}:${serverUrl.password}`; + if (parsedUrl.auth) { + options.auth = parsedUrl.auth; + } else if (parsedUrl.username || parsedUrl.password) { + options.auth = `${parsedUrl.username}:${parsedUrl.password}`; } - if (options.localAddress) requestOptions.localAddress = options.localAddress; - if (options.family) requestOptions.family = options.family; - if (isUnixSocket) { const parts = path.split(':'); - requestOptions.socketPath = parts[0]; - requestOptions.path = parts[1]; - } - - var agent = options.agent; - - // - // A custom agent is required for these options. - // - if ( - options.rejectUnauthorized != null || - options.checkServerIdentity || - options.passphrase || - options.ciphers || - options.ecdhCurve || - options.cert || - options.key || - options.pfx || - options.ca - ) { - if (options.passphrase) requestOptions.passphrase = options.passphrase; - if (options.ciphers) requestOptions.ciphers = options.ciphers; - if (options.ecdhCurve) requestOptions.ecdhCurve = options.ecdhCurve; - if (options.cert) requestOptions.cert = options.cert; - if (options.key) requestOptions.key = options.key; - if (options.pfx) requestOptions.pfx = options.pfx; - if (options.ca) requestOptions.ca = options.ca; - if (options.checkServerIdentity) { - requestOptions.checkServerIdentity = options.checkServerIdentity; - } - if (options.rejectUnauthorized != null) { - requestOptions.rejectUnauthorized = options.rejectUnauthorized; + if (options.agent == null && process.versions.modules < 57) { + // + // Setting `socketPath` in conjunction with `createConnection` without an + // agent throws an error on Node.js < 8. Work around the issue by using a + // different property. + // + options._socketPath = parts[0]; + } else { + options.socketPath = parts[0]; } - if (!agent) agent = new httpObj.Agent(requestOptions); + options.path = parts[1]; } - if (agent) requestOptions.agent = agent; - - var req = this._req = httpObj.get(requestOptions); + var req = this._req = httpObj.get(options); if (options.handshakeTimeout) { req.setTimeout( @@ -616,12 +561,12 @@ function initAsClient (address, protocols, options) { } const serverProt = res.headers['sec-websocket-protocol']; - const protList = (options.protocol || '').split(/, */); + const protList = (protocols || '').split(/, */); var protError; - if (!options.protocol && serverProt) { + if (!protocols && serverProt) { protError = 'Server sent a subprotocol but none was requested'; - } else if (options.protocol && !serverProt) { + } else if (protocols && !serverProt) { protError = 'Server sent no subprotocol'; } else if (serverProt && protList.indexOf(serverProt) === -1) { protError = 'Server sent an invalid subprotocol'; @@ -656,6 +601,30 @@ function initAsClient (address, protocols, options) { }); } +/** + * Create a `net.Socket` and initiate a connection. + * + * @param {Object} options Connection options + * @return {net.Socket} The newly created socket used to start the connection + * @private + */ +function netConnect (options) { + options.path = options.socketPath || options._socketPath || undefined; + return net.connect(options); +} + +/** + * Create a `tls.TLSSocket` and initiate a connection. + * + * @param {Object} options Connection options + * @return {tls.TLSSocket} The newly created socket used to start the connection + * @private + */ +function tlsConnect (options) { + options.path = options.socketPath || options._socketPath || undefined; + return tls.connect(options); +} + /** * Abort the handshake and emit an error. * diff --git a/test/websocket.test.js b/test/websocket.test.js index 79d0addec..a33187480 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -6,10 +6,8 @@ const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); -const dns = require('dns'); const url = require('url'); const fs = require('fs'); -const os = require('os'); const constants = require('../lib/constants'); const WebSocket = require('..'); @@ -52,16 +50,6 @@ describe('WebSocket', function () { }); describe('options', function () { - it('accepts an `agent` option', function (done) { - const agent = new CustomAgent(); - - agent.addRequest = () => { - done(); - }; - - const ws = new WebSocket('ws://localhost', { agent }); - }); - it('accepts the `options` object as 3rd argument', function () { const agent = new CustomAgent(); let count = 0; @@ -84,67 +72,6 @@ describe('WebSocket', function () { /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ ); }); - - it('accepts the `localAddress` option', function (done) { - const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - localAddress: '127.0.0.2' - }); - - ws.on('error', (err) => { - wss.close(() => { - // - // Skip this test on machines where 127.0.0.2 is disabled. - // - if (err.code === 'EADDRNOTAVAIL') return this.skip(); - - done(err); - }); - }); - }); - - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); - wss.close(done); - }); - }); - - it('accepts the `family` option', function (done) { - const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; - const ifaces = os.networkInterfaces(); - const hasIPv6 = Object.keys(ifaces).some((name) => { - return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); - }); - - // - // Skip this test on machines where IPv6 is not supported. - // - if (!hasIPv6) return this.skip(); - - dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { - // - // Skip this test if localhost does not resolve to ::1. - // - if (err) { - return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' - ? this.skip() - : done(err); - } - - if (!addresses.some((val) => val.address === '::1')) return this.skip(); - - const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - family: 6 - }); - }); - - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '::1'); - wss.close(done); - }); - }); - }); }); }); From 7c74567b974b79253ac32aac8f7d3dcb3e6d17e8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Mar 2018 18:23:31 +0100 Subject: [PATCH 463/489] [dist] 5.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0bb08c11..e4cbc3a39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.0.0", + "version": "5.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 83ce53489617d6ae68cf0f4ec314103be0160a5f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 30 Mar 2018 14:59:08 +0200 Subject: [PATCH 464/489] chore(package): update eslint-plugin-import to version 2.10.0 (#1340) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4cbc3a39..f23ca7fdb 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.9.0", + "eslint-plugin-import": "~2.10.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", From f52debe945f412f22f4fe27b9c1c9a9bfb82accd Mon Sep 17 00:00:00 2001 From: An-Li Ting Date: Fri, 30 Mar 2018 22:18:42 +0800 Subject: [PATCH 465/489] [doc] Explain the behavior of `server.close()` more precisely (#1342) --- doc/ws.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 6f1546bb2..f8643122f 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -139,7 +139,9 @@ returned as a string. ### server.close([callback]) -Close the server and terminate all clients, calls callback when done. +Close the HTTP server if created internally, terminate all clients and call +callback when done. If an external HTTP server is used via the `server` or +`noServer` constructor options, it must be closed manually. ### server.handleUpgrade(request, socket, head, callback) From 2d7bf88eafdf4bce81c16edd35c811d6a2a87743 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Apr 2018 14:50:34 +0200 Subject: [PATCH 466/489] [perf] Use js version of `{un,}mask()` for very small frames (#1348) --- lib/buffer-util.js | 81 ++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 38e16df3f..6974dd6a8 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -8,7 +8,7 @@ * @return {Buffer} The resulting buffer * @public */ -const concat = (list, totalLength) => { +function concat (list, totalLength) { const target = Buffer.allocUnsafe(totalLength); var offset = 0; @@ -19,43 +19,54 @@ const concat = (list, totalLength) => { } return target; -}; +} + +/** + * Masks a buffer using the given mask. + * + * @param {Buffer} source The buffer to mask + * @param {Buffer} mask The mask to use + * @param {Buffer} output The buffer where to store the result + * @param {Number} offset The offset at which to start writing + * @param {Number} length The number of bytes to mask. + * @public + */ +function _mask (source, mask, output, offset, length) { + for (var i = 0; i < length; i++) { + output[offset + i] = source[i] ^ mask[i & 3]; + } +} + +/** + * Unmasks a buffer using the given mask. + * + * @param {Buffer} buffer The buffer to unmask + * @param {Buffer} mask The mask to use + * @public + */ +function _unmask (buffer, mask) { + // Required until https://github.com/nodejs/node/issues/9006 is resolved. + const length = buffer.length; + for (var i = 0; i < length; i++) { + buffer[i] ^= mask[i & 3]; + } +} try { const bufferUtil = require('bufferutil'); + const bu = bufferUtil.BufferUtil || bufferUtil; - module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil); -} catch (e) /* istanbul ignore next */ { - /** - * Masks a buffer using the given mask. - * - * @param {Buffer} source The buffer to mask - * @param {Buffer} mask The mask to use - * @param {Buffer} output The buffer where to store the result - * @param {Number} offset The offset at which to start writing - * @param {Number} length The number of bytes to mask. - * @public - */ - const mask = (source, mask, output, offset, length) => { - for (var i = 0; i < length; i++) { - output[offset + i] = source[i] ^ mask[i & 3]; - } - }; - - /** - * Unmasks a buffer using the given mask. - * - * @param {Buffer} buffer The buffer to unmask - * @param {Buffer} mask The mask to use - * @public - */ - const unmask = (buffer, mask) => { - // Required until https://github.com/nodejs/node/issues/9006 is resolved. - const length = buffer.length; - for (var i = 0; i < length; i++) { - buffer[i] ^= mask[i & 3]; - } + module.exports = { + mask (source, mask, output, offset, length) { + if (length < 48) _mask(source, mask, output, offset, length); + else bu.mask(source, mask, output, offset, length); + }, + unmask (buffer, mask) { + if (buffer.length < 32) _unmask(buffer, mask); + else bu.unmask(buffer, mask); + }, + concat }; - - module.exports = { concat, mask, unmask }; +} catch (e) /* istanbul ignore next */ { + module.exports = { concat, mask: _mask, unmask: _unmask }; } From f335d7994142ecf61b3ad325752c82a8e26fa763 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Apr 2018 15:07:10 +0200 Subject: [PATCH 467/489] [fix] Add default value for `servername` option (#1347) Fixes #1346 --- lib/websocket.js | 5 ++++- test/websocket.test.js | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index c603675a9..67cd8a49f 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -465,7 +465,9 @@ function initAsClient (address, protocols, options) { options.createConnection = isSecure ? tlsConnect : netConnect; options.port = parsedUrl.port || (isSecure ? 443 : 80); - options.host = parsedUrl.hostname; + options.host = parsedUrl.hostname.startsWith('[') + ? parsedUrl.hostname.slice(1, -1) + : parsedUrl.hostname; options.headers = Object.assign({ 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key, @@ -622,6 +624,7 @@ function netConnect (options) { */ function tlsConnect (options) { options.path = options.socketPath || options._socketPath || undefined; + options.servername = options.servername || options.host; return tls.connect(options); } diff --git a/test/websocket.test.js b/test/websocket.test.js index a33187480..2807e59fb 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -41,12 +41,13 @@ describe('WebSocket', function () { const agent = new CustomAgent(); - agent.addRequest = (req) => { + agent.addRequest = (req, opts) => { + assert.strictEqual(opts.host, '::1'); assert.strictEqual(req.path, '/'); done(); }; - const ws = new WebSocket(new url.URL('ws://localhost'), { agent }); + const ws = new WebSocket(new url.URL('ws://[::1]'), { agent }); }); describe('options', function () { From 10c92fff16c53be18c7be05c8a4c65d25cae8088 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Apr 2018 15:17:31 +0200 Subject: [PATCH 468/489] [dist] 5.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f23ca7fdb..ba787ba90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.1.0", + "version": "5.1.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From c801e991ae52fdaaeabe0dd647db41f0f831e6d1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 8 Apr 2018 21:43:54 +0200 Subject: [PATCH 469/489] [doc] Improve docs and examples (#1355) Fixes #1334 Fixes #1338 --- README.md | 106 ++++++++++++++++++++++++++++++++++-------------------- doc/ws.md | 14 +++++--- 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8f94ca303..e35732673 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,10 @@ one of the many wrappers available on npm, like * [Usage examples](#usage-examples) + [Sending and receiving text data](#sending-and-receiving-text-data) + [Sending binary data](#sending-binary-data) - + [Server example](#server-example) - + [Broadcast example](#broadcast-example) - + [ExpressJS example](#expressjs-example) + + [Simple server](#simple-server) + + [External HTTP/S server](#external-https-server) + + [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) + + [Server broadcast](#server-broadcast) + [echo.websocket.org demo](#echowebsocketorg-demo) + [Other examples](#other-examples) * [Error handling best practices](#error-handling-best-practices) @@ -169,7 +170,7 @@ ws.on('open', function open() { }); ``` -### Server example +### Simple server ```js const WebSocket = require('ws'); @@ -185,7 +186,68 @@ wss.on('connection', function connection(ws) { }); ``` -### Broadcast example +### External HTTP/S server + +```js +const fs = require('fs'); +const https = require('https'); +const WebSocket = require('ws'); + +const server = new https.createServer({ + cert: fs.readFileSync('/path/to/cert.pem'), + key: fs.readFileSync('/path/to/key.pem') +}); +const wss = new WebSocket.Server({ server }); + +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); + +server.listen(8080); +``` + +### Multiple servers sharing a single HTTP/S server + +```js +const http = require('http'); +const WebSocket = require('ws'); + +const server = http.createServer(); +const wss1 = new WebSocket.Server({ noServer: true }); +const wss2 = new WebSocket.Server({ noServer: true }); + +wss1.on('connection', function connection(ws) { + // ... +}); + +wss2.on('connection', function connection(ws) { + // ... +}); + +server.on('upgrade', function upgrade(request, socket, head) { + const pathname = url.parse(request.url).pathname; + + if (pathname === '/foo') { + wss1.handleUpgrade(request, socket, head, function done(ws) { + wss1.emit('connection', ws); + }); + } else if (pathname === '/bar') { + wss2.handleUpgrade(request, socket, head, function done(ws) { + wss2.emit('connection', ws); + }); + } else { + socket.destroy(); + } +}); + +server.listen(8080); +``` + +### Server broadcast ```js const WebSocket = require('ws'); @@ -213,40 +275,6 @@ wss.on('connection', function connection(ws) { }); ``` -### ExpressJS example - -```js -const express = require('express'); -const http = require('http'); -const url = require('url'); -const WebSocket = require('ws'); - -const app = express(); - -app.use(function (req, res) { - res.send({ msg: "hello" }); -}); - -const server = http.createServer(app); -const wss = new WebSocket.Server({ server }); - -wss.on('connection', function connection(ws, req) { - const location = url.parse(req.url, true); - // You might use location.query.access_token to authenticate or share sessions - // or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312) - - ws.on('message', function incoming(message) { - console.log('received: %s', message); - }); - - ws.send('something'); -}); - -server.listen(8080, function listening() { - console.log('Listening on %d', server.address().port); -}); -``` - ### echo.websocket.org demo ```js diff --git a/doc/ws.md b/doc/ws.md index f8643122f..89c377da9 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -10,7 +10,7 @@ This class represents a WebSocket server. It extends the `EventEmitter`. - `host` {String} The hostname where to bind the server. - `port` {Number} The port where to bind the server. - `backlog` {Number} The maximum length of the queue of pending connections. - - `server` {http.Server|https.Server} A pre-created Node.js HTTP server. + - `server` {http.Server|https.Server} A pre-created Node.js HTTP/S server. - `verifyClient` {Function} A function which can be used to validate incoming connections. See description below. - `handleProtocols` {Function} A function which can be used to handle the @@ -23,7 +23,12 @@ This class represents a WebSocket server. It extends the `EventEmitter`. - `callback` {Function} Create a new server instance. One of `port`, `server` or `noServer` must be -provided or an error is thrown. +provided or an error is thrown. An HTTP server is automatically created, +started, and used if `port` is set. To use an external HTTP/S server instead, +specify only `server` or `noServer`. In this case the HTTP/S server must be +started manually. The "noServer" mode allows the WebSocket server to be +completly detached from the HTTP/S server. This makes it possible, for example, +to share a single HTTP/S server between multiple WebSocket servers. If `verifyClient` is not set then the handshake is automatically accepted. If @@ -91,9 +96,8 @@ When sending a fragmented message the length of the first fragment is compared to the threshold. This determines if compression is used for the entire message. -`callback` will be added as a listener for the `listening` event when the -HTTP server is created internally and that is when the `port` option is -provided. +`callback` will be added as a listener for the `listening` event on the HTTP +server when not operating in "noServer" mode. ### Event: 'connection' From 0100d82045125ef08b703ad0722822a3969cda37 Mon Sep 17 00:00:00 2001 From: Zoli Kahan Date: Wed, 11 Apr 2018 15:29:58 -0500 Subject: [PATCH 470/489] [doc] Improve FAQ example for X-Forwarded-For header (#1360) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e35732673..aea7ed721 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ the `X-Forwarded-For` header. ```js wss.on('connection', function connection(ws, req) { - const ip = req.headers['x-forwarded-for']; + const ip = req.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; }); ``` From 3215cf3bf8bd754728b4a0bff070fe41d5788520 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 12 Apr 2018 17:03:30 +0200 Subject: [PATCH 471/489] chore(package): update eslint-plugin-import to version 2.11.0 (#1361) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba787ba90..23f20eac9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.10.0", + "eslint-plugin-import": "~2.11.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", From a81e580badd6141f23532c9a5f453a8ade8ba8fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 13 Apr 2018 07:26:52 +0200 Subject: [PATCH 472/489] chore(package): update mocha to version 5.1.0 (#1362) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23f20eac9..d40eb2120 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~5.0.0", + "mocha": "~5.1.0", "nyc": "~11.6.0", "utf-8-validate": "~4.0.0" } From 9dc25a380d003142aad9bf1bbfce6fa5d1b5e81c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 19 Apr 2018 11:00:24 +0200 Subject: [PATCH 473/489] chore(package): update nyc to version 11.7.1 (#1364) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d40eb2120..2c4890b9a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.1.0", - "nyc": "~11.6.0", + "nyc": "~11.7.1", "utf-8-validate": "~4.0.0" } } From 690b3f277c6f5c3aef8cd84792929450f516b3ae Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 23 Apr 2018 10:07:20 +0200 Subject: [PATCH 474/489] [minor] Replace bound function with arrow function Do not mask potential issues, like #1280, with bound arguments. --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 67cd8a49f..2a2a33b30 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -523,7 +523,7 @@ function initAsClient (address, protocols, options) { if (options.handshakeTimeout) { req.setTimeout( options.handshakeTimeout, - abortHandshake.bind(null, this, req, 'Opening handshake has timed out') + () => abortHandshake(this, req, 'Opening handshake has timed out') ); } From 4385c7890a45ebc38df2404def4f648aa8ed228d Mon Sep 17 00:00:00 2001 From: Luke Avsec Date: Mon, 30 Apr 2018 01:34:55 -0400 Subject: [PATCH 475/489] [doc] Add `request` to emit arguments in shared server example (#1372) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aea7ed721..3fd9a8cc0 100644 --- a/README.md +++ b/README.md @@ -233,11 +233,11 @@ server.on('upgrade', function upgrade(request, socket, head) { if (pathname === '/foo') { wss1.handleUpgrade(request, socket, head, function done(ws) { - wss1.emit('connection', ws); + wss1.emit('connection', ws, request); }); } else if (pathname === '/bar') { wss2.handleUpgrade(request, socket, head, function done(ws) { - wss2.emit('connection', ws); + wss2.emit('connection', ws, request); }); } else { socket.destroy(); From 6d8f1f4d494c0470629680ffd77b18390c641668 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 May 2018 15:56:36 +0200 Subject: [PATCH 476/489] [ci] Test on node 10 --- .travis.yml | 1 + appveyor.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 668010445..0ec5464e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js sudo: false node_js: + - "10" - "9" - "8" - "6" diff --git a/appveyor.yml b/appveyor.yml index ce931fe9f..f797a9607 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,6 @@ environment: matrix: + - nodejs_version: "10" - nodejs_version: "9" - nodejs_version: "8" - nodejs_version: "6" From bb9c21c1aff2949b648747825f1b482d389fd2ae Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 May 2018 17:09:59 +0200 Subject: [PATCH 477/489] [test] Fix failing test on node 10 --- test/permessage-deflate.test.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index ab2a9ab11..3d51bd57e 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -527,17 +527,14 @@ describe('PerMessageDeflate', function () { it("doesn't call the callback twice when `maxPayload` is exceeded", function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, false, 25); const buf = Buffer.from('A'.repeat(50)); - const errors = []; perMessageDeflate.accept([{}]); perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - perMessageDeflate.decompress(data, true, (err) => errors.push(err)); - perMessageDeflate._inflate.flush(() => { - assert.strictEqual(errors.length, 1); - assert.ok(errors[0] instanceof RangeError); - assert.strictEqual(errors[0].message, 'Max payload size exceeded'); + perMessageDeflate.decompress(data, true, (err) => { + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); done(); }); }); From d871bdfdc806122862ee5e2b781989b576771caf Mon Sep 17 00:00:00 2001 From: Dmitry Kirilyuk Date: Sat, 12 May 2018 22:42:31 +0300 Subject: [PATCH 478/489] [feature] Add `headers` argument to `verifyClient()` callback (#1379) Add ability to specify custom headers when rejecting the handshake. --- doc/ws.md | 2 ++ lib/websocket-server.js | 21 ++++++++++++++------- test/websocket-server.test.js | 31 ++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 89c377da9..47d8abeda 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -53,6 +53,8 @@ if `verifyClient` is provided with two arguments then those are: error status code to be sent to the client. - `name` {String} When `result` is `false` this field determines the HTTP reason phrase. + - `headers` {Object} When `result` is `false` this field determines additional + HTTP headers to be sent to the client. For example, `{ 'Retry-After': 120 }`. `handleProtocols` takes two arguments: diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 72618d028..70513edf2 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -204,8 +204,10 @@ class WebSocketServer extends EventEmitter { }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (verified, code, message) => { - if (!verified) return abortHandshake(socket, code || 401, message); + this.options.verifyClient(info, (verified, code, message, headers) => { + if (!verified) { + return abortHandshake(socket, code || 401, message, headers); + } this.completeUpgrade(extensions, req, socket, head, cb); }); @@ -330,17 +332,22 @@ function socketOnError () { * @param {net.Socket} socket The socket of the upgrade request * @param {Number} code The HTTP response status code * @param {String} [message] The HTTP response body + * @param {Object} [headers] Additional HTTP response headers * @private */ -function abortHandshake (socket, code, message) { +function abortHandshake (socket, code, message, headers) { if (socket.writable) { message = message || http.STATUS_CODES[code]; + headers = Object.assign({ + 'Connection': 'close', + 'Content-type': 'text/html', + 'Content-Length': Buffer.byteLength(message) + }, headers); + socket.write( `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + - 'Connection: close\r\n' + - 'Content-type: text/html\r\n' + - `Content-Length: ${Buffer.byteLength(message)}\r\n` + - '\r\n' + + Object.keys(headers).map(h => `${h}: ${headers[h]}`).join('\r\n') + + '\r\n\r\n' + message ); } diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 402cd827b..45d287978 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -551,7 +551,7 @@ describe('WebSocketServer', function () { }); }); - it('can reject client asynchronously with status code', function (done) { + it('can reject client asynchronously w/ status code', function (done) { const wss = new WebSocket.Server({ verifyClient: (info, cb) => process.nextTick(cb, false, 404), port: 0 @@ -576,6 +576,35 @@ describe('WebSocketServer', function () { done(new Error("Unexpected 'connection' event")); }); }); + + it('can reject client asynchronously w/ custom headers', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info, cb) => { + process.nextTick(cb, false, 503, '', { 'Retry-After': 120 }); + }, + port: 0 + }, () => { + const req = http.get({ + port: wss.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 503); + assert.strictEqual(res.headers['retry-after'], '120'); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); + }); + }); }); it("doesn't emit the 'connection' event if socket is closed prematurely", function (done) { From aebda2bce3c0e0216f2ef79d192d2e79ffaee29b Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 16 May 2018 07:21:49 +0200 Subject: [PATCH 479/489] chore(package): update nyc to version 11.8.0 (#1382) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2c4890b9a..3bb326e57 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.1.0", - "nyc": "~11.7.1", + "nyc": "~11.8.0", "utf-8-validate": "~4.0.0" } } From 6dae94bc65f9aa6acf21891ba0d46457d5d095a7 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 17 May 2018 14:39:30 +0200 Subject: [PATCH 480/489] chore(package): update eslint-plugin-import to version 2.12.0 (#1384) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bb326e57..12d7b9a26 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.11.0", + "eslint-plugin-import": "~2.12.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", From e7bfe5f13895701cab8492a6ed7872ec6b724da2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 19 May 2018 07:29:18 +0200 Subject: [PATCH 481/489] chore(package): update mocha to version 5.2.0 (#1385) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12d7b9a26..bfaafd80e 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~5.1.0", + "mocha": "~5.2.0", "nyc": "~11.8.0", "utf-8-validate": "~4.0.0" } From e4d032c383dd2931de7dfbe81a0a8185cdffbf52 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 21 May 2018 21:11:33 +0200 Subject: [PATCH 482/489] [dist] 5.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bfaafd80e..af5a01146 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.1.1", + "version": "5.2.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From bcab5310beb5a07f62fff92105d4183b0e011252 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 25 May 2018 07:23:05 +0200 Subject: [PATCH 483/489] chore(package): update eslint-plugin-promise to version 3.8.0 (#1389) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af5a01146..f799b2d57 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.12.0", "eslint-plugin-node": "~6.0.0", - "eslint-plugin-promise": "~3.7.0", + "eslint-plugin-promise": "~3.8.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.2.0", "nyc": "~11.8.0", From bf9b2ececbe42dd07ef9619d2b4953f57243c843 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 4 Jun 2018 07:49:18 +0200 Subject: [PATCH 484/489] chore(package): update nyc to version 12.0.2 (#1395) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f799b2d57..8f1171860 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.8.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.2.0", - "nyc": "~11.8.0", + "nyc": "~12.0.2", "utf-8-validate": "~4.0.0" } } From 6046a2873944793b01457488aed8062ccaa53743 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 19 Jun 2018 22:38:36 +0200 Subject: [PATCH 485/489] [fix] Do not prematurely remove the listener of the `'data'` event --- lib/websocket.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 2a2a33b30..4d7c4f15e 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -750,7 +750,6 @@ function socketOnClose () { const websocket = this[kWebSocket]; this.removeListener('close', socketOnClose); - this.removeListener('data', socketOnData); this.removeListener('end', socketOnEnd); this[kWebSocket] = undefined; @@ -760,11 +759,16 @@ function socketOnClose () { // The close frame might not have been received or the `'end'` event emitted, // for example, if the socket was destroyed due to an error. Ensure that the // `receiver` stream is closed after writing any remaining buffered data to - // it. + // it. If the readable side of the socket is in flowing mode then there is no + // buffered data as everything has been already written and `readable.read()` + // will return `null`. If instead, the socket is paused, any possible buffered + // data will be read as a single chunk and emitted synchronously in a single + // `'data'` event. // websocket._socket.read(); websocket._receiver.end(); + this.removeListener('data', socketOnData); clearTimeout(websocket._closeTimer); if ( From 307be7a81ee1c08cbd1e2acf2ea98fbf26ae390d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Jun 2018 17:35:16 +0200 Subject: [PATCH 486/489] [fix] Remove the `'data'` listener when the receiver emits an error --- lib/websocket.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/websocket.js b/lib/websocket.js index 4d7c4f15e..643124f20 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -693,6 +693,8 @@ function receiverOnDrain () { function receiverOnError (err) { const websocket = this[kWebSocket]; + websocket._socket.removeListener('data', socketOnData); + websocket.readyState = WebSocket.CLOSING; websocket._closeCode = err[constants.kStatusCode]; websocket.emit('error', err); From 175ce4605b80d610e558c858b0f8d74599a16db1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Jun 2018 18:01:12 +0200 Subject: [PATCH 487/489] [dist] 5.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f1171860..3a35a499d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.2.0", + "version": "5.2.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 8aba8712dc5b94de17a952137c077f2d74efb529 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 11 Jul 2018 21:19:59 +0200 Subject: [PATCH 488/489] [fix] Fix use after invalidation bug Fixes #1418 --- lib/websocket.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 643124f20..f2bdf0d74 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -753,7 +753,6 @@ function socketOnClose () { this.removeListener('close', socketOnClose); this.removeListener('end', socketOnEnd); - this[kWebSocket] = undefined; websocket.readyState = WebSocket.CLOSING; @@ -771,6 +770,8 @@ function socketOnClose () { websocket._receiver.end(); this.removeListener('data', socketOnData); + this[kWebSocket] = undefined; + clearTimeout(websocket._closeTimer); if ( From 5d55e52529167c25f4fec35cb4753294e75bf9f2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 11 Jul 2018 21:47:43 +0200 Subject: [PATCH 489/489] [dist] 5.2.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a35a499d..df8629a82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.2.1", + "version": "5.2.2", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi",