Comparing version 0.4.1 to 0.4.2
@@ -0,1 +1,11 @@ | ||
v0.4.2 - Feb 4th 2012 | ||
===================== | ||
* Breaking change: WebSocketServer's verifyOrigin option has been renamed to verifyClient. [einaros] | ||
* verifyClient now receives { origin: 'origin header', secure: true/false }, where 'secure' will be true for ssl connections. [einaros] | ||
* Split benchmark, in preparation for more thorough case. [einaros] | ||
* Introduced hixie-76 draft support for server, since Safari (iPhone / iPad / OS X) and Opera still aren't updated to use Hybi. [einaros] | ||
* Expose 'supports' object from WebSocket, to indicate e.g. the underlying transport's support for binary data. [einaros] | ||
* Test and code cleanups. [einaros] | ||
v0.4.1 - Jan 25th 2012 | ||
@@ -2,0 +12,0 @@ ===================== |
@@ -131,3 +131,3 @@ /*! | ||
var buf = this.overflow.pop(); | ||
if (toRead < buf.length) this.overflow.push(buf.slice(toRead)) | ||
if (toRead < buf.length) this.overflow.push(buf.slice(toRead)); | ||
var read = Math.min(buf.length, toRead); | ||
@@ -156,3 +156,3 @@ this.add(buf.slice(0, read)); | ||
var buf = this.overflow.pop(); | ||
if (toRead < buf.length) this.overflow.push(buf.slice(toRead)) | ||
if (toRead < buf.length) this.overflow.push(buf.slice(toRead)); | ||
var read = Math.min(buf.length, toRead); | ||
@@ -193,3 +193,3 @@ this.add(buf.slice(0, read)); | ||
if (!(this.state.opcode == 1 || this.state.opcode == 2)) { | ||
this.error('continuation frame cannot follow current opcode', 1002) | ||
this.error('continuation frame cannot follow current opcode', 1002); | ||
return; | ||
@@ -196,0 +196,0 @@ } |
@@ -16,3 +16,5 @@ /*! | ||
, Sender = require('./Sender') | ||
, Receiver = require('./Receiver'); | ||
, Receiver = require('./Receiver') | ||
, SenderHixie = require('./Sender.hixie') | ||
, ReceiverHixie = require('./Receiver.hixie'); | ||
@@ -23,3 +25,4 @@ /** | ||
var protocolPrefix = "HyBi-"; | ||
// Default protocol version | ||
var protocolVersion = 13; | ||
@@ -33,125 +36,6 @@ | ||
var self = this; | ||
if (Object.prototype.toString.call(address) == '[object Array]') { | ||
/** | ||
* Act as server client | ||
*/ | ||
options = new Options({ | ||
protocolVersion: protocolVersion, | ||
protocol: null | ||
}).merge(options); | ||
// Exposed properties | ||
Object.defineProperty(this, 'protocol', { | ||
value: options.value.protocol, | ||
configurable: false, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(this, 'protocolVersion', { | ||
value: options.value.protocolVersion, | ||
configurable: false, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(this, 'upgradeReq', { | ||
value: address[0], | ||
configurable: false, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(this, '_readyState', { writable: true, value: WebSocket.CONNECTING }); | ||
Object.defineProperty(this, '_isServer', { writable: false, value: true }); | ||
} | ||
else { | ||
/** | ||
* Act as regular client | ||
*/ | ||
Object.defineProperty(this, '_isServer', { writable: false, value: false }); | ||
var serverUrl = url.parse(address); | ||
if (!serverUrl.host) throw new Error('invalid url'); | ||
var httpObj = (serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:') ? https : http; | ||
Object.defineProperty(this, 'url', { | ||
writable: false, | ||
configurable: false, | ||
enumerable: true, | ||
value: address | ||
}); | ||
options = new Options({ | ||
origin: null, | ||
protocolVersion: protocolVersion, | ||
protocol: null | ||
}).merge(options); | ||
if (options.value.protocolVersion != 8 && options.value.protocolVersion != 13) { | ||
throw new Error('unsupported protocol version'); | ||
} | ||
var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64'); | ||
var shasum = crypto.createHash('sha1'); | ||
shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); | ||
var expectedServerKey = shasum.digest('base64'); | ||
// node<=v0.4.x compatibility | ||
var isNodeV4 = false; | ||
var agent; | ||
if (/^v0\.4/.test(process.version)) { | ||
isNodeV4 = true; | ||
agent = new httpObj.Agent({ | ||
host: serverUrl.hostname, | ||
port: serverUrl.port || 80 | ||
}); | ||
} | ||
var requestOptions = { | ||
port: serverUrl.port || 80, | ||
host: serverUrl.hostname, | ||
headers: { | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'websocket', | ||
'Sec-WebSocket-Version': options.value.protocolVersion, | ||
'Sec-WebSocket-Key': key | ||
} | ||
}; | ||
if (options.value.protocol) { | ||
requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol; | ||
} | ||
if (isNodeV4) { | ||
requestOptions.path = (serverUrl.pathname || '/') + (serverUrl.search || ''); | ||
requestOptions.agent = agent; | ||
} | ||
else requestOptions.path = serverUrl.path || '/'; | ||
if (options.value.origin) { | ||
if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin; | ||
else requestOptions.headers['Origin'] = options.value.origin; | ||
} | ||
var req = httpObj.request(requestOptions); | ||
(isNodeV4 ? agent : req).on('error', function(error) { | ||
self.emit('error', error); | ||
}); | ||
(isNodeV4 ? agent : req).on('upgrade', function(res, socket, upgradeHead) { | ||
if (self.readyState == WebSocket.CLOSED) { | ||
// client closed before server accepted connection | ||
self.emit('close'); | ||
socket.end(); | ||
return; | ||
} | ||
var serverKey = res.headers['sec-websocket-accept']; | ||
if (typeof serverKey == 'undefined' || serverKey !== expectedServerKey) { | ||
self.emit('error', 'invalid server key'); | ||
socket.end(); | ||
return; | ||
} | ||
upgrade.call(self, res, socket, upgradeHead); | ||
}); | ||
req.end(); | ||
Object.defineProperty(this, '_readyState', { writable: true, value: WebSocket.CONNECTING }); | ||
} | ||
var realEmit = this.emit; | ||
this.emit = function(event) { | ||
if (event == 'error') delete this._queue; | ||
if (event == 'error') delete self._queue; | ||
realEmit.apply(this, arguments); | ||
@@ -162,9 +46,27 @@ } | ||
get: function() { | ||
return this._readyState; | ||
return self._readyState; | ||
} | ||
}); | ||
Object.defineProperty(this, 'supports', { | ||
get: function() { | ||
return { | ||
'binary': self.protocolVersion != 'hixie-76' | ||
}; | ||
} | ||
}); | ||
if (this._isServer) { | ||
upgrade.apply(this, address); | ||
if (Object.prototype.toString.call(address) == '[object Array]') { | ||
/** | ||
* Act as server client | ||
*/ | ||
initAsServerClient.apply(this, address.concat(options)); | ||
} | ||
else { | ||
/** | ||
* Act as regular client | ||
*/ | ||
initAsClient.apply(this, arguments); | ||
} | ||
} | ||
@@ -410,3 +312,128 @@ | ||
function upgrade(res, socket, upgradeHead) { | ||
function initAsServerClient(req, socket, upgradeHead, options) { | ||
options = new Options({ | ||
protocolVersion: protocolVersion, | ||
protocol: null | ||
}).merge(options); | ||
// expose state properties | ||
Object.defineProperty(this, 'protocol', { | ||
value: options.value.protocol, | ||
configurable: false, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(this, 'protocolVersion', { | ||
value: options.value.protocolVersion, | ||
configurable: false, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(this, 'upgradeReq', { | ||
value: req, | ||
configurable: false, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(this, '_readyState', { writable: true, value: WebSocket.CONNECTING }); | ||
Object.defineProperty(this, '_isServer', { writable: false, value: true }); | ||
// establish connection | ||
if (options.value.protocolVersion == 'hixie-76') establishHixieConnection.call(this, socket, upgradeHead); | ||
else establishHybiConnection.call(this, socket, upgradeHead); | ||
} | ||
function initAsClient(address, options) { | ||
options = new Options({ | ||
origin: null, | ||
protocolVersion: protocolVersion, | ||
protocol: null | ||
}).merge(options); | ||
if (options.value.protocolVersion != 8 && options.value.protocolVersion != 13) { | ||
throw new Error('unsupported protocol version'); | ||
} | ||
// verify url and establish http class | ||
var serverUrl = url.parse(address); | ||
if (!serverUrl.host) throw new Error('invalid url'); | ||
var httpObj = (serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:') ? https : http; | ||
// expose state properties | ||
Object.defineProperty(this, '_isServer', { writable: false, value: false }); | ||
Object.defineProperty(this, 'url', { | ||
writable: false, | ||
configurable: false, | ||
enumerable: true, | ||
value: address | ||
}); | ||
Object.defineProperty(this, 'protocolVersion', { | ||
value: options.value.protocolVersion, | ||
configurable: false, | ||
enumerable: true | ||
}); | ||
// 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'); | ||
var expectedServerKey = shasum.digest('base64'); | ||
// node<=v0.4.x compatibility | ||
var isNodeV4 = false; | ||
var agent; | ||
if (/^v0\.4/.test(process.version)) { | ||
isNodeV4 = true; | ||
agent = new httpObj.Agent({ | ||
host: serverUrl.hostname, | ||
port: serverUrl.port || 80 | ||
}); | ||
} | ||
var requestOptions = { | ||
port: serverUrl.port || 80, | ||
host: serverUrl.hostname, | ||
headers: { | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'websocket', | ||
'Sec-WebSocket-Version': options.value.protocolVersion, | ||
'Sec-WebSocket-Key': key | ||
} | ||
}; | ||
if (options.value.protocol) { | ||
requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol; | ||
} | ||
if (isNodeV4) { | ||
requestOptions.path = (serverUrl.pathname || '/') + (serverUrl.search || ''); | ||
requestOptions.agent = agent; | ||
} | ||
else requestOptions.path = serverUrl.path || '/'; | ||
if (options.value.origin) { | ||
if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin; | ||
else requestOptions.headers['Origin'] = options.value.origin; | ||
} | ||
var self = this; | ||
var req = httpObj.request(requestOptions); | ||
(isNodeV4 ? agent : req).on('error', function(error) { | ||
self.emit('error', error); | ||
}); | ||
(isNodeV4 ? agent : req).on('upgrade', function(res, socket, upgradeHead) { | ||
if (self.readyState == WebSocket.CLOSED) { | ||
// client closed before server accepted connection | ||
self.emit('close'); | ||
socket.end(); | ||
return; | ||
} | ||
var serverKey = res.headers['sec-websocket-accept']; | ||
if (typeof serverKey == 'undefined' || serverKey !== expectedServerKey) { | ||
self.emit('error', 'invalid server key'); | ||
socket.end(); | ||
return; | ||
} | ||
establishHybiConnection.call(self, socket, upgradeHead); | ||
}); | ||
req.end(); | ||
Object.defineProperty(this, '_readyState', { writable: true, value: WebSocket.CONNECTING }); | ||
} | ||
function establishHybiConnection(socket, upgradeHead) { | ||
this._socket = socket; | ||
@@ -467,5 +494,72 @@ socket.setTimeout(0); | ||
if (upgradeHead && upgradeHead.length > 0) receiver.add(upgradeHead); | ||
if (upgradeHead && upgradeHead.length > 0) { | ||
process.nextTick(function() { | ||
receiver.add(upgradeHead); | ||
}); | ||
} | ||
} | ||
function establishHixieConnection(socket, upgradeHead) { | ||
this._socket = socket; | ||
socket.setTimeout(0); | ||
socket.setNoDelay(true); | ||
var self = this; | ||
socket.on('end', function() { | ||
if (self.readyState == WebSocket.CLOSED) return; | ||
self._readyState = WebSocket.CLOSED; | ||
self.emit('close', self._closeCode || 1000, self._closeMessage || ''); | ||
}); | ||
socket.on('close', function() { | ||
if (self.readyState == WebSocket.CLOSED) return; | ||
self._readyState = WebSocket.CLOSED; | ||
self.emit('close', self._closeCode || 1000, self._closeMessage || ''); | ||
}); | ||
var receiver = new ReceiverHixie(); | ||
socket.on('data', function (data) { | ||
receiver.add(data); | ||
}); | ||
receiver.on('text', function (data, flags) { | ||
flags = flags || {}; | ||
self.emit('message', data, flags); | ||
}); | ||
receiver.on('binary', function (data, flags) { | ||
flags = flags || {}; | ||
flags.binary = true; | ||
self.emit('message', data, flags); | ||
}); | ||
receiver.on('ping', function(data, flags) { | ||
flags = flags || {}; | ||
self.pong(data, {mask: !self._isServer, binary: flags.binary === true}); | ||
self.emit('ping', data, flags); | ||
}); | ||
receiver.on('pong', function(data, flags) { | ||
self.emit('pong', data, flags); | ||
}); | ||
receiver.on('close', function(code, data, flags) { | ||
flags = flags || {}; | ||
self.close(code, data, {mask: !self._isServer}); | ||
}); | ||
receiver.on('error', function(reason, errorCode) { | ||
// close the connection when the receiver reports a HyBi error code | ||
if (typeof errorCode !== 'undefined') { | ||
self.close(errorCode, '', {mask: !self._isServer}); | ||
} | ||
self.emit('error', reason, errorCode); | ||
}); | ||
Object.defineProperty(this, '_sender', { value: new SenderHixie(socket) }); | ||
this._sender.on('error', function(error) { | ||
self.emit('error', error); | ||
}); | ||
this._readyState = WebSocket.OPEN; | ||
this.emit('open'); | ||
if (upgradeHead && upgradeHead.length > 0) { | ||
process.nextTick(function() { | ||
receiver.add(upgradeHead); | ||
}); | ||
} | ||
} | ||
function startQueue(instance) { | ||
@@ -472,0 +566,0 @@ instance._queue = instance._queue || []; |
@@ -14,2 +14,3 @@ /*! | ||
, WebSocket = require('./WebSocket') | ||
, tls = require('tls') | ||
, url = require('url'); | ||
@@ -26,5 +27,6 @@ | ||
server: null, | ||
verifyOrigin: null, | ||
verifyClient: null, | ||
path: null, | ||
noServer: false | ||
noServer: false, | ||
disableHixie: false | ||
}).merge(options); | ||
@@ -70,4 +72,5 @@ if (!options.value.port && !options.value.server && !options.value.noServer) { | ||
this._server.on('upgrade', function(req, socket, upgradeHead) { | ||
var client = self.handleUpgrade(req, socket, upgradeHead); | ||
if (client) self.emit('connection', client); | ||
self.handleUpgrade(req, socket, upgradeHead, function(client) { | ||
self.emit('connection', client); | ||
}); | ||
}); | ||
@@ -91,3 +94,3 @@ } | ||
/** | ||
* Immediately shuts down the connection | ||
* Immediately shuts down the connection. | ||
* | ||
@@ -129,3 +132,9 @@ * @api public | ||
WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead) { | ||
/** | ||
* Handle a HTTP Upgrade request. | ||
* | ||
* @api public | ||
*/ | ||
WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) { | ||
// check for wrong path | ||
@@ -142,2 +151,14 @@ if (this.options.path) { | ||
if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); | ||
else handleHybiUpgrade.apply(this, arguments); | ||
} | ||
module.exports = WebSocketServer; | ||
/** | ||
* Entirely private apis, | ||
* which may or may not be bound to a sepcific WebSocket instance. | ||
*/ | ||
function handleHybiUpgrade(req, socket, upgradeHead, cb) { | ||
// verify key presence | ||
@@ -156,8 +177,12 @@ if (!req.headers['sec-websocket-key']) { | ||
// verify origin | ||
// verify client | ||
var origin = version < 13 ? | ||
req.headers['sec-websocket-origin'] : | ||
req.headers['origin']; | ||
if (typeof this.options.verifyOrigin == 'function') { | ||
if (!this.options.verifyOrigin(origin)) { | ||
if (typeof this.options.verifyClient == 'function') { | ||
var info = { | ||
origin: origin, | ||
secure: typeof req.connection.encrypted !== 'undefined' | ||
}; | ||
if (!this.options.verifyClient(info)) { | ||
abortConnection(socket, 401, 'Unauthorized'); | ||
@@ -194,3 +219,3 @@ return; | ||
socket.setNoDelay(true); | ||
var client = new WebSocket(Array.prototype.slice.call(arguments, 0), { | ||
var client = new WebSocket([req, socket, upgradeHead], { | ||
protocolVersion: version, | ||
@@ -207,12 +232,123 @@ protocol: protocol | ||
}); | ||
return client; | ||
cb(client); | ||
} | ||
module.exports = WebSocketServer; | ||
function handleHixieUpgrade(req, socket, upgradeHead, cb) { | ||
if (this.options.disableHixie) { | ||
abortConnection(socket, 401, 'Hixie support disabled'); | ||
return; | ||
} | ||
/** | ||
* Entirely private apis, | ||
* which may or may not be bound to a sepcific WebSocket instance. | ||
*/ | ||
// verify key presence | ||
if (!req.headers['sec-websocket-key2']) { | ||
abortConnection(socket, 400, 'Bad Request'); | ||
return; | ||
} | ||
// verify client | ||
var location = (socket.encrypted ? 'wss' : 'ws') + '://' + req.headers.host + req.url | ||
, origin = req.headers['origin']; | ||
if (typeof this.options.verifyClient == 'function') { | ||
var info = { | ||
origin: origin, | ||
secure: typeof req.connection.encrypted !== 'undefined' | ||
}; | ||
if (!this.options.verifyClient(info)) { | ||
abortConnection(socket, 401, 'Unauthorized'); | ||
return; | ||
} | ||
} | ||
var protocol = req.headers['sec-websocket-protocol']; | ||
var completeHandshake = function(nonce, rest) { | ||
// calculate key | ||
var k1 = req.headers['sec-websocket-key1'] | ||
, k2 = req.headers['sec-websocket-key2'] | ||
, md5 = crypto.createHash('md5') | ||
, self = this; | ||
[k1, k2].forEach(function (k) { | ||
var n = parseInt(k.replace(/[^\d]/g, '')) | ||
, spaces = k.replace(/[^ ]/g, '').length; | ||
if (spaces === 0 || n % spaces !== 0){ | ||
abortConnection(socket, 400, 'Bad Request'); | ||
return; | ||
} | ||
n /= spaces; | ||
md5.update(String.fromCharCode( | ||
n >> 24 & 0xFF, | ||
n >> 16 & 0xFF, | ||
n >> 8 & 0xFF, | ||
n & 0xFF)); | ||
}); | ||
md5.update(nonce.toString('binary')); | ||
var headers = [ | ||
'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); | ||
} | ||
socket.setTimeout(0); | ||
socket.setNoDelay(true); | ||
try { | ||
socket.write(headers.concat('', '').join('\r\n')); | ||
socket.write(md5.digest('binary'), 'binary'); | ||
} | ||
catch (e) { | ||
try { socket.end(); } catch (_) {} | ||
return; | ||
} | ||
var client = new WebSocket([req, socket, rest], { | ||
protocolVersion: 'hixie-76', | ||
protocol: protocol | ||
}); | ||
this._clients.push(client); | ||
var self = this; | ||
client.on('close', function() { | ||
var index = self._clients.indexOf(client); | ||
if (index != -1) { | ||
self._clients.splice(index, 1); | ||
} | ||
}); | ||
cb(client); | ||
} | ||
var nonceLength = 8; | ||
if (upgradeHead && upgradeHead.length >= nonceLength) { | ||
var nonce = upgradeHead.slice(0, nonceLength); | ||
var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; | ||
completeHandshake.call(this, nonce, rest); | ||
} | ||
else { | ||
// nonce not present in upgradeHead, so we must wait for enough data | ||
// data to arrive before continuing | ||
var nonce = new Buffer(nonceLength); | ||
upgradeHead.copy(nonce, 0); | ||
var received = upgradeHead.length; | ||
var rest = null; | ||
var self = this; | ||
var handler = function (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); | ||
completeHandshake.call(self, nonce, rest); | ||
} | ||
} | ||
socket.on('data', handler); | ||
} | ||
} | ||
function abortConnection(socket, code, name) { | ||
@@ -219,0 +355,0 @@ try { |
{ | ||
"author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)", | ||
"name": "ws", | ||
"description": "simple and very fast websocket protocol client for node.js", | ||
"version": "0.4.1", | ||
"description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", | ||
"version": "0.4.2", | ||
"repository": { | ||
@@ -7,0 +7,0 @@ "type": "git", |
[![Build Status](https://secure.travis-ci.org/einaros/ws.png)](http://travis-ci.org/einaros/ws) | ||
# ws: a node.js websocket implementation # | ||
# ws: a node.js websocket library # | ||
`ws` is a simple to use websocket implementation, up-to-date against RFC-6455. | ||
`ws` is a simple to use websocket implementation, up-to-date against RFC-6455, and [probably the fastest WebSocket library for node.js](http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs). | ||
It is probably also the fastest websocket library running on node.js (http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs). | ||
Passes the quite extensible Autobahn test suite. See http://einaros.github.com/ws for the full reports. | ||
The module also comes with a command line utility, `wscat`, which can either act as a server (--listen), or client (--connect); Use it to debug simple websocket services. | ||
Comes with a command line utility, `wscat`, which can either act as a server (--listen), or client (--connect); Use it to debug simple websocket services. | ||
## 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`, or argument `-p 8` for wscat) | ||
* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`, or argument `-p 13` for wscat) | ||
See the echo.websocket.org example below for how to use the `protocolVersion` option. | ||
_See the echo.websocket.org example below for how to use the `protocolVersion` option._ | ||
@@ -105,2 +104,20 @@ ## Usage ## | ||
## API Docs ## | ||
_Note: This api documentation is currently incomplete. For a better understanding of the api, see the test set._ | ||
### WebSocket | ||
`supports.binary` | ||
> Returns true or false based on whether the underlying protocol (hixie, hybi, etc) has binary support. | ||
Example: | ||
```js | ||
var wss = new WebSocketServer({port: 8080}); | ||
wss.on('connection', function(client) { | ||
if (client.supports.binary) ... | ||
}); | ||
``` | ||
## Todos ## | ||
@@ -111,3 +128,3 @@ | ||
* Either expose a configurable setting indicating favoring speed or memory use, or do a timer based shrink of Receiver's pools. | ||
* Consider rewriting add() without copy: slice the buffer, then copy to overflow. | ||
* Make necessary changes to also support the even older hixie-75? Or at least write a few more tests for Hixie-76 to verify that fragmented nonce transfers really work. | ||
@@ -114,0 +131,0 @@ ## License ## |
var assert = require('assert') | ||
, Parser = require('../lib/Receiver'); | ||
, Receiver = require('../lib/Receiver'); | ||
require('should'); | ||
@@ -8,3 +8,3 @@ require('./hybi-common'); | ||
it('can parse unmasked text message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var packet = '81 05 48 65 6c 6c 6f'; | ||
@@ -22,3 +22,3 @@ | ||
it('can parse close message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var packet = '88 00'; | ||
@@ -35,3 +35,3 @@ | ||
it('can parse masked text message', function() { | ||
var p = new Parser(); | ||
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'; | ||
@@ -49,3 +49,3 @@ | ||
it('can parse a masked text message longer than 125 bytes', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var message = 'A'; | ||
@@ -65,3 +65,3 @@ for (var i = 0; i < 300; ++i) message += (i % 5).toString(); | ||
it('can parse a really long masked text message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var message = 'A'; | ||
@@ -81,3 +81,3 @@ for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString(); | ||
it('can parse a fragmented masked text message of 300 bytes', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var message = 'A'; | ||
@@ -101,3 +101,3 @@ for (var i = 0; i < 300; ++i) message += (i % 5).toString(); | ||
it('can parse a ping message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var message = 'Hello'; | ||
@@ -116,3 +116,3 @@ var packet = '89 ' + getHybiLengthAsHexString(message.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); | ||
it('can parse a ping with no data', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var packet = '89 00'; | ||
@@ -129,3 +129,3 @@ | ||
it('can parse a fragmented masked text message of 300 bytes with a ping in the middle', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var message = 'A'; | ||
@@ -161,3 +161,3 @@ for (var i = 0; i < 300; ++i) message += (i % 5).toString(); | ||
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 Parser(); | ||
var p = new Receiver(); | ||
var message = 'A'; | ||
@@ -197,3 +197,3 @@ for (var i = 0; i < 300; ++i) message += (i % 5).toString(); | ||
it('can parse a 100 byte long masked binary message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var length = 100; | ||
@@ -215,3 +215,3 @@ var message = new Buffer(length); | ||
it('can parse a 256 byte long masked binary message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var length = 256; | ||
@@ -233,3 +233,3 @@ var message = new Buffer(length); | ||
it('can parse a 200kb long masked binary message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var length = 200 * 1024; | ||
@@ -251,3 +251,3 @@ var message = new Buffer(length); | ||
it('can parse a 200kb long unmasked binary message', function() { | ||
var p = new Parser(); | ||
var p = new Receiver(); | ||
var length = 200 * 1024; | ||
@@ -254,0 +254,0 @@ var message = new Buffer(length); |
var assert = require('assert') | ||
, https = require('https') | ||
, http = require('http') | ||
, should = require('should') | ||
, WebSocket = require('../') | ||
@@ -54,2 +56,15 @@ , WebSocketServer = require('../').Server | ||
it('#protocolVersion exposes the protocol version', function(done) { | ||
server.createServer(++port, function(srv) { | ||
var url = 'ws://localhost:' + port; | ||
var ws = new WebSocket(url); | ||
assert.equal(13, ws.protocolVersion); | ||
ws.terminate(); | ||
ws.on('close', function() { | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#readyState', function() { | ||
@@ -152,45 +167,47 @@ it('defaults to connecting', function(done) { | ||
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() { | ||
assert.fail('connect shouldnt be raised here'); | ||
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() { | ||
assert.fail('connect shouldnt be raised here'); | ||
}); | ||
ws.on('close', function() { | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
ws.on('close', function() { | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
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() { | ||
assert.fail('connect shouldnt be raised here'); | ||
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() { | ||
assert.fail('connect shouldnt be raised here'); | ||
}); | ||
ws.on('close', function() { | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
ws.on('close', function() { | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
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() { | ||
srv.close(); | ||
done(); | ||
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() { | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
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() { | ||
srv.close(); | ||
done(); | ||
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() { | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
@@ -1259,2 +1276,42 @@ }); | ||
}); | ||
describe('protocol support discovery', function() { | ||
describe('#supports', function() { | ||
describe('#binary', function() { | ||
it('returns true for hybi transport', function(done) { | ||
var wss = new WebSocketServer({port: ++port}, function() { | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
}); | ||
wss.on('connection', function(client) { | ||
assert.equal(true, client.supports.binary); | ||
wss.close(); | ||
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(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
var http = require('http') | ||
, https = require('https') | ||
, WebSocket = require('../') | ||
@@ -183,101 +184,342 @@ , WebSocketServer = WebSocket.Server | ||
describe('connection establishing', function() { | ||
it('does not accept connections with no sec-websocket-key', function(done) { | ||
describe('#clients', function() { | ||
it('returns a list of connected clients', 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.clients.length.should.eql(0); | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
}); | ||
wss.on('connection', function(client) { | ||
wss.clients.length.should.eql(1); | ||
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.length.should.eql(0); | ||
wss.close(); | ||
done(); | ||
}); | ||
ws.terminate(); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
it('does not accept connections with no sec-websocket-version', function(done) { | ||
it('is updated when client closes the connection', function(done) { | ||
var ws; | ||
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); | ||
ws = new WebSocket('ws://localhost:' + port); | ||
}); | ||
wss.on('connection', function(client) { | ||
client.on('close', function() { | ||
wss.clients.length.should.eql(0); | ||
wss.close(); | ||
done(); | ||
}); | ||
ws.close(); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
}); | ||
it('does not accept connections with invalid sec-websocket-version', function(done) { | ||
describe('#options', function() { | ||
it('exposes options passed to constructor', 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.options.port.should.eql(port); | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
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'); | ||
}); | ||
}); | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
ws.on('message', function(message) { | ||
message.should.eql('hello'); | ||
wss.close(); | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
}); | ||
it('does not accept connections with invalid sec-websocket-origin (8)', function(done) { | ||
var wss = new WebSocketServer({port: ++port, verifyOrigin: function(o) { | ||
o.should.eql('http://foobar.com'); | ||
return false; | ||
}}, 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', | ||
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(); | ||
}); | ||
}); | ||
wss.on('connection', function(ws) { | ||
done(new Error('connection must not be established')); | ||
}); | ||
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-Key': 'dGhlIHNhbXBsZSBub25jZQ==', | ||
'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(); | ||
}); | ||
}); | ||
}); | ||
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-Key': 'dGhlIHNhbXBsZSBub25jZQ==', | ||
'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 wss = new WebSocketServer({port: ++port, verifyClient: function(info) { | ||
info.origin.should.eql('http://foobarbaz.com'); | ||
return false; | ||
}}, function() { | ||
var options = { | ||
port: port, | ||
host: '127.0.0.1', | ||
headers: { | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'websocket', | ||
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', | ||
'Sec-WebSocket-Version': 13, | ||
'Origin': 'http://foobarbaz.com' | ||
} | ||
}; | ||
var req = http.request(options); | ||
req.end(); | ||
req.on('response', function(res) { | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
it('verifyClient has secure:true for ssl connections', function(done) { | ||
var options = { | ||
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' | ||
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; | ||
} | ||
}; | ||
var req = http.request(options); | ||
req.end(); | ||
req.on('response', function(res) { | ||
res.statusCode.should.eql(401); | ||
}); | ||
app.listen(++port, function() { | ||
var ws = new WebSocket('wss://localhost:' + port); | ||
}); | ||
wss.on('connection', function(ws) { | ||
app.close(); | ||
ws.terminate(); | ||
wss.close(); | ||
success.should.be.ok; | ||
done(); | ||
}); | ||
}); | ||
wss.on('error', function() {}); | ||
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); | ||
}); | ||
wss.on('connection', function(ws) { | ||
app.close(); | ||
ws.terminate(); | ||
wss.close(); | ||
success.should.be.ok; | ||
done(); | ||
}); | ||
}); | ||
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-Key': 'dGhlIHNhbXBsZSBub25jZQ==', | ||
'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.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('messaging', function() { | ||
it('can send data', function(done) { | ||
var wss = new WebSocketServer({port: ++port}, function() { | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
ws.on('message', function(data, flags) { | ||
data.should.eql('hello!'); | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
wss.on('connection', function(client) { | ||
client.send('hello!'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('does not accept connections with invalid origin', function(done) { | ||
var wss = new WebSocketServer({port: ++port, verifyOrigin: function(o) { | ||
o.should.eql('http://foobar.com'); | ||
return false; | ||
}}, function() { | ||
describe('hixie mode', function() { | ||
it('can be disabled', function(done) { | ||
var wss = new WebSocketServer({port: ++port, disableHixie: true}, function() { | ||
var options = { | ||
@@ -288,32 +530,215 @@ port: port, | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'websocket', | ||
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', | ||
'Sec-WebSocket-Version': 13, | ||
'Origin': 'http://foobar.com' | ||
'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); | ||
wss.close(); | ||
done(); | ||
process.nextTick(function() { | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
wss.on('connection', function(ws) { | ||
done(new Error('connection must not be established')); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
}); | ||
it('can send data', function(done) { | ||
var wss = new WebSocketServer({port: ++port}, function() { | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
ws.on('message', function(data, flags) { | ||
data.should.eql('hello!'); | ||
wss.close(); | ||
done(); | ||
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 wss = new WebSocketServer({port: ++port, verifyClient: function(info) { | ||
info.origin.should.eql('http://foobarbaz.com'); | ||
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) { | ||
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() {}); | ||
}); | ||
}); | ||
wss.on('connection', function(client) { | ||
client.send('hello!'); | ||
}); | ||
}); | ||
@@ -356,76 +781,2 @@ | ||
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); | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
}); | ||
wss.on('connection', function(client) { | ||
wss.clients.length.should.eql(1); | ||
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.length.should.eql(0); | ||
wss.close(); | ||
done(); | ||
}); | ||
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.length.should.eql(0); | ||
wss.close(); | ||
done(); | ||
}); | ||
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); | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
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) { | ||
var client = wss.handleUpgrade(req, socket, upgradeHead); | ||
client.send('hello'); | ||
}); | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
ws.on('message', function(message) { | ||
message.should.eql('hello'); | ||
wss.close(); | ||
srv.close(); | ||
done(); | ||
}) | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
186942
44
4926
153
9