Comparing version 0.4.3 to 0.4.5
@@ -0,1 +1,13 @@ | ||
v0.4.5 - Feb 7th 2012 | ||
===================== | ||
* Corrected regression bug in handling of connections with the initial frame delivered across both http upgrade head and a standalone packet. This would lead to a race condition, which in some cases could cause message corruption. [einaros] | ||
v0.4.4 - Feb 6th 2012 | ||
===================== | ||
* Pass original request object to verifyClient, for cookie or authentication verifications. [einaros] | ||
* Implemented addEventListener and slightly improved the emulation API by adding a MessageEvent with a readonly data attribute. [aslakhellesoy] | ||
* Rewrite parts of hybi receiver to avoid stack overflows for large amounts of packets bundled in the same buffer / packet. [einaros] | ||
v0.4.3 - Feb 4th 2012 | ||
@@ -2,0 +14,0 @@ ===================== |
@@ -47,3 +47,3 @@ /*! | ||
if (self.state == EMPTY && data[0] != 0x00) { | ||
self.error('payload must start with 0x00 byte'); | ||
self.error('payload must start with 0x00 byte', true); | ||
return; | ||
@@ -58,3 +58,3 @@ } | ||
} | ||
else self.spanLength += data.length; | ||
else self.spanLength += data.length; | ||
} | ||
@@ -95,5 +95,5 @@ while(data) data = doAdd(); | ||
Receiver.prototype.error = function (reason) { | ||
Receiver.prototype.error = function (reason, terminate) { | ||
this.reset(); | ||
this.emit('error', reason); | ||
this.emit('error', reason, terminate); | ||
return this; | ||
@@ -100,0 +100,0 @@ } |
@@ -25,3 +25,3 @@ /*! | ||
function Receiver () { | ||
function Receiver () { | ||
// memory pool for fragmented messages | ||
@@ -32,7 +32,7 @@ var fragmentedPoolPrevUsed = -1; | ||
}, function(db) { | ||
return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? | ||
(fragmentedPoolPrevUsed + db.used) / 2 : | ||
return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? | ||
(fragmentedPoolPrevUsed + db.used) / 2 : | ||
db.used; | ||
}); | ||
// memory pool for unfragmented messages | ||
@@ -43,7 +43,7 @@ var unfragmentedPoolPrevUsed = -1; | ||
}, function(db) { | ||
return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? | ||
return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? | ||
(unfragmentedPoolPrevUsed + db.used) / 2 : | ||
db.used; | ||
}); | ||
this.state = { | ||
@@ -57,3 +57,3 @@ activeFragmentedOperation: null, | ||
this.overflow = []; | ||
this.headerBuffer = new Buffer(10); | ||
this.headerBuffer = new Buffer(10); | ||
this.expectOffset = 0; | ||
@@ -81,2 +81,4 @@ this.expectBuffer = null; | ||
Receiver.prototype.add = function(data) { | ||
var dataLength = data.length; | ||
if (dataLength == 0) return; | ||
if (this.expectBuffer == null) { | ||
@@ -86,29 +88,9 @@ this.overflow.push(data); | ||
} | ||
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset); | ||
var dest = this.expectBuffer; | ||
var offset = this.expectOffset; | ||
switch (toRead) { | ||
default: data.copy(dest, offset, 0, toRead); break; | ||
case 16: dest[offset+15] = data[15]; | ||
case 15: dest[offset+14] = data[14]; | ||
case 14: dest[offset+13] = data[13]; | ||
case 13: dest[offset+12] = data[12]; | ||
case 12: dest[offset+11] = data[11]; | ||
case 11: dest[offset+10] = data[10]; | ||
case 10: dest[offset+9] = data[9]; | ||
case 9: dest[offset+8] = data[8]; | ||
case 8: dest[offset+7] = data[7]; | ||
case 7: dest[offset+6] = data[6]; | ||
case 6: dest[offset+5] = data[5]; | ||
case 5: dest[offset+4] = data[4]; | ||
case 4: dest[offset+3] = data[3]; | ||
case 3: dest[offset+2] = data[2]; | ||
case 2: dest[offset+1] = data[1]; | ||
case 1: dest[offset] = data[0]; | ||
} | ||
var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); | ||
fastCopy(toRead, data, this.expectBuffer, this.expectOffset); | ||
this.expectOffset += toRead; | ||
if (toRead < data.length) { | ||
this.overflow.push(data.slice(toRead, data.length)); | ||
if (toRead < dataLength) { | ||
this.overflow.push(data.slice(toRead, dataLength)); | ||
} | ||
if (this.expectOffset == this.expectBuffer.length) { | ||
while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { | ||
var bufferForHandler = this.expectBuffer; | ||
@@ -136,6 +118,7 @@ this.expectBuffer = null; | ||
while (toRead > 0 && this.overflow.length > 0) { | ||
var buf = this.overflow.pop(); | ||
if (toRead < buf.length) this.overflow.push(buf.slice(toRead)); | ||
var read = Math.min(buf.length, toRead); | ||
this.add(buf.slice(0, read)); | ||
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; | ||
@@ -157,10 +140,10 @@ } | ||
this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); | ||
this.expectOffset = 0; | ||
this.expectHandler = handler; | ||
var toRead = length; | ||
while (toRead > 0 && this.overflow.length > 0) { | ||
var buf = this.overflow.pop(); | ||
if (toRead < buf.length) this.overflow.push(buf.slice(toRead)); | ||
var read = Math.min(buf.length, toRead); | ||
this.add(buf.slice(0, read)); | ||
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; | ||
@@ -321,2 +304,24 @@ } | ||
function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { | ||
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]; | ||
} | ||
} | ||
/** | ||
@@ -323,0 +328,0 @@ * Opcode handlers |
@@ -55,3 +55,5 @@ /*! | ||
if (Object.prototype.toString.call(address) == '[object Array]') initAsServerClient.apply(this, address.concat(options)); | ||
if (Object.prototype.toString.call(address) == '[object Array]') { | ||
initAsServerClient.apply(this, address.concat(options)); | ||
} | ||
else initAsClient.apply(this, arguments); | ||
@@ -61,2 +63,8 @@ } | ||
/** | ||
* Inherits from EventEmitter. | ||
*/ | ||
util.inherits(WebSocket, events.EventEmitter); | ||
/** | ||
* Ready States | ||
@@ -80,8 +88,2 @@ */ | ||
/** | ||
* Inherits from EventEmitter. | ||
*/ | ||
util.inherits(WebSocket, events.EventEmitter); | ||
/** | ||
* Gracefully closes the connection, after sending a description message to the server | ||
@@ -243,3 +245,3 @@ * | ||
/** | ||
* Emulates the Browser based WebSocket interface. | ||
* Emulates the W3C Browser based WebSocket interface using function members. | ||
* | ||
@@ -274,18 +276,3 @@ * @see http://dev.w3.org/html5/websockets/#the-websocket-interface | ||
this.removeAllListeners(method); | ||
if (typeof listener === 'function') { | ||
// Special case for messages as we need to wrap the response here to | ||
// emulate a WebSocket event response. | ||
if (method === 'message') { | ||
function message (data) { | ||
listener.call(this, { data: data }); | ||
} | ||
// store a reference so we can return the origional function again | ||
message._listener = listener; | ||
this.on(method, message); | ||
} else { | ||
this.on(method, listener); | ||
} | ||
} | ||
this.addEventListener(method, listener); | ||
} | ||
@@ -295,5 +282,42 @@ }); | ||
/** | ||
* Emulates the W3C Browser based WebSocket interface using addEventListener. | ||
* | ||
* @see https://developer.mozilla.org/en/DOM/element.addEventListener | ||
* @see http://dev.w3.org/html5/websockets/#the-websocket-interface | ||
* @api public | ||
*/ | ||
WebSocket.prototype.addEventListener = function(method, listener) { | ||
if (typeof listener === 'function') { | ||
// Special case for messages as we need to wrap the data | ||
// in a MessageEvent object. | ||
if (method === 'message') { | ||
function onMessage (data) { | ||
listener.call(this, new MessageEvent(data)); | ||
} | ||
// store a reference so we can return the origional function again | ||
onMessage._listener = listener; | ||
this.on(method, onMessage); | ||
} else { | ||
this.on(method, listener); | ||
} | ||
} | ||
} | ||
module.exports = WebSocket; | ||
/** | ||
* W3C MessageEvent | ||
* | ||
* @see http://www.w3.org/TR/html5/comms.html | ||
* @api private | ||
*/ | ||
function MessageEvent(dataArg) { | ||
// Currently only the data attribute is implemented. More can be added later if needed. | ||
Object.defineProperty(this, 'data', { writable: false, value: dataArg }); | ||
} | ||
/** | ||
* Entirely private apis, | ||
@@ -433,2 +457,4 @@ * which may or may not be bound to a sepcific WebSocket instance. | ||
var self = this; | ||
// socket cleanup handlers | ||
socket.on('end', function() { | ||
@@ -464,5 +490,32 @@ if (self.readyState == WebSocket.CLOSED) return; | ||
var receiver = new ReceiverClass(); | ||
socket.on('data', function (data) { | ||
// ensure that the upgradeHead is added to the receiver | ||
function firstHandler(data) { | ||
if (upgradeHead != null) { | ||
var head = upgradeHead; | ||
upgradeHead = null; | ||
receiver.add(head); | ||
} | ||
dataHandler = realHandler; | ||
receiver.add(data); | ||
}); | ||
} | ||
// subsequent packets are pushed straight to the receiver | ||
function realHandler(data) { receiver.add(data); } | ||
var dataHandler = firstHandler; | ||
socket.on('data', dataHandler); | ||
// 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. | ||
if (upgradeHead && upgradeHead.length > 0) { | ||
process.nextTick(function() { | ||
if (upgradeHead) { | ||
var toSend = upgradeHead; | ||
upgradeHead = null; | ||
receiver.add(toSend); | ||
} | ||
}); | ||
} | ||
// receiver event handlers | ||
receiver.on('text', function (data, flags) { | ||
@@ -497,2 +550,3 @@ flags = flags || {}; | ||
// finalize the client | ||
Object.defineProperty(this, '_sender', { value: new SenderClass(socket) }); | ||
@@ -504,8 +558,2 @@ this._sender.on('error', function(error) { | ||
this.emit('open'); | ||
if (upgradeHead && upgradeHead.length > 0) { | ||
process.nextTick(function() { | ||
receiver.add(upgradeHead); | ||
}); | ||
} | ||
} | ||
@@ -512,0 +560,0 @@ |
@@ -178,3 +178,4 @@ /*! | ||
origin: origin, | ||
secure: typeof req.connection.encrypted !== 'undefined' | ||
secure: typeof req.connection.encrypted !== 'undefined', | ||
req: req | ||
}; | ||
@@ -246,3 +247,4 @@ if (!this.options.verifyClient(info)) { | ||
origin: origin, | ||
secure: typeof req.connection.encrypted !== 'undefined' | ||
secure: typeof req.connection.encrypted !== 'undefined', | ||
req: req | ||
}; | ||
@@ -249,0 +251,0 @@ if (!this.options.verifyClient(info)) { |
@@ -5,3 +5,3 @@ { | ||
"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.3", | ||
"version": "0.4.5", | ||
"repository": { | ||
@@ -8,0 +8,0 @@ "type": "git", |
@@ -6,7 +6,7 @@ var assert = require('assert') | ||
describe('Receiver', function() { | ||
describe('Receiver', function() { | ||
it('can parse text message', function() { | ||
var p = new Receiver(); | ||
var packet = '00 48 65 6c 6c 6f ff'; | ||
var gotData = false; | ||
@@ -17,3 +17,3 @@ p.on('text', function(data) { | ||
}); | ||
p.add(getBufferFromHexString(packet)); | ||
@@ -26,3 +26,3 @@ gotData.should.be.ok; | ||
var packet = '00 48 65 6c 6c 6f ff 00 48 65 6c 6c 6f ff'; | ||
var gotData = false; | ||
@@ -34,3 +34,3 @@ var messages = []; | ||
}); | ||
p.add(getBufferFromHexString(packet)); | ||
@@ -53,3 +53,3 @@ gotData.should.be.ok; | ||
]; | ||
var gotData = false; | ||
@@ -61,3 +61,3 @@ var messages = []; | ||
}); | ||
for (var i = 0; i < packets.length; ++i) { | ||
@@ -82,3 +82,3 @@ p.add(getBufferFromHexString(packets[i])); | ||
]; | ||
var gotData = false; | ||
@@ -90,3 +90,3 @@ var messages = []; | ||
}); | ||
for (var i = 0; i < packets.length; ++i) { | ||
@@ -115,3 +115,3 @@ p.add(getBufferFromHexString(packets[i])); | ||
]; | ||
var gotData = false; | ||
@@ -124,6 +124,6 @@ var gotError = false; | ||
}); | ||
p.on('error', function() { | ||
gotError = true; | ||
p.on('error', function(reason, code) { | ||
gotError = code == true; | ||
}); | ||
for (var i = 0; i < packets.length && !gotError; ++i) { | ||
@@ -130,0 +130,0 @@ p.add(getBufferFromHexString(packets[i])); |
@@ -1107,3 +1107,3 @@ var assert = require('assert') | ||
describe('API emulation', function() { | ||
describe('W3C API emulation', function() { | ||
it('should not throw errors when getting and setting', function(done) { | ||
@@ -1138,4 +1138,4 @@ server.createServer(++port, function(srv) { | ||
ws.onmessage = function(data) { | ||
assert.ok(!!data.data); | ||
ws.onmessage = function(messageEvent) { | ||
assert.ok(!!messageEvent.data); | ||
++message; | ||
@@ -1170,2 +1170,18 @@ ws.close(); | ||
}); | ||
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.send('hi'); | ||
}); | ||
ws.addEventListener('message', function(messageEvent) { | ||
assert.equal('hi', messageEvent.data); | ||
ws.terminate(); | ||
srv.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -1172,0 +1188,0 @@ |
@@ -394,4 +394,6 @@ var http = require('http') | ||
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; | ||
@@ -413,2 +415,3 @@ }}, function() { | ||
req.on('response', function(res) { | ||
verifyClientCalled.should.be.ok; | ||
wss.close(); | ||
@@ -421,2 +424,31 @@ done(); | ||
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 = { | ||
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) { | ||
verifyClientCalled.should.be.ok; | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
it('verifyClient has secure:true for ssl connections', function(done) { | ||
@@ -500,3 +532,3 @@ var options = { | ||
wss.close(); | ||
done(); | ||
done(); | ||
}); | ||
@@ -507,17 +539,25 @@ }); | ||
}); | ||
describe('messaging', function() { | ||
it('can send data', function(done) { | ||
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(data, flags) { | ||
data.should.eql('hello!'); | ||
ws.on('message', function(message, flags) { | ||
ws.send(message); | ||
}); | ||
}); | ||
wss.on('connection', function(client) { | ||
client.on('message', function(message) { | ||
message.should.eql(data); | ||
wss.close(); | ||
done(); | ||
}); | ||
client.send(data); | ||
}); | ||
wss.on('connection', function(client) { | ||
client.send('hello!'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -689,4 +729,6 @@ }); | ||
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; | ||
@@ -709,2 +751,3 @@ }}, function() { | ||
req.on('response', function(res) { | ||
verifyClientCalled.should.be.ok; | ||
wss.close(); | ||
@@ -717,2 +760,32 @@ done(); | ||
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('handles messages passed along with the upgrade request (upgrade head)', function(done) { | ||
@@ -743,3 +816,3 @@ var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { | ||
wss.close(); | ||
done(); | ||
done(); | ||
}); | ||
@@ -749,3 +822,2 @@ }); | ||
}); | ||
}); | ||
@@ -752,0 +824,0 @@ }); |
Sorry, the diff of this file is not supported yet
191412
5011