是否有一种干净的方法来设置websocket与symfony 2.6

时间:2015-05-17 17:45:27

标签: javascript ajax symfony websocket

我使用symfony 2开发了一个网站。 到现在为止它还没有使用ajax。当您发布新评论时,页面已刷新。

我添加了一个ajax图层,因此表单无需刷新即可提交。

每当我发布一个新帖子时,我希望所有在线用户都能看到这篇帖子在时间轴上收到它。

这就是我现在所做的:

我创建了一个事件NewPostAdded和一个订阅呈现给html的帖子(希望我将它发送给客户端,客户端将$(&#39; .timeline&#39;)。prepend(post))< / p>

我正在寻找一种方法来实现一个将启动websocket服务器的symfony2命令。我的订阅者将能够将post +数据(是公共的吗?还是允许查看它的用户的id列表)推送到此服务器。这个服务器将有一个在线用户列表,如果该帖子是公开的,它将推送给其他人,它将把它推送给正确的在线用户。

我在使用symfony 2.1 + redis + nodejs + socket.io之前已经这样做了,但它显然不可维护,因为这个网站不是针对大量观众,所以我想保持简单。

以下是我的2个问题:

  • 是否有一个简单的捆绑包提供一个简单的方法来编写带有收入事件监听器的服务器(从symfony接收帖子)和事件发送者(向用户发送帖子),添加事件监听器客户端(准备使用资产添加以便能够编写客户端代码)来响应服务器消息吗?

  • 有没有办法在客户端使用除用户ID之外的其他内容来验证&#39; websocket服务器上的用户,以避免用户更改客户端代码中的id以接收他们不应该查看的帖子?

1 个答案:

答案 0 :(得分:2)

是的,您可以使用php web socket Ratchet&#34; http://socketo.me/&#34;,您可以将其用作编辑器包https://packagist.org/packages/cboden/ratchet

您应该创建一个服务应用程序,然后您应该创建一个运行套接字应用程序的控制台命令。

而不是userId你应该生成一个秘密令牌,比如userId的md5哈希+一些用于检查套接字连接是否可信的密钥。

<强>更新

composer.json

...
"cboden/ratchet": "0.3.*",
...

然后你应该创建一个新的包,让我们说&#34; WebSocketBundle&#34;

  1. 创建服务应用程序 WebSocketBundle /网页套接字/ WebSocketApplication.php
  2.     namespace MyApp\WebSocketBundle\WebSocket;
    
        use Ratchet\ConnectionInterface;
        use Ratchet\MessageComponentInterface;
    
        class WebSocketApplication implements MessageComponentInterface
        {
            protected $container;
            protected $clients;
            protected $redis;
    
            public function __construct($container)
            {
                $this->clients = [];
                $this->container = $container;
                $this->redis = $container->get('snc_redis.something'); // your redis service
            }
    
            public function onMessage(ConnectionInterface $from, $msg)
            {
                $messageData = $this->decodeJSONAndCheckMessage($from, $msg);
    
                // here you must pass a token in messageData and implement own function to check is the token valid 
                $loginResult = $this->userLogin($from, $messageData);
    
                if ($loginResult instanceof Success) { // my custom success class message
                    $this->handleMessage($from, $messageData);
                } else {
                    $this->onError($from, new \Exception('Cannot login a user.'));
                }
            }
    
            // some strategy wrapper
            private function handleMessage($from, $messageData)
            {
                $message = $messageData->message;
                if (method_exists($this, $message)) {
                    try {
                        $this->$message($from, $messageData);
                    } catch (Exception $ex) {
                        $this->onError($from, $ex);
                    }
                } else {
                    $this->onError($from, new \Exception(sprintf('Unknown method "%s"', $message)));
                }
            }
    
            // you can use here post new message action
            private function eventStartSomething($from, $messageData)
            {
                if (!$messageData->somethingId) {
                    $this->onError($from, new \Exception('Bad parameters'));
                    return;
                }
                $scope = [];
                $scope['clients'][$from->resourceId] = null;
    
                // I need socket for something only limited amount of time, you can implement here own logic
                $this->redis->setex($messageData->somethingId, 600, serialize($scope));
    
                $this->clients[$from->resourceId]['scope'] = $messageData->eventId;
                $this->logMessage($from, 'started new something with Id: ' . $messageData->somethingId);
                $from->send($this->getResultOKMessage());
            }
    
            private function eventGetSomething($from, $messageData)
            {
                $scopeKey = $this->redis->get($messageData->somethingId);
                if (!$scopeKey) {
                    $this->onError($from, new \Exception('Bad or expired something ' . $messageData->somethingId));
                    return;
                }
                if (!$this->checkForClientInScope($from->resourceId, $messageData->eventId)) {
                    if ($this->assignClientToScope($from->resourceId, $messageData->eventId)) {
                        $this->sendMessageToScope($from, $messageData->eventId, $this->getScopeWatchMessage($from, $messageData->eventId));
                        $from->send($this->getScopeWatchMessage($from, $messageData->eventId));
                    }
                }
            }
    
            private function assignClientToScope($clienResourseId, $scopeId)
            {
                $result = false;
                $scopeKey = $this->redis->get($scopeId);
                if (!$scopeKey) {
                    return $result;
                }
                $scope = unserialize($scopeKey);
                if (!array_key_exists($clienResourseId, $scope['clients'])) {
    
                // I need socket for something only limited amount of time, you can implement here own logic
                    $this->redis->setex($scopeId, 600, serialize($scope));
    
                    if (array_key_exists($clienResourseId, $this->clients)) {
                        $this->clients[$clienResourseId]['scope'] = $scopeId;
                    }
                    $result = true;
                }
                return $result;
            }
    
            private function sendMessageToScope($from, $scopeId, $message)
            {
                $scopeKey = $this->redis->get($scopeId);
                if (!$scopeKey) {
                    $this->onError($from, new \Exception('Bad or expired event ' . $scopeId . ' for sending message'));
                    return;
                }
                $scope = unserialize($scopeKey);
    
                foreach ($scope['clients'] as $clientResourceId => $remoteAddress) {
                    if (array_key_exists($clientResourceId, $this->clients) &&
                            $this->clients[$clientResourceId]['connection'] != $from) {
                        $this->clients[$clientResourceId]['connection']->send($message);
                    }
                }
            }
    
            public function onClose(ConnectionInterface $conn)
            {
                if (isset($this->clients[$conn->resourceId]['scope'])) {
                    $scopeId = $this->clients[$conn->resourceId]['scope'];
                    $this->removeClientFromScope($conn->resourceId);
                    $this->sendMessageToScope($conn, $scopeId, $this->getScopeWatchMessage($conn, $scopeId));
                }
                unset($this->clients[$conn->resourceId]);
                $this->logMessage($conn, 'Connection closed.');
            }
    
            public function onError(ConnectionInterface $conn, \Exception $e)
            {
                echo date("Y-m-d H:i:s") . ":" . "WebSocket error::" . $e->getMessage() . " resourceId:" . $conn->resourceId . ". remoteAddress:" . $conn->remoteAddress . "\n";
                $conn->send($this->getErrorMessage($e->getMessage()));
            }
    
            public function onOpen(ConnectionInterface $conn)
            {
                $this->clients[$conn->resourceId]['connection'] = $conn;
                $this->logMessage($conn, 'New connection.');
            }
        }
    
    1. 控制台命令:
    2. use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
      use Symfony\Component\Console\Input\InputInterface;
      use Symfony\Component\Console\Output\OutputInterface;
      use Symfony\Component\Console\Input\InputOption;
      use Ratchet\Server\IoServer;
      use Ratchet\Http\HttpServer;
      use Ratchet\WebSocket\WsServer;
      use Snc\RedisBundle\Session\Storage\Handler\RedisSessionHandler;
      use Ratchet\Session\SessionProvider;
      use MyApp\WebSocketBundle\WebSocket\WebSocketApplication;
      
      class ListenCommand extends ContainerAwareCommand
      {
          protected function configure()
          {
              $this->setName('myapp:websocket:listen')
                  ->setDescription('Listen for websocket requests ')
                  ->addOption('port', 'p', InputOption::VALUE_REQUIRED, 'The port to listen on', 8000)
                  ->addOption('interface', 'i', InputOption::VALUE_REQUIRED, 'The interface to listen on', '0.0.0.0');
          }
      
          protected function execute(InputInterface $input, OutputInterface $output)
          {
              $redis = $this->getContainer()->get('snc_redis.default');
              $application = new WebSocketApplication($this->getContainer());
              $server = IoServer::factory(
                  new HttpServer(
                      new WsServer(
                          new SessionProvider(
                              $application,
                              new RedisSessionHandler($redis)
                          )
                      )
                  ),
                  $input->getOption('port'),
                  $input->getOption('interface')
              );
              echo "Listening on: ".$input->getOption('interface').":".$input->getOption('port')."\n";
              $server->run();
      
          }
      }