Я разрабатываю большой проект Symfony 3 со слушателями. Я попытался создать сервер уведомлений для пользователей с веб-сокетом.

Моя проблема: отправлять уведомления подключенным пользователям в режиме реального времени. Поэтому мне нужно отправлять уведомления конкретному аутентифицированному пользователю.

Решение

Сервер WebSocket

Я использую Ratchet для обработки веб-сервера: http://socketo.me/

Установка довольно проста:

composer require cboden/ratchet

И создайте новый класс, реализующий MessageComponentInterface.

В symfony я создаю процесс командной строки для создания сервера websocket:

<?php
namespace NotificationsBundle\Command;


use NotificationsBundle\Server\Notification;
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ServerCommand extends ContainerAwareCommand
{

    /**
     * Configure a new Command Line
     */
    protected function configure()
    {
        $this
            ->setName('Project:notification:server')
            ->setDescription('Start the notification server.');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {

        $server = IoServer::factory(new HttpServer(
            new WsServer(
                new Notification($this->getContainer())
            )
        ), 8080);

        $server->run();

    }

}

Мой серверный класс выглядит так:

<?php
namespace NotificationsBundle\Server;



use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use UserCrmBundle\Entity\User;

class Notification implements MessageComponentInterface
{

    protected $connections = array();

    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * A new websocket connection
     *
     * @param ConnectionInterface $conn
     */
    public function onOpen(ConnectionInterface $conn)
    {
        $this->connections[] = $conn;
        $conn->send('..:: Hello from the Notification Center ::..');
        echo "New connection \n";
    }

    /**
     * Handle message sending
     *
     * @param ConnectionInterface $from
     * @param string $msg
     */
    public function onMessage(ConnectionInterface $from, $msg)
    {
        $messageData = json_decode(trim($msg));
        if(isset($messageData->userData)){
            //1st app message with connected user
            $token_user = $messageData->userData;

            //a user auth, else, app sending message auth
                echo "Check user credentials\n";
                //get credentials
                $jwt_manager = $this->container->get('lexik_jwt_authentication.jwt_manager');
                $token = new JWTUserToken();
                $token->setRawToken($token_user);
                $payload = $jwt_manager->decode($token);

                //getUser by email
                if(!$user = $this->getUserByEmail($payload['username'])){
                    $from->close();
                }
                echo "User found : ".$user->getFirstname() . "\n";
                $index_connection = $payload['username'];

            $all_connections = $this->connections;
            foreach($all_connections as $key => $conn){
                if($conn === $from){
                    $this->connections[$index_connection] = $from;
                    $from->send('..:: Connected as '.$index_connection.'  ::..');
                    unset($this->connections[$key]);
                    break;
                } else {
                    continue;
                }

            }
        } else {
            //error
            $from->send("You're not able to do that!");
        }

    }

    /**
     * A connection is closed
     * @param ConnectionInterface $conn
     */
    public function onClose(ConnectionInterface $conn)
    {
        foreach($this->connections as $key => $conn_element){
            if($conn === $conn_element){
                unset($this->connections[$key]);
                break;
            }
        }
    }

    /**
     * Error handling
     *
     * @param ConnectionInterface $conn
     * @param \Exception $e
     */
    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        $conn->send("Error : " . $e->getMessage());
        $conn->close();
    }


    /**
     * Get user from email credential
     *
     * @param $email
     * @return false|User
     */
    protected function getUserByEmail($email)
    {
        if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
            return false;
        }

        $em = $this->container->get('doctrine')->getManager();
        $repo = $em->getRepository('UserCrmBundle:User');

        $user = $repo->findOneBy(array('email' => $email));

        if($user && $user instanceof User){
            return $user;
        } else {
            return false;
        }

    }

}

Объяснение :

Мы не можем отправить какой-либо настраиваемый заголовок на сервер веб-сокета. Итак, вам нужно найти способ аутентифицировать пользователей. Мой пользователь аутентифицирован в моем API с помощью JWToken благодаря пакету lexik / jwt-authentication-bundle. Когда пользователь подключается к серверу websocket, ему нужно отправить мне сообщение со своим токеном.

Я назначаю веб-сокет электронной почте пользователя после аутентификации. И теперь я могу отправить сообщение конкретному пользователю.

На стороне клиента:

Вам просто нужно следовать этому примеру:

//lancement serveur chat
function NotifServer(){
   notif = new WebSocket("ws://IP_SERVER:8080");
   
   notif.onmessage = function (event) {
   console.log(event.data);
      $('.content').append('<p>'+ event.data +'</p>');
      
     }
      
   notif.onopen = function() {
         $('.content').append('<p> >>> Connected</p>');
       var token_user = *TOKEN_USER*;
         var authElements = "{\"userData\":\""+token_user+"\"}";
       notif.send(authElements);
     }

   notif.onerror = function(error) {
      $('.content').append('<p> >>> Error...</p>');
      //alert('error');
     }
      
     
}

NotifServer();

Вот и все!

Для запуска вашего сервера:

php bin/console Project:notification:server

Чтобы развернуть его в производственной среде, вам нужно что-то, чтобы проверить, жив ли сервер уведомлений. Для этого можно использовать supervisord.

Простое руководство по использованию Supervisord для сервера NodeJS. Вы можете использовать его для создания демона сервера Websocket: https://serversforhackers.com/monitoring-processes-with-supervisord