Compare commits

...

27 Commits

Author SHA1 Message Date
Roman Shtylman
213577204a Merge branch 'dynamic-namespaces' of https://github.com/markitx/socket.io into dynamic-namespace
Conflicts:
	lib/client.js
	test/socket.io.js
2015-02-04 18:17:49 -08:00
Dylan Lingelbach
dd72676c25 Make name more descriptive 2015-02-04 13:43:21 -06:00
Dylan Lingelbach
3bf7be73a0 Style cleanup 2015-02-04 13:40:40 -06:00
Dylan Lingelbach
0360553c99 Merge branch 'master' into dynamic-namespaces 2015-02-04 13:10:40 -06:00
Dylan Lingelbach
de5cbdb833 Improve tests 2015-02-04 12:33:18 -06:00
Guillermo Rauch
a93d05a9f3 package: bump parser 2015-02-03 16:30:49 -08:00
Guillermo Rauch
5ce06d3088 socket: warn node_redis-style about missing error 2015-02-03 16:26:21 -08:00
Guillermo Rauch
816bfec783 added failing tests 2015-02-03 16:26:08 -08:00
Guillermo Rauch
0a17c90d7a test: added failing test 2015-02-03 15:10:46 -08:00
Guillermo Rauch
3645741b86 test: increase timeout for large binary data test 2015-01-31 09:35:37 -08:00
Guillermo Rauch
f2a7322b5a update deps 2015-01-31 08:46:32 -08:00
Guillermo Rauch
97c6568f65 Merge pull request #1966 from cha0s/clients
Suggestion for implementation of clients API
2015-01-31 08:34:43 -08:00
Ruben Rodriguez II
8814825a35 Suggestion for implementation of clients API 2015-01-31 02:17:49 -06:00
Guillermo Rauch
58eaecad27 Merge branch 'master' of github.com:Automattic/socket.io 2015-01-29 20:39:00 -08:00
Guillermo Rauch
94157e650e point to master 2015-01-29 20:38:36 -08:00
Guillermo Rauch
0935b81da2 Merge pull request #1963 from michael-luo/bugfix/1956
bugfix/1956 test for don't reuse same-namespace connections
2015-01-29 20:34:38 -08:00
Michael Luo
afa871bb8a bugfix/1956 test for don't reuse same-namspace connections 2015-01-24 20:19:16 -08:00
Guillermo Rauch
1b01e16a6c fix broken previous commit 2015-01-24 08:02:05 -08:00
Guillermo Rauch
bd6f638c8f package: bump to work with all objects (fixes #1955) 2015-01-24 07:06:30 -08:00
Guillermo Rauch
83b36e54ac Merge pull request #1958 from nkzawa/compress
Support compression
2015-01-23 10:04:43 -08:00
Guillermo Rauch
2f0d9d05af fix origin verification default https port [evanlucas]
for PR #1693
2015-01-22 21:41:59 +00:00
Naoyuki Kanezawa
429eb0cb7c point to the specific commit 2015-01-22 05:26:37 +09:00
Naoyuki Kanezawa
ac8e8598d7 support compression 2015-01-22 05:01:45 +09:00
Dylan Lingelbach
0ce0ce1dbc Merge pull request #1 from markitx/dynamic-namespaces
Dynamic namespaces
2015-01-09 11:56:51 -06:00
Dylan Lingelbach
cc7ce79251 Ensure server sends connect message 2014-12-19 15:32:20 -06:00
Dylan Lingelbach
1b77c27f7b Fix test 2014-12-19 15:27:08 -06:00
Dylan Lingelbach
1f8bb8a0ec Support dynamic namespaces 2014-12-19 13:22:41 -06:00
7 changed files with 457 additions and 32 deletions

View File

@@ -240,6 +240,40 @@ server.listen(3000);
Hash of `Socket` objects that are connected to this namespace indexed
by `id`.
### Namespace#clients(fn:Function)
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
An example to get all clients in a namespace:
```js
var io = require('socket.io')();
io.of('/chat').clients(function(error, clients){
if (error) throw error;
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
});
```
An example to get all clients in namespace's room:
```js
var io = require('socket.io')();
io.of('/chat').in('general').clients(function(error, clients){
if (error) throw error;
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
});
```
As with broadcasting, the default is all clients from the default namespace ('/'):
```js
var io = require('socket.io')();
io.clients(function(error, clients){
if (error) throw error;
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
});
```
### Namespace#use(fn:Function):Namespace
Registers a middleware, which is a function that gets executed for
@@ -341,6 +375,18 @@ server.listen(3000);
});
```
### Socket#compress(v:Boolean):Socket
Sets a modifier for a subsequent event emission that the event data will
only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method.
```js
var io = require('socket.io')();
io.on('connection', function(socket){
socket.compress(false).emit('an event', { some: 'data' });
});
```
### Client
The `Client` class represents an incoming transport (engine.io)

View File

@@ -59,26 +59,40 @@ Client.prototype.setup = function(){
*/
Client.prototype.connect = function(name){
debug('connecting to namespace %s', name);
if (!this.server.nsps[name]) {
this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
return;
}
var nsp = this.server.of(name);
if ('/' != name && !this.nsps['/']) {
this.connectBuffer.push(name);
return;
}
var self = this;
var socket = nsp.add(this, function(){
self.sockets.push(socket);
self.nsps[nsp.name] = socket;
debug('connecting to namespace %s', name);
if ('/' == nsp.name && self.connectBuffer.length > 0) {
self.connectBuffer.forEach(self.connect, self);
self.connectBuffer = [];
function connectNamespace() {
var nsp = self.server.of(name);
if ('/' != name && !self.nsps['/']) {
self.connectBuffer.push(name);
return;
}
var socket = nsp.add(self, function(){
self.sockets.push(socket);
self.nsps[nsp.name] = socket;
if ('/' == nsp.name && self.connectBuffer.length > 0) {
self.connectBuffer.forEach(self.connect, self);
self.connectBuffer = [];
}
});
}
if (self.server.nsps[name]) {
// Namespace already created, connect
connectNamespace();
return;
}
self.server.checkNamespace(name, function(allow) {
if (allow) {
connectNamespace();
return
}
self.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
});
};
@@ -135,17 +149,18 @@ Client.prototype.close = function(){
* @param {Object} packet object
* @param {Boolean} whether packet is already encoded
* @param {Boolean} whether packet is volatile
* @param {Boolean} whether packet should be compressed
* @api private
*/
Client.prototype.packet = function(packet, preEncoded, volatile){
Client.prototype.packet = function(packet, preEncoded, volatile, compress){
var self = this;
// this writes to the actual connection
function writeToEngine(encodedPackets) {
if (volatile && !self.conn.transport.writable) return;
for (var i = 0; i < encodedPackets.length; i++) {
self.conn.write(encodedPackets[i]);
self.conn.write(encodedPackets[i], { compress: compress });
}
}

View File

@@ -43,6 +43,7 @@ function Server(srv, opts){
}
opts = opts || {};
this.nsps = {};
this.nspValidators = [];
this.path(opts.path || '/socket.io');
this.serveClient(false !== opts.serveClient);
this.adapter(opts.adapter || Adapter);
@@ -69,7 +70,10 @@ Server.prototype.checkRequest = function(req, fn) {
if (origin) {
try {
var parts = url.parse(origin);
parts.port = parts.port || 80;
var defaultPort = 'https:' == parts.protocol ? 443 : 80;
parts.port = parts.port != null
? parts.port
: defaultPort;
var ok =
~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
~this._origins.indexOf(parts.hostname + ':*') ||
@@ -134,6 +138,53 @@ Server.prototype.set = function(key, val){
return this;
};
/**
* Sets up server middleware to validate incoming namespaces not already created on the server.
*
* @return {Server} self
* @api public
*/
Server.prototype.useNamespaceValidator = function(fn){
this.nspValidators.push(fn);
return this;
};
/**
* Executes the middleware for an incoming namespace not already created on the server.
*
* @param name of incomming namespace
* @param {Function} last fn call in the middleware
* @api private
*/
Server.prototype.checkNamespace = function(name, fn){
var fns = this.nspValidators.slice(0);
if (!fns.length) return fn(false);
var namespaceAllowed = false; // Deny unknown namespaces by default
function run(i){
fns[i](name, function(err, allow){
// upon error, short-circuit
if (err) return fn(false);
// if one piece of middleware explicitly denies namespace, short-circuit
if (allow === false) return fn(false);
namespaceAllowed = namespaceAllowed || allow === true;
// if no middleware left, summon callback
if (!fns[i + 1]) return fn(namespaceAllowed);
// go on to next
run(i + 1);
});
}
run(0);
};
/**
* Sets the client serving path.
*
@@ -251,7 +302,7 @@ Server.prototype.attachServe = function(srv){
var self = this;
srv.removeAllListeners('request');
srv.on('request', function(req, res) {
if (0 == req.url.indexOf(url)) {
if (0 === req.url.indexOf(url)) {
self.serve(req, res);
} else {
for (var i = 0; i < evs.length; i++) {
@@ -358,7 +409,7 @@ Server.prototype.close = function(){
* Expose main namespace (/).
*/
['on', 'to', 'in', 'use', 'emit', 'send', 'write'].forEach(function(fn){
['on', 'to', 'in', 'use', 'emit', 'send', 'write', 'clients'].forEach(function(fn){
Server.prototype[fn] = function(){
var nsp = this.sockets[fn];
return nsp.apply(this.sockets, arguments);

View File

@@ -7,7 +7,7 @@ var Socket = require('./socket');
var Emitter = require('events').EventEmitter;
var parser = require('socket.io-parser');
var debug = require('debug')('socket.io:namespace');
var hasBin = require('has-binary-data');
var hasBin = require('has-binary');
/**
* Module exports.
@@ -240,3 +240,15 @@ Namespace.prototype.write = function(){
this.emit.apply(this, args);
return this;
};
/**
* Gets a list of clients.
*
* @return {Namespace} self
* @api public
*/
Namespace.prototype.clients = function(fn){
this.adapter.clients(this.rooms, fn);
return this;
};

View File

@@ -7,7 +7,7 @@ var Emitter = require('events').EventEmitter;
var parser = require('socket.io-parser');
var url = require('url');
var debug = require('debug')('socket.io:socket');
var hasBin = require('has-binary-data');
var hasBin = require('has-binary');
/**
* Module exports.
@@ -202,7 +202,8 @@ Socket.prototype.write = function(){
Socket.prototype.packet = function(packet, preEncoded){
packet.nsp = this.nsp.name;
var volatile = this.flags && this.flags.volatile;
this.client.packet(packet, preEncoded, volatile);
var compress = !this.flags || false !== this.flags.compress;
this.client.packet(packet, preEncoded, volatile, compress);
};
/**
@@ -390,7 +391,12 @@ Socket.prototype.ondisconnect = function(){
*/
Socket.prototype.onerror = function(err){
this.emit('error', err);
if (this.listeners('error').length) {
this.emit('error', err);
} else {
console.error('Missing error handler on `socket`.');
console.error(err.stack);
}
};
/**
@@ -442,3 +448,17 @@ Socket.prototype.disconnect = function(close){
}
return this;
};
/**
* Sets the compress flag.
*
* @param {Boolean} if `true`, compresses the sending data
* @return {Socket} self
* @api public
*/
Socket.prototype.compress = function(compress){
this.flags = this.flags || {};
this.flags.compress = compress;
return this;
};

View File

@@ -19,11 +19,11 @@
"test": "mocha --reporter dot --slow 200ms --bail"
},
"dependencies": {
"engine.io": "1.5.1",
"socket.io-parser": "2.2.2",
"socket.io-client": "1.3.2",
"socket.io-adapter": "0.3.1",
"has-binary-data": "0.1.3",
"engine.io": "automattic/engine.io#ddc64a",
"socket.io-parser": "2.2.3",
"socket.io-client": "automattic/socket.io-client#210d65",
"socket.io-adapter": "automattic/socket.io-adapter#ae79d8",
"has-binary": "0.1.6",
"debug": "2.1.0"
},
"devDependencies": {

View File

@@ -317,6 +317,17 @@ describe('socket.io', function(){
done();
});
});
it('should default to port 443 when protocol is https', function(done) {
var sockets = io({ origins: 'https://foo.example:443' }).listen('54036');
request.get('http://localhost:54036/socket.io/default/')
.set('origin', 'https://foo.example')
.query({ transport: 'polling' })
.end(function (err, res) {
expect(res.status).to.be(200);
done();
});
});
});
describe('close', function(){
@@ -586,6 +597,211 @@ describe('socket.io', function(){
});
});
});
it('should not reuse same-namespace connections', function(done){
var srv = http();
var sio = io(srv);
var connections = 0;
srv.listen(function() {
var clientSocket1 = client(srv);
var clientSocket2 = client(srv);
sio.on('connection', function() {
connections++;
if(connections === 2) {
done();
}
});
});
});
it('should find all clients in a namespace', function(done){
var srv = http();
var sio = io(srv);
var chatSids = [];
var otherSid = null;
srv.listen(function(){
var c1 = client(srv, '/chat');
var c2 = client(srv, '/chat', {forceNew: true});
var c3 = client(srv, '/other', {forceNew: true});
var total = 3;
sio.of('/chat').on('connection', function(socket){
chatSids.push(socket.id);
--total || getClients();
});
sio.of('/other').on('connection', function(socket){
otherSid = socket.id;
--total || getClients();
});
});
function getClients() {
sio.of('/chat').clients(function(error, sids) {
expect(error).to.be.undefined;
expect(sids).to.contain(chatSids[0]);
expect(sids).to.contain(chatSids[1]);
expect(sids).to.not.contain(otherSid);
done();
});
}
});
it('should find all clients in a namespace room', function(done){
var srv = http();
var sio = io(srv);
var chatFooSid = null;
var chatBarSid = null;
var otherSid = null;
srv.listen(function(){
var c1 = client(srv, '/chat');
var c2 = client(srv, '/chat', {forceNew: true});
var c3 = client(srv, '/other', {forceNew: true});
var chatIndex = 0;
var total = 3;
sio.of('/chat').on('connection', function(socket){
if (chatIndex++) {
socket.join('foo', function() {
chatFooSid = socket.id;
--total || getClients();
});
} else {
socket.join('bar', function() {
chatBarSid = socket.id;
--total || getClients();
});
}
});
sio.of('/other').on('connection', function(socket){
socket.join('foo', function() {
otherSid = socket.id;
--total || getClients();
});
});
});
function getClients() {
sio.of('/chat').in('foo').clients(function(error, sids) {
expect(error).to.be.undefined;
expect(sids).to.contain(chatFooSid);
expect(sids).to.not.contain(chatBarSid);
expect(sids).to.not.contain(otherSid);
done();
});
}
});
it('should find all clients across namespace rooms', function(done){
var srv = http();
var sio = io(srv);
var chatFooSid = null;
var chatBarSid = null;
var otherSid = null;
srv.listen(function(){
var c1 = client(srv, '/chat');
var c2 = client(srv, '/chat', {forceNew: true});
var c3 = client(srv, '/other', {forceNew: true});
var chatIndex = 0;
var total = 3;
sio.of('/chat').on('connection', function(socket){
if (chatIndex++) {
socket.join('foo', function() {
chatFooSid = socket.id;
--total || getClients();
});
} else {
socket.join('bar', function() {
chatBarSid = socket.id;
--total || getClients();
});
}
});
sio.of('/other').on('connection', function(socket){
socket.join('foo', function() {
otherSid = socket.id;
--total || getClients();
});
});
});
function getClients() {
sio.of('/chat').clients(function(error, sids) {
expect(error).to.be.undefined;
expect(sids).to.contain(chatFooSid);
expect(sids).to.contain(chatBarSid);
expect(sids).to.not.contain(otherSid);
done();
});
}
});
it('should allow connections to dynamic namespaces', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var namespace = '/dynamic';
var dynamic = client(srv,namespace);
sio.useNamespaceValidator(function(nsp, next) {
expect(nsp).to.be(namespace);
next(null, true);
});
dynamic.on('error', function(err) {
expect().fail();
});
dynamic.on('connect', function() {
expect(sio.nsps[namespace]).to.be.a(Namespace);
expect(sio.nsps[namespace].sockets.length).to.be(1);
done();
});
});
});
it('should not allow connections to dynamic namespaces if not supported', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var namespace = '/dynamic';
sio.useNamespaceValidator(function(nsp, next) {
expect(nsp).to.be(namespace);
next(null, false);
});
sio.on('connect', function(socket) {
if (socket.nsp.name === namespace) {
expect().fail();
}
});
var dynamic = client(srv,namespace);
dynamic.on('connect', function(){
expect().fail();
});
dynamic.on('error', function(err) {
expect(err).to.be("Invalid namespace");
done();
});
});
});
it('should not allow connections to dynamic namespaces if there is an error', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var namespace = '/dynamic';
sio.useNamespaceValidator(function(nsp, next) {
expect(nsp).to.be(namespace);
next(new Error(), true);
});
sio.on('connect', function(socket) {
if (socket.nsp.name === namespace) {
expect().fail();
}
});
var dynamic = client(srv,namespace);
dynamic.on('connect', function(){
expect().fail();
});
dynamic.on('error', function(err) {
expect(err).to.be("Invalid namespace");
done();
});
});
});
});
describe('socket', function(){
@@ -1279,6 +1495,8 @@ describe('socket.io', function(){
});
it('should handle very large binary data', function(done){
this.timeout(10000);
var srv = http();
var sio = io(srv);
var received = 0;
@@ -1329,6 +1547,70 @@ describe('socket.io', function(){
});
});
});
it('should enable compression by default', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var socket = client(srv);
sio.on('connection', function(s){
s.conn.once('packetCreate', function(packet) {
expect(packet.options.compress).to.be(true);
done();
});
s.emit('woot', 'hi');
});
});
});
it('should disable compression', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var socket = client(srv);
sio.on('connection', function(s){
s.conn.once('packetCreate', function(packet) {
expect(packet.options.compress).to.be(false);
done();
});
s.compress(false).emit('woot', 'hi');
});
});
});
it('should error with raw binary and warn', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var socket = client(srv);
sio.on('connection', function(s){
s.conn.on('upgrade', function(){
console.log('\033[96mNote: warning expected and normal in test.\033[39m');
socket.io.engine.write('5woooot');
setTimeout(function(){
done();
}, 100);
});
});
});
});
it('should not crash with raw binary', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var socket = client(srv);
sio.on('connection', function(s){
s.once('error', function(err){
expect(err.message).to.match(/Illegal attachments/);
done();
});
s.conn.on('upgrade', function(){
socket.io.engine.write('5woooot');
});
});
});
});
});
describe('messaging many', function(){
@@ -1651,7 +1933,6 @@ describe('socket.io', function(){
});
});
});
});
describe('middleware', function(done){