mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a300223122 | ||
|
|
0769c40368 | ||
|
|
355203afdb | ||
|
|
46bfcd0d83 | ||
|
|
46b2f86372 | ||
|
|
e5c86178f5 | ||
|
|
0c31d6eabf | ||
|
|
0e08d67e48 | ||
|
|
42904cb3d7 | ||
|
|
8efb1bc6e2 | ||
|
|
8bdf221935 | ||
|
|
899fb7faa1 | ||
|
|
34622b74ef | ||
|
|
d327976064 | ||
|
|
07b9c4696d | ||
|
|
7a5913b8a6 | ||
|
|
fbb268fbce | ||
|
|
a31c267e83 | ||
|
|
b82fd79f57 | ||
|
|
f85ce74a1f | ||
|
|
65f1399a44 | ||
|
|
894ec9f84e | ||
|
|
d86ffcf06d | ||
|
|
ab5beaff63 | ||
|
|
f0ef33b45f | ||
|
|
a4ec5aafa6 | ||
|
|
984639ba67 | ||
|
|
9923c1dee9 | ||
|
|
1b0a4849df | ||
|
|
4fc43f322f | ||
|
|
5c0f78ab02 | ||
|
|
d8c7060cc8 | ||
|
|
b0335b0a61 | ||
|
|
a1797ccd4b | ||
|
|
a1c997bc58 | ||
|
|
0b9b28d251 | ||
|
|
a79b2fa761 | ||
|
|
195eba74de | ||
|
|
3edebe5d61 | ||
|
|
b56389fbc8 | ||
|
|
203293db0b | ||
|
|
2b7ea448c4 | ||
|
|
c627f1b7d0 | ||
|
|
4c20afd4b7 | ||
|
|
f689434f61 | ||
|
|
b694ee68c9 | ||
|
|
8b22ca2ffd | ||
|
|
5c50c4844f | ||
|
|
4fcad6e4bc | ||
|
|
3b2316e0d8 | ||
|
|
a821cce390 | ||
|
|
abe142ac66 | ||
|
|
5eff0e5ca7 | ||
|
|
c06242efd3 | ||
|
|
f69f387e1d | ||
|
|
f5c10aec7f | ||
|
|
34bd9d9092 | ||
|
|
c30151d03a | ||
|
|
f784c477f0 | ||
|
|
c899c98f31 | ||
|
|
53f0f4d66d | ||
|
|
c826fadb9f | ||
|
|
097094cd7a | ||
|
|
c3fa1bf5af | ||
|
|
8798cfbced | ||
|
|
81d71ebb08 | ||
|
|
074e74c6c5 | ||
|
|
65b8272724 | ||
|
|
ca3f3379cb | ||
|
|
faad10baee | ||
|
|
d3eac92eaa | ||
|
|
fb5b9bc0b1 | ||
|
|
34505071f4 | ||
|
|
0a2d0b9d0b | ||
|
|
4495f5987a | ||
|
|
aad29d5d92 | ||
|
|
ad8452035d | ||
|
|
b2ffed891b | ||
|
|
27ab98dca4 | ||
|
|
a8ca11cb47 | ||
|
|
d39d1401c4 | ||
|
|
159c75096d | ||
|
|
a70347b15f | ||
|
|
1a2c8aa31f | ||
|
|
15e1e68cfd | ||
|
|
167da44211 | ||
|
|
1372838092 | ||
|
|
7257e1ec36 | ||
|
|
af5960bc28 | ||
|
|
206cfdf9c6 | ||
|
|
65d7229079 | ||
|
|
c18aa40ba6 | ||
|
|
249f33da16 | ||
|
|
b9a7c8be90 | ||
|
|
e4ac72a316 | ||
|
|
dff9cbfe1b | ||
|
|
489bc860d2 | ||
|
|
29a8fff576 | ||
|
|
e66a68f0fa | ||
|
|
4058eacbd4 | ||
|
|
8cbd1544b9 | ||
|
|
f302744fec | ||
|
|
a3ba4e2c10 | ||
|
|
a483f9cafd | ||
|
|
b43c82f3db | ||
|
|
84f0099c1d | ||
|
|
c6a883c8a9 |
74
History.md
74
History.md
@@ -1,4 +1,78 @@
|
||||
|
||||
0.8.0 / 2011-08-28
|
||||
==================
|
||||
|
||||
* Updated to work with two-level websocket versioning. [einaros]
|
||||
* Added hybi07 support. [einaros]
|
||||
* Added hybi10 support. [einaros]
|
||||
* Added http referrer verification to manager.js verifyOrigin. [einaors]
|
||||
|
||||
0.7.11 / 2011-08-27
|
||||
===================
|
||||
|
||||
* Updated socket.io-client.
|
||||
|
||||
0.7.10 / 2011-08-27
|
||||
===================
|
||||
|
||||
* Updated socket.io-client.
|
||||
|
||||
0.7.9 / 2011-08-12
|
||||
==================
|
||||
|
||||
* Updated socket.io-client.
|
||||
* Make sure we only do garbage collection when the server we receive is actually run.
|
||||
|
||||
0.7.8 / 2011-08-08
|
||||
==================
|
||||
|
||||
* Changed; make sure sio#listen passes options to both HTTP server and socket.io manager.
|
||||
* Added docs for sio#listen.
|
||||
* Added options parameter support for Manager constructor.
|
||||
* Added memory leaks tests and test-leaks Makefile task.
|
||||
* Removed auto npm-linking from make test.
|
||||
* Make sure that you can disable heartbeats. [3rd-Eden]
|
||||
* Fixed rooms memory leak [3rd-Eden]
|
||||
* Send response once we got all POST data, not immediately [Pita]
|
||||
* Fixed onLeave behavior with missing clientsk [3rd-Eden]
|
||||
* Prevent duplicate references in rooms.
|
||||
* Added alias for `to` to `in` and `in` to `to`.
|
||||
* Fixed roomClients definition.
|
||||
* Removed dependency on redis for installation without npm [3rd-Eden]
|
||||
* Expose path and querystring in handshakeData [3rd-Eden]
|
||||
|
||||
0.7.7 / 2011-07-12
|
||||
==================
|
||||
|
||||
* Fixed double dispatch handling with emit to closed clients.
|
||||
* Added test for emitting to closed clients to prevent regression.
|
||||
* Fixed race condition in redis test.
|
||||
* Changed Transport#end instrumentation.
|
||||
* Leveraged $emit instead of emit internally.
|
||||
* Made tests faster.
|
||||
* Fixed double disconnect events.
|
||||
* Fixed disconnect logic
|
||||
* Simplified remote events handling in Socket.
|
||||
* Increased testcase timeout.
|
||||
* Fixed unknown room emitting (GH-291). [3rd-Eden]
|
||||
* Fixed `address` in handshakeData. [3rd-Eden]
|
||||
* Removed transports definition in chat example.
|
||||
* Fixed room cleanup
|
||||
* Fixed; make sure the client is cleaned up after booting.
|
||||
* Make sure to mark the client as non-open if the connection is closed.
|
||||
* Removed unneeded `buffer` declarations.
|
||||
* Fixed; make sure to clear socket handlers and subscriptions upon transport close.
|
||||
|
||||
0.7.6 / 2011-06-30
|
||||
==================
|
||||
|
||||
* Fixed general dispatching when a client has closed.
|
||||
|
||||
0.7.5 / 2011-06-30
|
||||
==================
|
||||
|
||||
* Fixed dispatching to clients that are disconnected.
|
||||
|
||||
0.7.4 / 2011-06-30
|
||||
==================
|
||||
|
||||
|
||||
5
Makefile
5
Makefile
@@ -2,8 +2,8 @@
|
||||
ALL_TESTS = $(shell find test/ -name '*.test.js')
|
||||
|
||||
run-tests:
|
||||
@npm link > /dev/null --local
|
||||
@./node_modules/.bin/expresso \
|
||||
-t 3000 \
|
||||
-I support \
|
||||
-I lib \
|
||||
--serial \
|
||||
@@ -16,4 +16,7 @@ test:
|
||||
test-cov:
|
||||
@TESTFLAGS=--cov $(MAKE) test
|
||||
|
||||
test-leaks:
|
||||
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
|
||||
|
||||
.PHONY: test
|
||||
|
||||
@@ -20,7 +20,7 @@ Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express`
|
||||
web framework:
|
||||
|
||||
```js
|
||||
var app = express.createServer();
|
||||
var app = express.createServer()
|
||||
, io = io.listen(app);
|
||||
|
||||
app.listen(80);
|
||||
|
||||
@@ -61,10 +61,6 @@ app.listen(3000, function () {
|
||||
var io = sio.listen(app)
|
||||
, nicknames = {};
|
||||
|
||||
io.set('transports', [
|
||||
, 'xhr-polling'
|
||||
]);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('user message', function (msg) {
|
||||
socket.broadcast.emit('user message', socket.nickname, msg);
|
||||
|
||||
139
lib/manager.js
139
lib/manager.js
@@ -1,4 +1,3 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
@@ -9,9 +8,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, https = require('https')
|
||||
, fs = require('fs')
|
||||
var fs = require('fs')
|
||||
, url = require('url')
|
||||
, util = require('./util')
|
||||
, store = require('./store')
|
||||
@@ -55,7 +52,7 @@ var parent = module.parent.exports
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Manager (server) {
|
||||
function Manager (server, options) {
|
||||
this.server = server;
|
||||
this.namespaces = {};
|
||||
this.sockets = this.of('');
|
||||
@@ -83,6 +80,10 @@ function Manager (server) {
|
||||
, 'client store expiration': 15
|
||||
};
|
||||
|
||||
for (var i in options) {
|
||||
this.settings[i] = options[i];
|
||||
}
|
||||
|
||||
this.initStore();
|
||||
|
||||
// reset listeners
|
||||
@@ -99,6 +100,14 @@ function Manager (server) {
|
||||
self.handleUpgrade(req, socket, head);
|
||||
});
|
||||
|
||||
server.on('close', function () {
|
||||
clearInterval(self.gc);
|
||||
});
|
||||
|
||||
server.once('listening', function () {
|
||||
self.gc = setInterval(self.garbageCollection.bind(self), 10000);
|
||||
});
|
||||
|
||||
for (var i in transports) {
|
||||
if (transports[i].init) {
|
||||
transports[i].init(this);
|
||||
@@ -324,16 +333,6 @@ Manager.prototype.onOpen = function (id) {
|
||||
*/
|
||||
|
||||
Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
|
||||
// go through the users who have pending buffers
|
||||
for (var i = 0, l = this.closedA.length; i < l; i++) {
|
||||
if (this.roomClients[this.closedA[i]][room]) {
|
||||
if (!~exceptions.indexOf(this.closedA[i])) {
|
||||
this.closed[this.closedA[i]].push(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go through room clients
|
||||
if (this.rooms[room]) {
|
||||
for (var i = 0, l = this.rooms[room].length; i < l; i++) {
|
||||
var id = this.rooms[room][i];
|
||||
@@ -357,15 +356,17 @@ Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
|
||||
|
||||
Manager.prototype.onJoin = function (id, name) {
|
||||
if (!this.roomClients[id]) {
|
||||
this.roomClients[id] = [];
|
||||
this.roomClients[id] = {};
|
||||
}
|
||||
|
||||
if (!this.rooms[name]) {
|
||||
this.rooms[name] = [];
|
||||
}
|
||||
|
||||
this.rooms[name].push(id);
|
||||
this.roomClients[id][name] = true;
|
||||
if (!~this.rooms[name].indexOf(id)) {
|
||||
this.rooms[name].push(id);
|
||||
this.roomClients[id][name] = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -376,7 +377,15 @@ Manager.prototype.onJoin = function (id, name) {
|
||||
|
||||
Manager.prototype.onLeave = function (id, room) {
|
||||
if (this.rooms[room]) {
|
||||
this.rooms[room].splice(this.rooms[room].indexOf(id), 1);
|
||||
var index = this.rooms[room].indexOf(id);
|
||||
|
||||
if (index >= 0) {
|
||||
this.rooms[room].splice(index, 1);
|
||||
}
|
||||
|
||||
if (!this.rooms[room].length) {
|
||||
delete this.rooms[room];
|
||||
}
|
||||
delete this.roomClients[id][room];
|
||||
}
|
||||
};
|
||||
@@ -388,6 +397,10 @@ Manager.prototype.onLeave = function (id, room) {
|
||||
*/
|
||||
|
||||
Manager.prototype.onClose = function (id) {
|
||||
if (this.open[id]) {
|
||||
delete this.open[id];
|
||||
}
|
||||
|
||||
this.closed[id] = [];
|
||||
this.closedA.push(id);
|
||||
|
||||
@@ -407,7 +420,9 @@ Manager.prototype.onClose = function (id) {
|
||||
*/
|
||||
|
||||
Manager.prototype.onClientDispatch = function (id, packet) {
|
||||
this.closed[id].push(packet);
|
||||
if (this.closed[id]) {
|
||||
this.closed[id].push(packet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -434,6 +449,8 @@ Manager.prototype.onClientDisconnect = function (id, reason) {
|
||||
this.namespaces[name].handleDisconnect(id, reason);
|
||||
}
|
||||
}
|
||||
|
||||
this.onDisconnect(id);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -465,8 +482,9 @@ Manager.prototype.onDisconnect = function (id, local) {
|
||||
|
||||
if (this.roomClients[id]) {
|
||||
for (var room in this.roomClients[id]) {
|
||||
this.rooms[room].splice(this.rooms.indexOf(id), 1);
|
||||
this.onLeave(id, room);
|
||||
}
|
||||
delete this.roomClients[id]
|
||||
}
|
||||
|
||||
this.store.destroyClient(id, this.get('client store expiration'));
|
||||
@@ -583,9 +601,10 @@ Manager.prototype.handleClient = function (data, req) {
|
||||
return;
|
||||
}
|
||||
|
||||
var transport = new transports[data.transport](this, data, req);
|
||||
var transport = new transports[data.transport](this, data, req)
|
||||
, handshaken = this.handshaken[data.id];
|
||||
|
||||
if (this.handshaken[data.id]) {
|
||||
if (handshaken) {
|
||||
if (transport.open) {
|
||||
if (this.closed[data.id] && this.closed[data.id].length) {
|
||||
transport.payload(this.closed[data.id]);
|
||||
@@ -601,6 +620,11 @@ Manager.prototype.handleClient = function (data, req) {
|
||||
this.onConnect(data.id);
|
||||
this.store.publish('connect', data.id);
|
||||
|
||||
// flag as used
|
||||
delete handshaken.issued;
|
||||
this.onHandshake(data.id, handshaken);
|
||||
this.store.publish('handshake', data.id, handshaken);
|
||||
|
||||
// initialize the socket for all namespaces
|
||||
for (var i in this.namespaces) {
|
||||
var socket = this.namespaces[i].socket(data.id, true);
|
||||
@@ -775,7 +799,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
|
||||
var id = self.generateId()
|
||||
, hs = [
|
||||
id
|
||||
, self.get('heartbeat timeout') || ''
|
||||
, self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : ''
|
||||
, self.get('close timeout') || ''
|
||||
, self.transports(data).join(',')
|
||||
].join(':');
|
||||
@@ -807,16 +831,31 @@ Manager.prototype.handleHandshake = function (data, req, res) {
|
||||
*/
|
||||
|
||||
Manager.prototype.handshakeData = function (data) {
|
||||
var connectionAddress = null;
|
||||
if (data.request.connection.address) {
|
||||
connectionAddress = data.request.connection.address();
|
||||
var connection = data.request.connection
|
||||
, connectionAddress
|
||||
, date = new Date;
|
||||
|
||||
if (connection.remoteAddress) {
|
||||
connectionAddress = {
|
||||
address: connection.remoteAddress
|
||||
, port: connection.remotePort
|
||||
};
|
||||
} else if (connection.socket && connection.socket.remoteAddress) {
|
||||
connectionAddress = {
|
||||
address: connection.socket.remoteAddress
|
||||
, port: connection.socket.remotePort
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
headers: data.headers
|
||||
, address: connectionAddress
|
||||
, time: (new Date).toString()
|
||||
, time: date.toString()
|
||||
, query: data.query
|
||||
, url: data.request.url
|
||||
, xdomain: !!data.request.headers.origin
|
||||
, secure: data.request.connection.secure
|
||||
, issued: +date
|
||||
};
|
||||
};
|
||||
|
||||
@@ -827,7 +866,7 @@ Manager.prototype.handshakeData = function (data) {
|
||||
*/
|
||||
|
||||
Manager.prototype.verifyOrigin = function (request) {
|
||||
var origin = request.headers.origin
|
||||
var origin = request.headers.origin || request.headers.referer
|
||||
, origins = this.get('origins');
|
||||
|
||||
if (origin === 'null') origin = '*';
|
||||
@@ -839,14 +878,19 @@ Manager.prototype.verifyOrigin = function (request) {
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
|
||||
return
|
||||
~origins.indexOf(parts.host + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.host + ':*') ||
|
||||
var ok =
|
||||
~origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.hostname + ':*') ||
|
||||
~origins.indexOf('*:' + parts.port);
|
||||
} catch (ex) {}
|
||||
if (!ok) this.log.warn('illegal origin: ' + origin);
|
||||
return ok;
|
||||
} catch (ex) {
|
||||
this.log.warn('error parsing origin');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.log.warn('origin missing from handshake, yet required by config');
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -946,6 +990,8 @@ Manager.prototype.checkRequest = function (req) {
|
||||
|
||||
/**
|
||||
* Declares a socket namespace
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.of = function (nsp) {
|
||||
@@ -955,3 +1001,26 @@ Manager.prototype.of = function (nsp) {
|
||||
|
||||
return this.namespaces[nsp] = new SocketNamespace(this, nsp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform garbage collection on long living objects and properties that cannot
|
||||
* be removed automatically.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.garbageCollection = function () {
|
||||
// clean up unused handshakes
|
||||
var ids = Object.keys(this.handshaken)
|
||||
, i = ids.length
|
||||
, now = Date.now()
|
||||
, handshake;
|
||||
|
||||
while (i--) {
|
||||
handshake = this.handshaken[ids[i]];
|
||||
|
||||
if ('issued' in handshake && (now - handshake.issued) >= 3E4) {
|
||||
this.onDisconnect(ids[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ SocketNamespace.prototype.$emit = EventEmitter.prototype.emit;
|
||||
|
||||
SocketNamespace.prototype.clients = function (room) {
|
||||
var room = this.name + (room !== undefined ?
|
||||
(this.name !== '' ? '/' : '') + room : '');
|
||||
'/' + room : '');
|
||||
|
||||
if (!this.manager.rooms[room]) {
|
||||
return [];
|
||||
@@ -108,8 +108,8 @@ SocketNamespace.prototype.__defineGetter__('volatile', function () {
|
||||
* @api public
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.in = function (room) {
|
||||
this.flags.endpoint = (this.name === '' ? '' : (this.name + '/')) + room;
|
||||
SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) {
|
||||
this.flags.endpoint = this.name + (room ? '/' + room : '');
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -181,7 +181,7 @@ SocketNamespace.prototype.send = function (data) {
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.emit = function (name) {
|
||||
if (name == 'connection' || name == 'newListener') {
|
||||
if (name == 'newListener') {
|
||||
return this.$emit.apply(this, arguments);
|
||||
}
|
||||
|
||||
@@ -227,6 +227,7 @@ SocketNamespace.prototype.authorization = function (fn) {
|
||||
SocketNamespace.prototype.handleDisconnect = function (sid, reason) {
|
||||
if (this.sockets[sid] && this.sockets[sid].readable) {
|
||||
this.sockets[sid].onDisconnect(reason);
|
||||
delete this.sockets[sid];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -287,7 +288,7 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
|
||||
socket.packet({ type: 'connect' });
|
||||
|
||||
// emit connection event
|
||||
self.emit('connection', socket);
|
||||
self.$emit('connection', socket);
|
||||
};
|
||||
|
||||
switch (packet.type) {
|
||||
@@ -333,7 +334,7 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
|
||||
this.manager.onLeave(sessid, this.name);
|
||||
this.store.publish('leave', sessid, this.name);
|
||||
|
||||
socket.emit('disconnect', packet.reason || 'packet');
|
||||
socket.$emit('disconnect', packet.reason || 'packet');
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
@@ -343,6 +344,6 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
|
||||
if (dataAck)
|
||||
params.push(ack);
|
||||
|
||||
socket.emit.apply(socket, params);
|
||||
socket.$emit.apply(socket, params);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ var client = require('socket.io-client');
|
||||
* Version.
|
||||
*/
|
||||
|
||||
exports.version = '0.7.4';
|
||||
exports.version = '0.8.0';
|
||||
|
||||
/**
|
||||
* Supported protocol version.
|
||||
@@ -32,6 +32,9 @@ exports.clientVersion = client.version;
|
||||
/**
|
||||
* Attaches a manager
|
||||
*
|
||||
* @param {HTTPServer/Number} a HTTP/S server or a port number to listen on.
|
||||
* @param {Object} opts to be passed to Manager and/or http server
|
||||
* @param {Function} callback if a port is supplied
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -65,7 +68,7 @@ exports.listen = function (server, options, fn) {
|
||||
}
|
||||
|
||||
// otherwise assume a http/s server
|
||||
return new exports.Manager(server);
|
||||
return new exports.Manager(server, options);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,22 +19,6 @@ var parser = require('./parser')
|
||||
|
||||
exports = module.exports = Socket;
|
||||
|
||||
/**
|
||||
* Reserved event names.
|
||||
*/
|
||||
|
||||
var events = {
|
||||
message: 1
|
||||
, connect: 1
|
||||
, disconnect: 1
|
||||
, open: 1
|
||||
, close: 1
|
||||
, error: 1
|
||||
, retry: 1
|
||||
, reconnect: 1
|
||||
, newListener: 1
|
||||
};
|
||||
|
||||
/**
|
||||
* Socket constructor.
|
||||
*
|
||||
@@ -122,7 +106,7 @@ Socket.prototype.__defineGetter__('broadcast', function () {
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.to = function (room) {
|
||||
Socket.prototype.to = Socket.prototype.in = function (room) {
|
||||
this.flags.room = room;
|
||||
return this;
|
||||
};
|
||||
@@ -149,7 +133,7 @@ Socket.prototype.setFlags = function () {
|
||||
|
||||
Socket.prototype.onDisconnect = function (reason) {
|
||||
if (!this.disconnected) {
|
||||
this.emit('disconnect', reason);
|
||||
this.$emit('disconnect', reason);
|
||||
this.disconnected = true;
|
||||
}
|
||||
};
|
||||
@@ -162,7 +146,7 @@ Socket.prototype.onDisconnect = function (reason) {
|
||||
|
||||
Socket.prototype.join = function (name, fn) {
|
||||
var nsp = this.namespace.name
|
||||
, name = (nsp === '' ? '' : (nsp + '/')) + name;
|
||||
, name = (nsp + '/') + name;
|
||||
|
||||
this.manager.onJoin(this.id, name);
|
||||
this.manager.store.publish('join', this.id, name);
|
||||
@@ -183,7 +167,7 @@ Socket.prototype.join = function (name, fn) {
|
||||
|
||||
Socket.prototype.leave = function (name, fn) {
|
||||
var nsp = this.namespace.name
|
||||
, name = (nsp === '' ? '' : (nsp + '/')) + name;
|
||||
, name = (nsp + '/') + name;
|
||||
|
||||
this.manager.onLeave(this.id, name);
|
||||
this.manager.store.publish('leave', this.id, name);
|
||||
@@ -293,13 +277,8 @@ Socket.prototype.disconnect = function () {
|
||||
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
|
||||
this.manager.transports[this.id].onForcedDisconnect();
|
||||
} else {
|
||||
if (this.manager.open[this.id]) {
|
||||
// if the connection is open in a different node
|
||||
this.manager.store.publish('disconnect-force:' + this.id);
|
||||
} else {
|
||||
this.manager.onClientDisconnect(this.id);
|
||||
this.manager.store.publish('disconnect:' + this.id);
|
||||
}
|
||||
this.manager.onClientDisconnect(this.id);
|
||||
this.manager.store.publish('disconnect:' + this.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +321,7 @@ Socket.prototype.$emit = EventEmitter.prototype.emit;
|
||||
*/
|
||||
|
||||
Socket.prototype.emit = function (ev) {
|
||||
if (events[ev]) {
|
||||
if (ev == 'newListener') {
|
||||
return this.$emit.apply(this, arguments);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
var crypto = require('crypto')
|
||||
, Store = require('../store')
|
||||
, assert = require('assert')
|
||||
, redis = require('redis');
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Exports the constructor.
|
||||
@@ -25,6 +24,7 @@ Redis.Client = Client;
|
||||
* Redis store.
|
||||
* Options:
|
||||
* - nodeId (fn) gets an id that uniquely identifies this node
|
||||
* - redis (fn) redis constructor, defaults to redis
|
||||
* - redisPub (object) options to pass to the pub redis client
|
||||
* - redisSub (object) options to pass to the sub redis client
|
||||
* - redisClient (object) options to pass to the general redis client
|
||||
@@ -60,6 +60,8 @@ function Redis (opts) {
|
||||
}
|
||||
}
|
||||
|
||||
var redis = opts.redis || require('redis');
|
||||
|
||||
// initialize a pubsub client and a regular client
|
||||
this.pub = redis.createClient(opts.redisPub);
|
||||
this.sub = redis.createClient(opts.redisSub);
|
||||
|
||||
@@ -28,7 +28,6 @@ function Transport (mng, data, req) {
|
||||
this.id = data.id;
|
||||
this.disconnected = false;
|
||||
this.drained = true;
|
||||
this.buffer = [];
|
||||
this.handleRequest(req);
|
||||
};
|
||||
|
||||
@@ -260,7 +259,7 @@ Transport.prototype.clearCloseTimeout = function () {
|
||||
*/
|
||||
|
||||
Transport.prototype.setHeartbeatTimeout = function () {
|
||||
if (!this.heartbeatTimeout) {
|
||||
if (!this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
|
||||
var self = this;
|
||||
|
||||
this.heartbeatTimeout = setTimeout(function () {
|
||||
@@ -280,7 +279,7 @@ Transport.prototype.setHeartbeatTimeout = function () {
|
||||
*/
|
||||
|
||||
Transport.prototype.clearHeartbeatTimeout = function () {
|
||||
if (this.heartbeatTimeout) {
|
||||
if (this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
|
||||
clearTimeout(this.heartbeatTimeout);
|
||||
this.heartbeatTimeout = null;
|
||||
this.log.debug('cleared heartbeat timeout for client', this.id);
|
||||
@@ -295,7 +294,7 @@ Transport.prototype.clearHeartbeatTimeout = function () {
|
||||
*/
|
||||
|
||||
Transport.prototype.setHeartbeatInterval = function () {
|
||||
if (!this.heartbeatInterval) {
|
||||
if (!this.heartbeatInterval && this.manager.enabled('heartbeats')) {
|
||||
var self = this;
|
||||
|
||||
this.heartbeatInterval = setTimeout(function () {
|
||||
@@ -357,11 +356,13 @@ Transport.prototype.onMessage = function (packet) {
|
||||
if ('disconnect' == packet.type && packet.endpoint == '') {
|
||||
this.log.debug('got disconnection packet');
|
||||
|
||||
if (current && current.open) {
|
||||
if (current) {
|
||||
current.onForcedDisconnect();
|
||||
} else {
|
||||
this.store.publish('disconnect-force:' + this.id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.id && packet.ack != 'data') {
|
||||
@@ -397,7 +398,7 @@ Transport.prototype.onMessage = function (packet) {
|
||||
*/
|
||||
|
||||
Transport.prototype.clearHeartbeatInterval = function () {
|
||||
if (this.heartbeatInterval) {
|
||||
if (this.heartbeatInterval && this.manager.enabled('heartbeats')) {
|
||||
clearTimeout(this.heartbeatInterval);
|
||||
this.heartbeatInterval = null;
|
||||
this.log.debug('cleared heartbeat interval for client', this.id);
|
||||
@@ -439,6 +440,7 @@ Transport.prototype.close = function () {
|
||||
Transport.prototype.onClose = function () {
|
||||
if (this.open) {
|
||||
this.setCloseTimeout();
|
||||
this.clearHandlers();
|
||||
this.open = false;
|
||||
this.manager.onClose(this.id);
|
||||
this.store.publish('close', this.id);
|
||||
@@ -453,7 +455,7 @@ Transport.prototype.onClose = function () {
|
||||
|
||||
Transport.prototype.end = function (reason) {
|
||||
if (!this.disconnected) {
|
||||
this.log.info('ending socket');
|
||||
this.log.info('transport end');
|
||||
|
||||
var local = this.manager.transports[this.id];
|
||||
|
||||
@@ -480,7 +482,6 @@ Transport.prototype.discard = function () {
|
||||
this.discarded = true;
|
||||
this.clearTimeouts();
|
||||
this.clearHandlers();
|
||||
this.buffer = [];
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ exports = module.exports = FlashSocket;
|
||||
*/
|
||||
|
||||
function FlashSocket (mng, data, req) {
|
||||
WebSocket.call(this, mng, data, req);
|
||||
return WebSocket.call(this, mng, data, req);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,9 @@ HTTPTransport.prototype.handleRequest = function (req) {
|
||||
});
|
||||
|
||||
req.on('end', function () {
|
||||
res.writeHead(200, headers);
|
||||
res.end('1');
|
||||
|
||||
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
|
||||
});
|
||||
|
||||
@@ -65,9 +68,6 @@ HTTPTransport.prototype.handleRequest = function (req) {
|
||||
headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead(200, headers);
|
||||
res.end('1');
|
||||
} else {
|
||||
this.response = req.res;
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ JSONPPolling.prototype.doWrite = function (data) {
|
||||
'Content-Type': 'text/javascript; charset=UTF-8'
|
||||
, 'Content-Length': Buffer.byteLength(data)
|
||||
, 'Connection': 'Keep-Alive'
|
||||
, 'X-XSS-Protection': '0'
|
||||
});
|
||||
|
||||
this.response.write(data);
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../transport')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, crypto = require('crypto')
|
||||
, parser = require('../parser');
|
||||
var protocolVersions = require('./websocket/');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
@@ -28,323 +25,9 @@ exports = module.exports = WebSocket;
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.log.debug(self.name + ' received data packet', packet);
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function () {
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
this.socket.setNoDelay(true);
|
||||
|
||||
this.buffer = true;
|
||||
this.buffered = [];
|
||||
|
||||
if (this.req.headers.upgrade !== 'WebSocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
var version = req.headers['sec-websocket-version'];
|
||||
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
|
||||
return new protocolVersions[version](mng, data, req);
|
||||
}
|
||||
|
||||
var origin = this.req.headers.origin
|
||||
, location = (this.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url
|
||||
, waitingForNonce = false;
|
||||
|
||||
if (this.req.headers['sec-websocket-key1']) {
|
||||
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
|
||||
if (! (this.req.head && this.req.head.length >= 8)) {
|
||||
waitingForNonce = true;
|
||||
}
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 WebSocket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Origin: ' + origin
|
||||
, 'Sec-WebSocket-Location: ' + location
|
||||
];
|
||||
|
||||
if (this.req.headers['sec-websocket-protocol']){
|
||||
headers.push('Sec-WebSocket-Protocol: '
|
||||
+ this.req.headers['sec-websocket-protocol']);
|
||||
}
|
||||
} else {
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Web Socket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'WebSocket-Origin: ' + origin
|
||||
, 'WebSocket-Location: ' + location
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
this.socket.setEncoding('utf8');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitingForNonce) {
|
||||
this.socket.setEncoding('binary');
|
||||
} else if (this.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
var headBuffer = '';
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
if (waitingForNonce) {
|
||||
headBuffer += data;
|
||||
|
||||
if (headBuffer.length < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the connection to utf8 encoding after receiving the nonce
|
||||
self.socket.setEncoding('utf8');
|
||||
waitingForNonce = false;
|
||||
|
||||
// Stuff the nonce into the location where it's expected to be
|
||||
self.req.head = headBuffer.substr(0, 8);
|
||||
headBuffer = '';
|
||||
|
||||
if (self.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
this.drained = false;
|
||||
|
||||
if (this.buffer) {
|
||||
this.buffered.push(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
var length = Buffer.byteLength(data)
|
||||
, buffer = new Buffer(2 + length);
|
||||
|
||||
buffer.write('\u0000', 'binary');
|
||||
buffer.write(data, 1, 'utf8');
|
||||
buffer.write('\uffff', 1 + length, 'binary');
|
||||
|
||||
try {
|
||||
if (this.socket.write(buffer)) {
|
||||
this.drained = true;
|
||||
}
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Flushes the internal buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.flush = function () {
|
||||
this.buffer = false;
|
||||
|
||||
for (var i = 0, l = this.buffered.length; i < l; i++) {
|
||||
this.write(this.buffered.splice(0, 1)[0]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the handshake.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.proveReception = function (headers) {
|
||||
var self = this
|
||||
, k1 = this.req.headers['sec-websocket-key1']
|
||||
, k2 = this.req.headers['sec-websocket-key2'];
|
||||
|
||||
if (k1 && k2){
|
||||
var md5 = crypto.createHash('md5');
|
||||
|
||||
[k1, k2].forEach(function (k) {
|
||||
var n = parseInt(k.replace(/[^\d]/g, ''))
|
||||
, spaces = k.replace(/[^ ]/g, '').length;
|
||||
|
||||
if (spaces === 0 || n % spaces !== 0){
|
||||
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
|
||||
self.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
n /= spaces;
|
||||
|
||||
md5.update(String.fromCharCode(
|
||||
n >> 24 & 0xFF,
|
||||
n >> 16 & 0xFF,
|
||||
n >> 8 & 0xFF,
|
||||
n & 0xFF));
|
||||
});
|
||||
|
||||
md5.update(this.req.head.toString('binary'));
|
||||
|
||||
try {
|
||||
this.socket.write(md5.digest('binary'), 'binary');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a payload.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.payload = function (msgs) {
|
||||
for (var i = 0, l = msgs.length; i < l; i++) {
|
||||
this.write(msgs[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Adds data to the buffer.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function (data) {
|
||||
this.buffer += data;
|
||||
this.parse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the buffer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.parse = function () {
|
||||
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
|
||||
chr = this.buffer[i];
|
||||
|
||||
if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
|
||||
this.emit('close');
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (i === 0){
|
||||
if (chr != '\u0000')
|
||||
this.error('Bad framing. Expected null byte as first frame');
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chr == '\ufffd'){
|
||||
this.emit('data', this.buffer.substr(1, i - 1));
|
||||
this.buffer = this.buffer.substr(i + 1);
|
||||
this.i = 0;
|
||||
return this.parse();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
return new protocolVersions['default'](mng, data, req);
|
||||
};
|
||||
|
||||
461
lib/transports/websocket/7.js
Normal file
461
lib/transports/websocket/7.js
Normal file
@@ -0,0 +1,461 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../../transport')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, crypto = require('crypto')
|
||||
, parser = require('../../parser')
|
||||
, util = require('../../util');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = WebSocket;
|
||||
exports.Parser = Parser;
|
||||
|
||||
/**
|
||||
* HTTP interface constructor. Interface compatible with all transports that
|
||||
* depend on request-response cycles.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('ping', function () {
|
||||
// version 8 ping => pong
|
||||
this.socket.write('\u008a\u0000');
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function () {
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
if (this.req.headers.upgrade !== 'websocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers.origin
|
||||
, location = (this.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url;
|
||||
|
||||
if (!this.req.headers['sec-websocket-key']) {
|
||||
this.log.warn(this.name + ' connection invalid: received no key');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// calc key
|
||||
var key = this.req.headers['sec-websocket-key'];
|
||||
var shasum = crypto.createHash('sha1');
|
||||
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
key = shasum.digest('base64');
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Switching Protocols'
|
||||
, 'Upgrade: websocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Accept: ' + key
|
||||
];
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
var buf = this.frame(0x81, data);
|
||||
this.socket.write(buf, 'binary');
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Frame server-to-client output as a text packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.frame = function (opcode, data) {
|
||||
var startOffset = 2, secondByte = data.length, buf;
|
||||
if (data.length > 125) {
|
||||
// opcode = 0x81;
|
||||
startOffset += 2;
|
||||
secondByte = 126;
|
||||
}
|
||||
if (data.length > 65536) {
|
||||
startOffset += 6;
|
||||
secondByte = 127;
|
||||
}
|
||||
buf = new Buffer(data.length + startOffset, 'utf8');
|
||||
buf[0] = opcode;
|
||||
buf[1] = secondByte;
|
||||
switch (secondByte) {
|
||||
case 126:
|
||||
buf[2] = data.length >>> 8;
|
||||
buf[3] = data.length % 256;
|
||||
break;
|
||||
case 127:
|
||||
for (var i = 6; i > 2; i--) {
|
||||
buf[i] = secondByte % 256;
|
||||
secondByte >>>= 8;
|
||||
}
|
||||
}
|
||||
buf.write(data, startOffset, 'utf8');
|
||||
return buf;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.overflow = null;
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.currentMessage = '';
|
||||
|
||||
var self = this;
|
||||
this.opcodeHandlers = {
|
||||
// text
|
||||
'1': function(data) {
|
||||
var finish = function(mask, data) {
|
||||
self.currentMessage += self.unmask(mask, data);
|
||||
if (self.state.lastFragment) {
|
||||
self.emit('data', self.currentMessage);
|
||||
self.currentMessage = '';
|
||||
}
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
if (util.unpack(data.slice(0, 4)) != 0) {
|
||||
self.error('packets with length spanning more than 32 bit is currently not supported');
|
||||
return;
|
||||
}
|
||||
var lengthBytes = data.slice(4); // note: cap to 32 bit length
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
},
|
||||
// close
|
||||
'8': function(data) {
|
||||
self.emit('close');
|
||||
self.reset();
|
||||
},
|
||||
// ping
|
||||
'9': function(data) {
|
||||
if (self.state.lastFragment == false) {
|
||||
self.error('fragmented ping is not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
var finish = function(mask, data) {
|
||||
self.emit('ping', self.unmask(mask, data));
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength == 0) {
|
||||
finish(null, null);
|
||||
}
|
||||
else if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Add new data to the parser.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function(data) {
|
||||
if (this.expectBuffer == null) {
|
||||
this.addToOverflow(data);
|
||||
return;
|
||||
}
|
||||
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
|
||||
data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
|
||||
this.expectOffset += toRead;
|
||||
if (toRead < data.length) {
|
||||
// at this point the overflow buffer shouldn't at all exist
|
||||
this.overflow = new Buffer(data.length - toRead);
|
||||
data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
|
||||
}
|
||||
if (this.expectOffset == this.expectBuffer.length) {
|
||||
var bufferForHandler = this.expectBuffer;
|
||||
this.expectBuffer = null;
|
||||
this.expectOffset = 0;
|
||||
this.expectHandler.call(this, bufferForHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a piece of data to the overflow.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.addToOverflow = function(data) {
|
||||
if (this.overflow == null) this.overflow = data;
|
||||
else {
|
||||
var prevOverflow = this.overflow;
|
||||
this.overflow = new Buffer(this.overflow.length + data.length);
|
||||
prevOverflow.copy(this.overflow, 0);
|
||||
data.copy(this.overflow, prevOverflow.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a certain amount of bytes to be available, then fires a callback.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.expect = function(what, length, handler) {
|
||||
this.expectBuffer = new Buffer(length);
|
||||
this.expectOffset = 0;
|
||||
this.expectHandler = handler;
|
||||
if (this.overflow != null) {
|
||||
var toOverflow = this.overflow;
|
||||
this.overflow = null;
|
||||
this.add(toOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing a new packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.processPacket = function (data) {
|
||||
if ((data[0] & 0x70) != 0) this.error('reserved fields not empty');
|
||||
this.state.lastFragment = (data[0] & 0x80) == 0x80;
|
||||
this.state.masked = (data[1] & 0x80) == 0x80;
|
||||
var opcode = data[0] & 0xf;
|
||||
if (opcode == 0) {
|
||||
// continuation frame
|
||||
if (this.state.opcode != 1 || this.state.opcode != 2) {
|
||||
this.error('continuation frame cannot follow current opcode')
|
||||
return;
|
||||
}
|
||||
}
|
||||
else this.state.opcode = opcode;
|
||||
this.state.opcode = data[0] & 0xf;
|
||||
var handler = this.opcodeHandlers[this.state.opcode];
|
||||
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
|
||||
else handler(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endprocessing a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.endPacket = function() {
|
||||
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.state.lastFragment = false;
|
||||
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
|
||||
this.state.masked = false;
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the parser state.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.reset = function() {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.overflow = null;
|
||||
this.currentMessage = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmask received data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.unmask = function (mask, buf) {
|
||||
if (mask != null) {
|
||||
for (var i = 0, ll = buf.length; i < ll; i++) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
return buf != null ? buf.toString('utf8') : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.reset();
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
461
lib/transports/websocket/8.js
Normal file
461
lib/transports/websocket/8.js
Normal file
@@ -0,0 +1,461 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../../transport')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, crypto = require('crypto')
|
||||
, parser = require('../../parser')
|
||||
, util = require('../../util');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = WebSocket;
|
||||
exports.Parser = Parser;
|
||||
|
||||
/**
|
||||
* HTTP interface constructor. Interface compatible with all transports that
|
||||
* depend on request-response cycles.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('ping', function () {
|
||||
// version 8 ping => pong
|
||||
this.socket.write('\u008a\u0000');
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function () {
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
if (this.req.headers.upgrade !== 'websocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers.origin
|
||||
, location = (this.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url;
|
||||
|
||||
if (!this.req.headers['sec-websocket-key']) {
|
||||
this.log.warn(this.name + ' connection invalid: received no key');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// calc key
|
||||
var key = this.req.headers['sec-websocket-key'];
|
||||
var shasum = crypto.createHash('sha1');
|
||||
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
key = shasum.digest('base64');
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Switching Protocols'
|
||||
, 'Upgrade: websocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Accept: ' + key
|
||||
];
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
var buf = this.frame(0x81, data);
|
||||
this.socket.write(buf, 'binary');
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Frame server-to-client output as a text packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.frame = function (opcode, data) {
|
||||
var startOffset = 2, secondByte = data.length, buf;
|
||||
if (data.length > 125) {
|
||||
// opcode = 0x81;
|
||||
startOffset += 2;
|
||||
secondByte = 126;
|
||||
}
|
||||
if (data.length > 65536) {
|
||||
startOffset += 6;
|
||||
secondByte = 127;
|
||||
}
|
||||
buf = new Buffer(data.length + startOffset, 'utf8');
|
||||
buf[0] = opcode;
|
||||
buf[1] = secondByte;
|
||||
switch (secondByte) {
|
||||
case 126:
|
||||
buf[2] = data.length >>> 8;
|
||||
buf[3] = data.length % 256;
|
||||
break;
|
||||
case 127:
|
||||
for (var i = 6; i > 2; i--) {
|
||||
buf[i] = secondByte % 256;
|
||||
secondByte >>>= 8;
|
||||
}
|
||||
}
|
||||
buf.write(data, startOffset, 'utf8');
|
||||
return buf;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.overflow = null;
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.currentMessage = '';
|
||||
|
||||
var self = this;
|
||||
this.opcodeHandlers = {
|
||||
// text
|
||||
'1': function(data) {
|
||||
var finish = function(mask, data) {
|
||||
self.currentMessage += self.unmask(mask, data);
|
||||
if (self.state.lastFragment) {
|
||||
self.emit('data', self.currentMessage);
|
||||
self.currentMessage = '';
|
||||
}
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
if (util.unpack(data.slice(0, 4)) != 0) {
|
||||
self.error('packets with length spanning more than 32 bit is currently not supported');
|
||||
return;
|
||||
}
|
||||
var lengthBytes = data.slice(4); // note: cap to 32 bit length
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
},
|
||||
// close
|
||||
'8': function(data) {
|
||||
self.emit('close');
|
||||
self.reset();
|
||||
},
|
||||
// ping
|
||||
'9': function(data) {
|
||||
if (self.state.lastFragment == false) {
|
||||
self.error('fragmented ping is not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
var finish = function(mask, data) {
|
||||
self.emit('ping', self.unmask(mask, data));
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength == 0) {
|
||||
finish(null, null);
|
||||
}
|
||||
else if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Add new data to the parser.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function(data) {
|
||||
if (this.expectBuffer == null) {
|
||||
this.addToOverflow(data);
|
||||
return;
|
||||
}
|
||||
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
|
||||
data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
|
||||
this.expectOffset += toRead;
|
||||
if (toRead < data.length) {
|
||||
// at this point the overflow buffer shouldn't at all exist
|
||||
this.overflow = new Buffer(data.length - toRead);
|
||||
data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
|
||||
}
|
||||
if (this.expectOffset == this.expectBuffer.length) {
|
||||
var bufferForHandler = this.expectBuffer;
|
||||
this.expectBuffer = null;
|
||||
this.expectOffset = 0;
|
||||
this.expectHandler.call(this, bufferForHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a piece of data to the overflow.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.addToOverflow = function(data) {
|
||||
if (this.overflow == null) this.overflow = data;
|
||||
else {
|
||||
var prevOverflow = this.overflow;
|
||||
this.overflow = new Buffer(this.overflow.length + data.length);
|
||||
prevOverflow.copy(this.overflow, 0);
|
||||
data.copy(this.overflow, prevOverflow.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a certain amount of bytes to be available, then fires a callback.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.expect = function(what, length, handler) {
|
||||
this.expectBuffer = new Buffer(length);
|
||||
this.expectOffset = 0;
|
||||
this.expectHandler = handler;
|
||||
if (this.overflow != null) {
|
||||
var toOverflow = this.overflow;
|
||||
this.overflow = null;
|
||||
this.add(toOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing a new packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.processPacket = function (data) {
|
||||
if ((data[0] & 0x70) != 0) this.error('reserved fields not empty');
|
||||
this.state.lastFragment = (data[0] & 0x80) == 0x80;
|
||||
this.state.masked = (data[1] & 0x80) == 0x80;
|
||||
var opcode = data[0] & 0xf;
|
||||
if (opcode == 0) {
|
||||
// continuation frame
|
||||
if (this.state.opcode != 1 || this.state.opcode != 2) {
|
||||
this.error('continuation frame cannot follow current opcode')
|
||||
return;
|
||||
}
|
||||
}
|
||||
else this.state.opcode = opcode;
|
||||
this.state.opcode = data[0] & 0xf;
|
||||
var handler = this.opcodeHandlers[this.state.opcode];
|
||||
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
|
||||
else handler(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endprocessing a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.endPacket = function() {
|
||||
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.state.lastFragment = false;
|
||||
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
|
||||
this.state.masked = false;
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the parser state.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.reset = function() {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.overflow = null;
|
||||
this.currentMessage = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmask received data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.unmask = function (mask, buf) {
|
||||
if (mask != null) {
|
||||
for (var i = 0, ll = buf.length; i < ll; i++) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
return buf != null ? buf.toString('utf8') : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.reset();
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
350
lib/transports/websocket/default.js
Normal file
350
lib/transports/websocket/default.js
Normal file
@@ -0,0 +1,350 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../../transport')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, crypto = require('crypto')
|
||||
, parser = require('../../parser');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = WebSocket;
|
||||
|
||||
/**
|
||||
* HTTP interface constructor. Interface compatible with all transports that
|
||||
* depend on request-response cycles.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.log.debug(self.name + ' received data packet', packet);
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function () {
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
this.socket.setNoDelay(true);
|
||||
|
||||
this.buffer = true;
|
||||
this.buffered = [];
|
||||
|
||||
if (this.req.headers.upgrade !== 'WebSocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers.origin
|
||||
, location = (this.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url
|
||||
, waitingForNonce = false;
|
||||
|
||||
if (this.req.headers['sec-websocket-key1']) {
|
||||
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
|
||||
if (! (this.req.head && this.req.head.length >= 8)) {
|
||||
waitingForNonce = true;
|
||||
}
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 WebSocket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Origin: ' + origin
|
||||
, 'Sec-WebSocket-Location: ' + location
|
||||
];
|
||||
|
||||
if (this.req.headers['sec-websocket-protocol']){
|
||||
headers.push('Sec-WebSocket-Protocol: '
|
||||
+ this.req.headers['sec-websocket-protocol']);
|
||||
}
|
||||
} else {
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Web Socket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'WebSocket-Origin: ' + origin
|
||||
, 'WebSocket-Location: ' + location
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
this.socket.setEncoding('utf8');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitingForNonce) {
|
||||
this.socket.setEncoding('binary');
|
||||
} else if (this.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
var headBuffer = '';
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
if (waitingForNonce) {
|
||||
headBuffer += data;
|
||||
|
||||
if (headBuffer.length < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the connection to utf8 encoding after receiving the nonce
|
||||
self.socket.setEncoding('utf8');
|
||||
waitingForNonce = false;
|
||||
|
||||
// Stuff the nonce into the location where it's expected to be
|
||||
self.req.head = headBuffer.substr(0, 8);
|
||||
headBuffer = '';
|
||||
|
||||
if (self.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
this.drained = false;
|
||||
|
||||
if (this.buffer) {
|
||||
this.buffered.push(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
var length = Buffer.byteLength(data)
|
||||
, buffer = new Buffer(2 + length);
|
||||
|
||||
buffer.write('\u0000', 'binary');
|
||||
buffer.write(data, 1, 'utf8');
|
||||
buffer.write('\uffff', 1 + length, 'binary');
|
||||
|
||||
try {
|
||||
if (this.socket.write(buffer)) {
|
||||
this.drained = true;
|
||||
}
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Flushes the internal buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.flush = function () {
|
||||
this.buffer = false;
|
||||
|
||||
for (var i = 0, l = this.buffered.length; i < l; i++) {
|
||||
this.write(this.buffered.splice(0, 1)[0]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the handshake.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.proveReception = function (headers) {
|
||||
var self = this
|
||||
, k1 = this.req.headers['sec-websocket-key1']
|
||||
, k2 = this.req.headers['sec-websocket-key2'];
|
||||
|
||||
if (k1 && k2){
|
||||
var md5 = crypto.createHash('md5');
|
||||
|
||||
[k1, k2].forEach(function (k) {
|
||||
var n = parseInt(k.replace(/[^\d]/g, ''))
|
||||
, spaces = k.replace(/[^ ]/g, '').length;
|
||||
|
||||
if (spaces === 0 || n % spaces !== 0){
|
||||
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
|
||||
self.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
n /= spaces;
|
||||
|
||||
md5.update(String.fromCharCode(
|
||||
n >> 24 & 0xFF,
|
||||
n >> 16 & 0xFF,
|
||||
n >> 8 & 0xFF,
|
||||
n & 0xFF));
|
||||
});
|
||||
|
||||
md5.update(this.req.head.toString('binary'));
|
||||
|
||||
try {
|
||||
this.socket.write(md5.digest('binary'), 'binary');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a payload.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.payload = function (msgs) {
|
||||
for (var i = 0, l = msgs.length; i < l; i++) {
|
||||
this.write(msgs[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Adds data to the buffer.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function (data) {
|
||||
this.buffer += data;
|
||||
this.parse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the buffer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.parse = function () {
|
||||
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
|
||||
chr = this.buffer[i];
|
||||
|
||||
if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
|
||||
this.emit('close');
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (i === 0){
|
||||
if (chr != '\u0000')
|
||||
this.error('Bad framing. Expected null byte as first frame');
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chr == '\ufffd'){
|
||||
this.emit('data', this.buffer.substr(1, i - 1));
|
||||
this.buffer = this.buffer.substr(i + 1);
|
||||
this.i = 0;
|
||||
return this.parse();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
10
lib/transports/websocket/index.js
Normal file
10
lib/transports/websocket/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
/**
|
||||
* Export websocket versions.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
7: require('./7'),
|
||||
8: require('./8'),
|
||||
default: require('./default')
|
||||
};
|
||||
25
lib/util.js
25
lib/util.js
@@ -23,3 +23,28 @@ exports.toArray = function (enu) {
|
||||
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unpacks a buffer to a number.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Left pads a string.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.padl = function (s,n,c) {
|
||||
return new Array(1 + n - s.length).join(c) + s;
|
||||
}
|
||||
|
||||
|
||||
12
package.json
12
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "socket.io"
|
||||
, "version": "0.7.4"
|
||||
, "description": "Realtime apps made cross-browser & easy with a WebSocket-like API"
|
||||
, "version": "0.8.0"
|
||||
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
|
||||
, "homepage": "http://socket.io"
|
||||
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
|
||||
, "author": "Guillermo Rauch <guillermo@learnboost.com>"
|
||||
@@ -9,19 +9,21 @@
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
|
||||
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
|
||||
, { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
|
||||
]
|
||||
, "repository":{
|
||||
"type": "git"
|
||||
, "url": "https://github.com/LearnBoost/Socket.IO-node.git"
|
||||
}
|
||||
, "dependencies": {
|
||||
"socket.io-client": "0.7.3"
|
||||
, "policyfile": "0.0.3"
|
||||
, "redis": "0.6.0"
|
||||
"socket.io-client": "0.8.0"
|
||||
, "policyfile": "0.0.4"
|
||||
, "redis": "0.6.6"
|
||||
}
|
||||
, "devDependencies": {
|
||||
"expresso": "0.7.7"
|
||||
, "should": "0.0.4"
|
||||
, "assertvanish": "0.0.3-1"
|
||||
}
|
||||
, "main": "index"
|
||||
, "engines": { "node": ">= 0.4.0" }
|
||||
|
||||
@@ -180,7 +180,9 @@ client = function (port) {
|
||||
|
||||
create = function (cl) {
|
||||
console.log('');
|
||||
return io.listen(cl.port);
|
||||
var manager = io.listen(cl.port);
|
||||
manager.set('client store expiration', 0);
|
||||
return manager;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
54
test/leaks/socket.leaktest.js
Normal file
54
test/leaks/socket.leaktest.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
require.paths.unshift(__dirname + '/../../lib');
|
||||
|
||||
var assertvanish = require('assertvanish')
|
||||
, common = require('../common')
|
||||
, ports = 15800;
|
||||
|
||||
function resultCallback (leaks, leakedSocket) {
|
||||
if (leaks) {
|
||||
console.error('Leak detected');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.error('No leaks');
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test.
|
||||
*/
|
||||
|
||||
var cl = client(++ports);
|
||||
var io = create(cl);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
console.log('connected');
|
||||
|
||||
socket.on('disconnect', function() {
|
||||
console.log("client gone");
|
||||
setTimeout(gc, 1000);
|
||||
assertvanish(socket, 2000, {silent: true, callback: resultCallback});
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
cl.handshake(function (sid) {
|
||||
var ws = websocket(cl, sid);
|
||||
ws.on('open', function () {
|
||||
console.log('open!');
|
||||
setTimeout(function() {
|
||||
ws.close();
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
}, 100);
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
@@ -21,6 +20,22 @@ var sio = require('socket.io')
|
||||
module.exports = {
|
||||
|
||||
'test setting and getting a configuration flag': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(http.createServer());
|
||||
|
||||
io.set('a', 'b');
|
||||
io.get('a').should.eql('b');
|
||||
|
||||
var port = ++ports
|
||||
, io = sio.listen(http.createServer());
|
||||
|
||||
io.configure(function () {
|
||||
io.set('a', 'b');
|
||||
io.enable('tobi');
|
||||
});
|
||||
|
||||
io.get('a').should.eql('b');
|
||||
|
||||
done();
|
||||
},
|
||||
|
||||
@@ -37,11 +52,9 @@ module.exports = {
|
||||
, io = sio.listen(http.createServer());
|
||||
|
||||
io.configure(function () {
|
||||
io.set('a', 'b');
|
||||
io.enable('tobi');
|
||||
});
|
||||
|
||||
io.get('a').should.eql('b');
|
||||
io.enabled('tobi').should.be.true;
|
||||
|
||||
done();
|
||||
@@ -459,6 +472,91 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test that a referer is accepted for *:* origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', '*:*');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:82/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that valid referer is accepted for addr:* origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:*');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that erroneous referer is denied for addr:* origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:*');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://baz.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(403);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that valid referer port is accepted for addr:port origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:81');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:81/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that erroneous referer port is denied for addr:port origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:81');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(403);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test limiting the supported transports for a manager': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
@@ -534,6 +632,100 @@ module.exports = {
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'test disabling heartbeats': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port)
|
||||
, messages = 0
|
||||
, beat = false
|
||||
, ws;
|
||||
|
||||
io.configure(function () {
|
||||
io.disable('heartbeats');
|
||||
io.set('heartbeat interval', .05);
|
||||
io.set('heartbeat timeout', .05);
|
||||
io.set('close timeout', .05);
|
||||
});
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
setTimeout(function () {
|
||||
socket.disconnect();
|
||||
}, io.get('heartbeat timeout') * 1000 + 100);
|
||||
|
||||
socket.on('disconnect', function (reason) {
|
||||
beat.should.be.false;
|
||||
|
||||
cl.end();
|
||||
ws.finishClose();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}/', function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
data.should.match(/([^:]+)::[\.0-9]+:(.*)/);
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid);
|
||||
ws.on('message', function (packet) {
|
||||
if (++messages == 1) {
|
||||
packet.type.should.eql('connect');
|
||||
} else if (packet.type == 'heartbeat'){
|
||||
beat = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'no duplicate room members': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port);
|
||||
|
||||
Object.keys(io.rooms).length.should.equal(0);
|
||||
|
||||
io.onJoin(123, 'foo');
|
||||
io.rooms.foo.length.should.equal(1);
|
||||
|
||||
io.onJoin(123, 'foo');
|
||||
io.rooms.foo.length.should.equal(1);
|
||||
|
||||
io.onJoin(124, 'foo');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
|
||||
io.onJoin(124, 'foo');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
|
||||
io.onJoin(123, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(1);
|
||||
|
||||
io.onJoin(123, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(1);
|
||||
|
||||
io.onJoin(124, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(2);
|
||||
|
||||
io.onJoin(124, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(2);
|
||||
|
||||
io.server.close();
|
||||
done();
|
||||
},
|
||||
|
||||
'test passing options directly to the Manager through listen': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port, { resource: '/my resource', custom: 'opt' });
|
||||
|
||||
io.get('resource').should.equal('/my resource');
|
||||
io.get('custom').should.equal('opt');
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,8 +80,8 @@ module.exports = {
|
||||
fn(null, true);
|
||||
})
|
||||
.on('connection', function (socket) {
|
||||
socket.handshake.address.address.should.equal('127.0.0.1');
|
||||
socket.handshake.address.port.should.equal(ports);
|
||||
(!!socket.handshake.address.address).should.be.true;
|
||||
(!!socket.handshake.address.port).should.be.true;
|
||||
socket.handshake.headers.host.should.equal('localhost');
|
||||
socket.handshake.headers.connection.should.equal('keep-alive');
|
||||
socket.handshake.time.should.match(/GMT/);
|
||||
@@ -139,5 +139,109 @@ module.exports = {
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
'broadcasting sends and emits on a namespace': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
, calls = 0
|
||||
, connect = 0
|
||||
, message = 0
|
||||
, events = 0
|
||||
, expected = 5
|
||||
, ws1
|
||||
, ws2;
|
||||
|
||||
io.of('a')
|
||||
.on('connection', function (socket){
|
||||
socket.broadcast.emit('b', 'test');
|
||||
socket.broadcast.json.emit('json', {foo:'bar'});
|
||||
socket.broadcast.send('foo');
|
||||
});
|
||||
|
||||
function finish () {
|
||||
connect.should.equal(2);
|
||||
message.should.equal(1);
|
||||
events.should.equal(2);
|
||||
|
||||
cl.end();
|
||||
ws1.finishClose();
|
||||
ws2.finishClose();
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws1 = websocket(cl, sid);
|
||||
|
||||
ws1.on('open', function() {
|
||||
ws1.packet({
|
||||
type: 'connect'
|
||||
, endpoint: 'a'
|
||||
});
|
||||
});
|
||||
|
||||
ws1.on('message', function (data) {
|
||||
if (data.type === 'connect') {
|
||||
++connect;
|
||||
if (++calls === expected) finish();
|
||||
}
|
||||
|
||||
if (data.type === 'message') {
|
||||
++message;
|
||||
if (++calls === expected) finish();
|
||||
}
|
||||
|
||||
if (data.type === 'event') {
|
||||
if (data.name === 'b' || data.name === 'json') ++events;
|
||||
if (++calls === expected) finish();
|
||||
}
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws2 = websocket(cl, sid);
|
||||
|
||||
ws2.on('open', function () {
|
||||
ws2.packet({
|
||||
type: 'connect'
|
||||
, endpoint: 'a'
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
'joining rooms inside a namespace': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
, calls = 0
|
||||
, ws;
|
||||
|
||||
io.of('/foo').on('connection', function (socket) {
|
||||
socket.join('foo.bar');
|
||||
this.in('foo.bar').emit('baz', 'pewpew');
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid);
|
||||
|
||||
ws.on('open', function (){
|
||||
ws.packet({
|
||||
type: 'connect'
|
||||
, endpoint: '/foo'
|
||||
});
|
||||
});
|
||||
|
||||
ws.on('message', function (data) {
|
||||
if (data.type === 'event') {
|
||||
data.name.should.equal('baz');
|
||||
|
||||
cl.end();
|
||||
ws.finishClose();
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
@@ -233,7 +233,7 @@ module.exports = {
|
||||
store.destroy();
|
||||
done();
|
||||
});
|
||||
}, 1900);
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
235
test/transports.websocket.hybi07-parser.test.js
Normal file
235
test/transports.websocket.hybi07-parser.test.js
Normal file
@@ -0,0 +1,235 @@
|
||||
var assert = require('assert');
|
||||
var Parser = require('../lib/transports/wsver/7.js').Parser;
|
||||
|
||||
function makeBufferFromHexString(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;
|
||||
}
|
||||
|
||||
function splitBuffer(buffer) {
|
||||
var b1 = new Buffer(Math.ceil(buffer.length / 2));
|
||||
var b2 = new Buffer(Math.floor(buffer.length / 2));
|
||||
buffer.copy(b1, 0, 0, b1.length);
|
||||
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
|
||||
return [b1, b2];
|
||||
}
|
||||
|
||||
function mask(str, maskString) {
|
||||
var buf = new Buffer(str);
|
||||
var mask = makeBufferFromHexString(maskString || '34 83 a8 68');
|
||||
for (var i = 0; i < buf.length; ++i) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function unpack(buffer) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < buffer.length; ++i) {
|
||||
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function pack(length, number) {
|
||||
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
|
||||
}
|
||||
|
||||
function padl(s,n,c) {
|
||||
return new Array(1 + n - s.length).join(c) + s;
|
||||
}
|
||||
|
||||
function dump(data) {
|
||||
var s = '';
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
s += padl(data[i].toString(16), 2, '0') + ' ';
|
||||
}
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'can parse unmasked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 05 48 65 6c 6c 6f';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('Hello', data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse close message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '88 00';
|
||||
|
||||
var gotClose = false;
|
||||
p.on('close', function(data) {
|
||||
gotClose = true;
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotClose);
|
||||
},
|
||||
'can parse masked text message': function() {
|
||||
var p = new Parser();
|
||||
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.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('5:::{"name":"echo"}', data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a masked text message longer than 125 bytes': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a really long masked text message': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet1));
|
||||
p.add(makeBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a ping message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'Hello';
|
||||
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a ping with no data': function() {
|
||||
var p = new Parser();
|
||||
var packet = '89 00';
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet1));
|
||||
p.add(makeBufferFromHexString(pingPacket));
|
||||
p.add(makeBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'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 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 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
var buffers = [];
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet1)));
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(pingPacket)));
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet2)));
|
||||
for (var i = 0; i < buffers.length; ++i) {
|
||||
p.add(buffers[i]);
|
||||
}
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
};
|
||||
|
||||
235
test/transports.websocket.hybi10-parser.test.js
Normal file
235
test/transports.websocket.hybi10-parser.test.js
Normal file
@@ -0,0 +1,235 @@
|
||||
var assert = require('assert');
|
||||
var Parser = require('../lib/transports/wsver/8.js').Parser;
|
||||
|
||||
function makeBufferFromHexString(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;
|
||||
}
|
||||
|
||||
function splitBuffer(buffer) {
|
||||
var b1 = new Buffer(Math.ceil(buffer.length / 2));
|
||||
var b2 = new Buffer(Math.floor(buffer.length / 2));
|
||||
buffer.copy(b1, 0, 0, b1.length);
|
||||
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
|
||||
return [b1, b2];
|
||||
}
|
||||
|
||||
function mask(str, maskString) {
|
||||
var buf = new Buffer(str);
|
||||
var mask = makeBufferFromHexString(maskString || '34 83 a8 68');
|
||||
for (var i = 0; i < buf.length; ++i) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function unpack(buffer) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < buffer.length; ++i) {
|
||||
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function pack(length, number) {
|
||||
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
|
||||
}
|
||||
|
||||
function padl(s,n,c) {
|
||||
return new Array(1 + n - s.length).join(c) + s;
|
||||
}
|
||||
|
||||
function dump(data) {
|
||||
var s = '';
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
s += padl(data[i].toString(16), 2, '0') + ' ';
|
||||
}
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'can parse unmasked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 05 48 65 6c 6c 6f';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('Hello', data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse close message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '88 00';
|
||||
|
||||
var gotClose = false;
|
||||
p.on('close', function(data) {
|
||||
gotClose = true;
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotClose);
|
||||
},
|
||||
'can parse masked text message': function() {
|
||||
var p = new Parser();
|
||||
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.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('5:::{"name":"echo"}', data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a masked text message longer than 125 bytes': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a really long masked text message': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet1));
|
||||
p.add(makeBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a ping message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'Hello';
|
||||
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a ping with no data': function() {
|
||||
var p = new Parser();
|
||||
var packet = '89 00';
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
|
||||
var p = new Parser();
|
||||
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 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet1));
|
||||
p.add(makeBufferFromHexString(pingPacket));
|
||||
p.add(makeBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'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 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 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
var buffers = [];
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet1)));
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(pingPacket)));
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet2)));
|
||||
for (var i = 0; i < buffers.length; ++i) {
|
||||
p.add(buffers[i]);
|
||||
}
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
var sio = require('socket.io')
|
||||
, should = require('./common')
|
||||
, parser = sio.parser
|
||||
, ports = 15400;
|
||||
, ports = 15800;
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
@@ -899,6 +899,59 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test leaving a room': function (done) {
|
||||
var port = ++ports
|
||||
, cl1 = client(port)
|
||||
, cl2 = client(port)
|
||||
, io = create(cl1)
|
||||
, joins = 0
|
||||
, disconnects = 0;
|
||||
|
||||
io.set('close timeout', 0);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.join('foo');
|
||||
io.sockets.clients('foo').should.have.length(++joins);
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
socket.leave('foo');
|
||||
socket.leave('foo');
|
||||
socket.leave('foo');
|
||||
|
||||
io.sockets.clients('foo').should.have.length(--joins);
|
||||
|
||||
if (++disconnects == 2) {
|
||||
io.server.close();
|
||||
cl1.end();
|
||||
cl2.end();
|
||||
done();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
cl1.handshake(function (sid) {
|
||||
var ws1 = websocket(cl1, sid);
|
||||
ws1.on('message', function (msg) {
|
||||
if (!ws1.connected) {
|
||||
msg.type.should.eql('connect');
|
||||
ws1.connected = true;
|
||||
ws1.finishClose();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
cl2.handshake(function (sid) {
|
||||
var ws2 = websocket(cl2, sid);
|
||||
ws2.on('message', function (msg) {
|
||||
if (!ws2.connected) {
|
||||
msg.type.should.eql('connect');
|
||||
ws2.connected = true;
|
||||
ws2.finishClose();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test message with broadcast flag': function (done) {
|
||||
var port = ++ports
|
||||
, cl1 = client(port)
|
||||
@@ -1493,8 +1546,8 @@ module.exports = {
|
||||
, ws;
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.handshake.address.address.should.equal('127.0.0.1');
|
||||
socket.handshake.address.port.should.equal(ports);
|
||||
(!!socket.handshake.address.address).should.be.true;
|
||||
(!!socket.handshake.address.port).should.be.true;
|
||||
socket.handshake.headers.host.should.equal('localhost');
|
||||
socket.handshake.headers.connection.should.equal('keep-alive');
|
||||
socket.handshake.time.should.match(/GMT/);
|
||||
@@ -1584,6 +1637,42 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test accessing handshake data from sockets on disconnect': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
, ws;
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('disconnect', function () {
|
||||
|
||||
(!!socket.handshake.address.address).should.be.true;
|
||||
(!!socket.handshake.address.port).should.be.true;
|
||||
socket.handshake.headers.host.should.equal('localhost');
|
||||
socket.handshake.headers.connection.should.equal('keep-alive');
|
||||
socket.handshake.time.should.match(/GMT/);
|
||||
|
||||
setTimeout(function () {
|
||||
ws.finishClose();
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
socket.disconnect();
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid);
|
||||
ws.on('message', function (msg) {
|
||||
if (!ws.connected) {
|
||||
msg.type.should.eql('connect');
|
||||
ws.connected = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test for intentional and unintentional disconnects': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
@@ -1642,5 +1731,41 @@ module.exports = {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'test socket clean up': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
, ws;
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
var self = this
|
||||
, id = socket.id;
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
setTimeout(function () {
|
||||
var available = !!self.sockets[id];
|
||||
|
||||
available.should.be.false;
|
||||
ws.finishClose();
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
socket.disconnect();
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid);
|
||||
ws.on('message', function (msg) {
|
||||
if (!ws.connected) {
|
||||
msg.type.should.eql('connect');
|
||||
ws.connected = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -2635,4 +2635,82 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test emitting to closed clients': function (done) {
|
||||
var cl = client(++ports)
|
||||
, cl2 = client(ports)
|
||||
, io = create(cl)
|
||||
, connections = 0;
|
||||
|
||||
io.configure(function () {
|
||||
io.set('close timeout', .1);
|
||||
});
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.send('a');
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, packs) {
|
||||
res.statusCode.should.equal(200);
|
||||
packs.should.have.length(1);
|
||||
packs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
|
||||
|
||||
cl2.handshake(function (sid2) {
|
||||
cl2.get(
|
||||
'/socket.io/{protocol}/xhr-polling/' + sid2
|
||||
, function (res, packs) {
|
||||
res.statusCode.should.equal(200);
|
||||
packs.should.have.length(1);
|
||||
packs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
|
||||
|
||||
io.sockets.emit('woot', 'b');
|
||||
|
||||
var total = 2;
|
||||
|
||||
cl.get(
|
||||
'/socket.io/{protocol}/xhr-polling/' + sid
|
||||
, function (res, packs) {
|
||||
res.statusCode.should.equal(200);
|
||||
packs.should.have.length(1);
|
||||
packs[0].should.eql({
|
||||
type: 'event'
|
||||
, endpoint: ''
|
||||
, name: 'woot'
|
||||
, args: ['b']
|
||||
});
|
||||
|
||||
--total || finish();
|
||||
}
|
||||
);
|
||||
|
||||
cl2.get(
|
||||
'/socket.io/{protocol}/xhr-polling/' + sid2
|
||||
, function (res, packs) {
|
||||
res.statusCode.should.equal(200);
|
||||
packs.should.have.length(1);
|
||||
packs[0].should.eql({
|
||||
type: 'event'
|
||||
, endpoint: ''
|
||||
, name: 'woot'
|
||||
, args: ['b']
|
||||
});
|
||||
|
||||
--total || finish();
|
||||
}
|
||||
);
|
||||
|
||||
function finish () {
|
||||
cl.end();
|
||||
cl2.end();
|
||||
io.server.close();
|
||||
done();
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user