Search code examples
pythondjangopython-3.xtornadosockjs-tornado

Private messaging using sockjs-tornado


I have implemented chat feature using sockjs-tornado and could store the messages in RethinkDB.

Could you please help me on how do I establish private channel for messaging in sockjs-tornado ? (I mean Private conversation / one to one)

Below is the on_message function in my server side code -

def on_message(self, message):
    str=message
    mg=str.split('#:#')
    sender=1 # This is the sender user id
    receiver=2 #This is the receiver user id - I need to implement session variables to have these id's so that I can use it here this way
    ts=r.expr(datetime.now(r.make_timezone('00:00')))
    connection = r.connect(host="192.x.x.x")
    r.db("djrechat").table('events').insert({"usrs":mg[0],"msg":mg[1],"tstamp":ts,"snder":sender,"rcver":receiver}).run(connection)
    log.info(message)
    self.broadcast(self.participants, '{} - {}'.format(self.stamp(),message))

Currently this is broadcasting to all the clients connected. May be I should have a channel id and to send message only to the two clients which will have the same channel id, but how do I implement it or is there any better solution for this?

At client side, I have below javascript -

      function connect() {
        disconnect();
        conn = new SockJS('http://localhost:8080/chat', ['websocket','xhr-streaming','iframe-eventsource','iframe-htmlfile','xhr-polling','iframe-xhr-polling','jsonp-polling']);
        //log('Connecting.....');
        conn.onopen = function() {
        //  log('Connected. (' + conn.protocol + ')');
        log('Connected.');
        };

        conn.onmessage = function(e) {
          log(e.data);
        };

        conn.onclose = function() {
          log('Disconnected.');
          conn = null;
        };
      }

Am using python 3.4 - Django 1.8.4 and Rethinkdb


Solution

  • My answer makes the assumption that all clients in the current solution connect to a SockJS server with a channel name that is used for broadcasts of the chat messages (or something close to this scenario). I further assume that the round-trip when a user sends a message from the client is:

    [Sender (Client)] ------- Message (POST) --> [App Server] --- Message --> [Database]
                                                      |
                                                   Message
                                                      v
    [Receiver(s) (Client)] <-- Message (WS) -- [SockJS Server]
    

    There are multiple solutions to this problem. I will only outline the one I think is simplest and most robust here:

    • Let every user subscribe to a per-user (private) channel in addition to the broadcast-channel in the chat. If you implement this solution, the only additional change is to make the app server aware of private messages which must be sendt only to the receiver's private channel.

    Another, slightly more complex, and inelegant solution would be to create ad hoc channels for each private pair when needed. However, how would you get User B to subscribe to the (newly created) private channel when User A wants to chat with User B? One way would be to broadcast a request for User B to connect to that channel, but that would tell all the other clients (at the code level) about this private chat that will take place; and if any of those clients are compromised, that information can be misused for, for example, eavesdropping or crashing the private party.

    Additional Advice

    I would also like to mention that since I wrote my sockjs - example of implementing rooms answer, I have changed my own architecture to (still) use SockJS in the front-end, but with RabbitMQ using the Web-STOMP plugin on the back-end. That way,

    1. the client still uses sockjs-client with stomp-websocket in the browser,
    2. all of the back-end code to handle front-end multiplexing could be removed,
    3. the (RabbitMQ-based) back-end is fast and really robust,
    4. the client POSTs a message to the back-end app server, which in turn
      1. persists the message in the database, and
      2. acts as a client of the RabbitMQ server and either (1) broadcasts the message to all connected users, or if the message is private (2) sends the message to a single recipient over the recipient's own private channel, as sketched out in the above suggested solution.

    The whole new solution is placed behind HAProxy which terminates HTTPS and SSL/TLS-encrypted WebSocket connections.