Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sending to all clients except sender #15

Closed
ghost opened this issue Sep 30, 2014 · 3 comments
Closed

Sending to all clients except sender #15

ghost opened this issue Sep 30, 2014 · 3 comments

Comments

@ghost
Copy link

ghost commented Sep 30, 2014

I have an event listener:
var events = require('events');
var eventEmitter = new events.EventEmitter();
eventEmitter.on('doorOpen', function ringBell()
{
console.log('ring ring ring');
});

I have also private variables inside my workers, like:
var ClientsMap[data.userID] = {
SocketHandler: socket.id
};

The problem is when I emit an event (coming from an external request):
eventEmitter.emit('doorOpen');

I don't have access to my variable 'ClientsMap' because the variable is inside another worker.
Example: I add a user on a process PID 6910 and the event is sent to another worker 6913, so 6913 he don't have any reference to my local variable ClientsMap...

How I can send a general event between my workers (like req maybe) ?

@ghost ghost changed the title Events between workers Events/variables between workers Sep 30, 2014
@jondubois
Copy link
Member

What you pointed out is correct, each worker has its own scope and cannot access each other's variables directly as you suggested.

Also, note that you should treat workers as though they are disposable and can crash at any time - This is a central idea of the 12-factor app (http://12factor.net/disposability).

In SC, you can share events/data between workers using the global nData object (accessible through property of worker object):

worker.global

The global object is an nData client (https://github.com/topcloud/ndata) which you can use to store/retrieve data asynchronously in a central place to share between all workers. It's basically a lightweight version of Redis which runs entirely in Node.js.

Note that in SocketCluster, a client will always be sent to the same worker (this applies to every HTTP request and WebSocket connection made during that session). If a user has multiple tabs open in the browser (multiple sockets), they will all be sent to the same worker as well.

There are a few general rules for using variables inside workers:

  • These variables should only store data which concerns a user/session/socket which is bound to the current worker, and;
  • The data that you store inside these variables shouldn't be operation-critical - Basically, you shouldn't store data in a variable if that data is necessary for your system to continue to operate normally after a worker crash. (You should assume that your workers are disposable)

All this said, when designing a distributed system, you shouldn't try to store individual clients as you are trying to do. This works against the principles of the Publish/Subscribe pattern (http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern).

You should try to resist the urge of having all your workers 'know' about all of your clients (this approach is simply not scalable). Instead, you should design your system in such a way that you do not have to keep track of them at all (yes it's possible) - You will find this MUCH easier to deal with and it will scale seamlessly.

Your clients should listen for events that they are interested in - In your workers, you can use the SUBSCRIBE middleware to authorize/block them from doing so. Once your client is subscribed to a channel, they will receive all events that you publish to it. You don't have to know which clients are subscribed to that channel in order to send messages to them.

For example, you could have an event channel for a user ('Bob') and another user ('Alice') could send messages to that channel by emitting to it.

For example, assume this client-side code (for Bob):

socket.on('Bob', function (data) {
    console.log('Received message from ' + data.from + ': ' + data.message);
});

Then assume this client-side code (for Alice):

socket.publish('Bob', {
    from: 'Alice',
    message: 'Hi Bob!'
});

Here Alice is sending a message to Bob's event channel. Only clients which are subscribed to the 'Bob' event will receive the message (in this case just Bob, no one else). As mentioned before, you can control who can subscribe to a channel using middleware - You can control access by authenticating the user. See this post for more details: http://ncombo.wordpress.com/2014/08/22/full-stack-pubsub-with-socketcluster/

@jondubois
Copy link
Member

Your approach to handling events sounds good.

With regard to middleware, you're almost there.
Note that you should always call next() eventually (for every publish message).
If you call next() with no arguments then it means allow, if you call next with a a string as argument like:

next('You are not allowed to publish to this channel...')

then that means fail.
If you never call next() (as would happen in some cases in the example you showed) then it will timeout and throw a timeout error on the client (which doesn't give much information to the browser) - It's better to call next() with an explicit error to let the browser know exactly why they are not allowed to publish to that channel.

Also, I feel that you don't need middleware to do what you're trying to do. If you don't want a specific client socket to receive an event - All you have to do is not listen to that event.
SocketCluster is really efficient at making sure that only client sockets which are listening to an event will receive that event.

You generally don't need to block a user/socket from publishing an event to their own channel since there is no security issue with that. The publish middleware is mostly used for preventing other users from publishing events to a channel which they shouldn't have access to.

Documentation on middleware is lacking at the moment, so I'll do my best to provide an explanation:

Following from the example I gave earlier.
If you have a channel for 'Bob', you might want to setup a SUBSCRIBE middleware function which will make sure that only the sockets which belong to the user Bob will be allowed to listen to that event. On the publish side, you might want to add middleware to make sure that only friends of Bob are allowed to post messages to his channel.

To make good use of middleware, you need some way to associate sockets (or a session) with a particular user's identify. You can do this in a number of ways (you can make use of tools like Redis or other database engines to keep track of session IDs and authentication tokens). Note that you can get the session id from:

socket.session.id

However, probably the easiest way is to use SC's socket.session (also accessible over HTTP using req.session) object directly to set/get auth tokens, you can perform authentication over either HTTP or over a realtime connection (whatever works best for you):

Example (server) code:

scServer.on('connection', function (socket) {
  socket.on('login', function (data) {
    // Get user's actualPassword from a database then compare it with 
    // the provided data.password to check if the user is who they claim to be
    // Here we are using MySQL (see https://github.com/felixge/node-mysql/)
    connection.query('SELECT * from user WHERE username = ?', [data.username], 
      function (err, rows) {
        var actualPassword = rows[0].password;

        if (data.password == actualPassword) {
          // Password matches so set a token to associate this session with the username
          socket.session.set('username', data.username);
        }
      }
    );
  });
});

Then the subscribe middleware could look like this (Note that this is highly simplified):

scServer.addMiddleware(scServer.MIDDLEWARE_SUBSCRIBE, function (socket, event, next) {
  socket.session.get('username', function (err, username) {
    if (event.indexOf(username) == 0) {
      // If the event begins with the session's username token,
      // then allow it to subscribe to that event
      next();
    } else {
      // Otherwise block with appropriate error message
      next("ERROR - You cannot subscribe to other users' channels");
    }
  });
});

@ghost
Copy link
Author

ghost commented Sep 30, 2014

Thanks for the tip for the next method with parameters.

The middleware example provided can't be used because in my case 1 username can have multiple events.

I try to achieve this: sending to all clients except sender:

// Example from socket.io
 socket.broadcast.emit('message', "this is a test");

@ghost ghost changed the title Events/variables between workers Sending to all clients except sender Sep 30, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant