Я разрабатываю большой проект 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