Send user ID from browser to websocket server while opening connection
Asked Answered
P

3

7

Before asking this question, I did my best by reading severel questions on SO (tagged Ratchet and dealing with similar issues but to no avail. I even asked a question which received no attention and I therefore deleted it to write another one (that hopefully is more clear).

My final goal is to build a one-to-one private chat application using Ratchet. Everything is working fine except that I can't send message to a specific user.

Every logged in user connects to the websocket server while accessing secured area of website:

$(document).ready(function() { 

    var conn = new WebSocket('ws://localhost:8080');
        conn.onopen = function(e) {
            console.log("Connection established!");

            // Here I need to send the logged in user_id to websocket server
            // and get it in onOpen method so that I can index my array 
            // of connections with user_id instead of
            //$connection->ResourceId, I explain more below

        };

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

});

When a user writes a message in the chat box, the message is sent via AJAX to web server then pushed to Websocket using ZeroMQ. In the controller:

// Persistence of Message(message_id, sender_id, receiver_id, message_text)
                .....

                $context = new \ZMQContext();
                $socket = $context->getSocket(\ZMQ::SOCKET_PUSH, 'my pusher');
                $socket->connect("tcp://localhost:5555");

                $pushData = array(
                       'receiver_id' => $receiver_id,
                       'sender_id'  => $user->getId(),
                       'message'  => $message->getMessageText(),
                    );
                $socket->send(json_encode($pushData));

So at the end, my websocket server is able to know which is the id of receiver using the JSON. But how will he know which is the connection of that user? In other words, I need to store websocket connections in an array that is indexed by the user id.

<?php
namespace RealTime;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Wamp\WampServerInterface;

class Pusher implements WampServerInterface, MessageComponentInterface{

    private $clients;

    public function onOpen(ConnectionInterface $conn) {

        $this->clients[$conn->resourceId] = $conn;
        // I need here to get the user_id received from browser while opening connection
    }

    public function onMessageEntry($entry) {
        $entryData = json_decode($entry, true);

        //This is not what I need (It sends to all users in array)
        foreach ($this->clients as $key => $client) {

        $client->send($entryData['message']); 
        }
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        echo $msg; 
    }
}

And the websocket server:

  <?php
        require dirname(__DIR__) . '/vendor/autoload.php';
        use RealTime\Pusher;

        $loop   = React\EventLoop\Factory::create();
        $pusher = new Pusher;

        $context = new React\ZMQ\Context($loop);
        $pull = $context->getSocket(ZMQ::SOCKET_PULL);
        $pull->bind('tcp://127.0.0.1:5555'); 
        $pull->on('message', array($pusher, 'onMessageEntry'));


        $webSock = new React\Socket\Server($loop);
        $webSock->listen(8080, '0.0.0.0'); 
        $webServer = new Ratchet\Server\IoServer(
            new Ratchet\Http\HttpServer(
                new Ratchet\WebSocket\WsServer(
                    new Ratchet\Wamp\WampServer(
                        $pusher
                    )
                )
            ),
            $webSock
        );
        $loop->run();

        ?>

Questions:

  1. How to send the logged in user_id from client side while opening connection.I need to have the value in websocket server so that I can index my array of clients with it ($client[user_id]=$conn instead of $client[recourceId]=$conn). I tried the javascript function send but I don't know where to receive the sent data (even onMessage is not printing anything).

  2. Why the onMessage method is not executing even MessageComponentInterface implemented (Is it because I have onMessageEntry method + $pull->on('message', array($pusher, 'onMessageEntry')); line of code?

Thank you.

Pristine answered 11/4, 2015 at 9:3 Comment(0)
P
2

This is what I found and any suggestions to enhance this solution are welcome.

One can use the Ratchet SessionProvider. This will require using one of the Symfony Custom Session handlers as indicated. I use in the following code the PdoSessionHandler.

<?php
    require dirname(__DIR__) . '/vendor/autoload.php';

    use YourDirectory\Pusher;
    use Symfony\Component\HttpFoundation\Session\Storage\Handler;

    use \Ratchet\Session\SessionProvider;

    $pusher = new Pusher;

    $pdo = new PDO('mysql:host=localhost;dbname=community', 'root', null);

    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    //This info is related to you db
    $dbOptions = array(
        'db_table'      => 'session',
        'db_id_col'     => 'sess_id',
        'db_data_col'   => 'sess_data',
        'db_time_col'   => 'sess_time',);

        $loop   = \React\EventLoop\Factory::create();
        $context = new \React\ZMQ\Context($loop);
        $pull = $context->getSocket(\ZMQ::SOCKET_PULL);
        $pull->bind('tcp://127.0.0.1:5555');
        $pull->on('message', array($pusher, 'onMessageEntry'));

        $webSock = new React\Socket\Server($loop);
        $webSock->listen(8080, '0.0.0.0'); // Binding to 0.0.0.0 means remotes can connect
        $webServer = new Ratchet\Server\IoServer(
            new Ratchet\Http\HttpServer(
                new Ratchet\WebSocket\WsServer(
                    new SessionProvider(
                        new Ratchet\Wamp\WampServer(
                            $pusher
                        ),new Handler\PdoSessionHandler($pdo,$dbOptions)
                    )
                )
            ),
            $webSock
        );

        $loop->run();
    ?>

Then my stub class will become:

   public function onOpen(ConnectionInterface $conn) {  
        $this->clients[$conn->Session->get('current_user_id')] = $conn;
    }

public function onMessageEntry($entry) {

            $entryData = json_decode($entry, true);
            $ReceiverConnection=$this->clients[$entryData['receiver_id']];
            $ReceiverConnection->send($entryData['message']);                  
        }

But before, I have added the user id to the session in Web server (in the controller that returns the initial page)

$user = $this->getUser();
$request->getSession()->set('current_user_id', $user->getId()); 

PS:

  1. Moving to PdoSessionHandler can be done by implementing this (Symfony).

  2. I still can't answer 2 but all the logic that can be put onMessage is now moved to onMessageEntry which satisfies temporarly the needs.

Pristine answered 12/4, 2015 at 21:47 Comment(0)
C
3

As an alternative way to make the association between your clientConnection and his ID you need to send a message using websockets just after opening the connection to your websocket server this message will contain your user id you will use it to index his connection object by his ID in your array.

For the second question as I know the default websocket implementation is not working properly specialy with pubsub protocol you need to use a websocket library for that I suggest to use AutobahnJS it's a good websocket library with a lot of wonderfull features.

Creator answered 13/4, 2015 at 20:31 Comment(4)
Thank you Anas for your usual contribution. your alternative was the first thing i tried. I did with javascript while openening connection using send function, but I don't find a way to get the value in the Pusher class. Your solution is what I really look for and SessionProvider were used because I just failed. Where to get the sent id in websocket server?\Pristine
You didn't receive data because the default implementation of websockets in web browsers is not working properly with pubsub protocole try to use it with AuthobahnJS it have an implementation of pubsub protocole.Creator
look at this, I didn't use PDOSessionProvider because the session don't have the same lifecycle as your connection object take a look at this groups.google.com/forum/#!topic/ratchet-php/LNCsDIugvBgCreator
okey I see what you mean. I will try with AuthobanhnJS and give a feedback. thanks for google groups linkPristine
S
3

Actually on my last try, I gave up on PHP WebSocket (it was so complicated to make this work) and started using SocketIO with nodeJS that solved my entire problem and could give me a functionnal simple Chat system.

Saprophyte answered 21/6, 2015 at 21:43 Comment(1)
Thank you for sharing your experience with the whole community.Pristine
P
2

This is what I found and any suggestions to enhance this solution are welcome.

One can use the Ratchet SessionProvider. This will require using one of the Symfony Custom Session handlers as indicated. I use in the following code the PdoSessionHandler.

<?php
    require dirname(__DIR__) . '/vendor/autoload.php';

    use YourDirectory\Pusher;
    use Symfony\Component\HttpFoundation\Session\Storage\Handler;

    use \Ratchet\Session\SessionProvider;

    $pusher = new Pusher;

    $pdo = new PDO('mysql:host=localhost;dbname=community', 'root', null);

    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    //This info is related to you db
    $dbOptions = array(
        'db_table'      => 'session',
        'db_id_col'     => 'sess_id',
        'db_data_col'   => 'sess_data',
        'db_time_col'   => 'sess_time',);

        $loop   = \React\EventLoop\Factory::create();
        $context = new \React\ZMQ\Context($loop);
        $pull = $context->getSocket(\ZMQ::SOCKET_PULL);
        $pull->bind('tcp://127.0.0.1:5555');
        $pull->on('message', array($pusher, 'onMessageEntry'));

        $webSock = new React\Socket\Server($loop);
        $webSock->listen(8080, '0.0.0.0'); // Binding to 0.0.0.0 means remotes can connect
        $webServer = new Ratchet\Server\IoServer(
            new Ratchet\Http\HttpServer(
                new Ratchet\WebSocket\WsServer(
                    new SessionProvider(
                        new Ratchet\Wamp\WampServer(
                            $pusher
                        ),new Handler\PdoSessionHandler($pdo,$dbOptions)
                    )
                )
            ),
            $webSock
        );

        $loop->run();
    ?>

Then my stub class will become:

   public function onOpen(ConnectionInterface $conn) {  
        $this->clients[$conn->Session->get('current_user_id')] = $conn;
    }

public function onMessageEntry($entry) {

            $entryData = json_decode($entry, true);
            $ReceiverConnection=$this->clients[$entryData['receiver_id']];
            $ReceiverConnection->send($entryData['message']);                  
        }

But before, I have added the user id to the session in Web server (in the controller that returns the initial page)

$user = $this->getUser();
$request->getSession()->set('current_user_id', $user->getId()); 

PS:

  1. Moving to PdoSessionHandler can be done by implementing this (Symfony).

  2. I still can't answer 2 but all the logic that can be put onMessage is now moved to onMessageEntry which satisfies temporarly the needs.

Pristine answered 12/4, 2015 at 21:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.