Храповик PHP WAMP-React / ZeroMQ-специфичная передача пользователя

Примечание: это не аналогично этот вопрос С MessageComponentInterface. Я использую WampServerInterface вместо этого, поэтому этот вопрос относится именно к этой части. Мне нужен ответ с примерами кода и объяснением, поскольку я вижу, что это полезно для других в будущем.

попытка зацикленных толчков для отдельных пользователей

я использую часть WAMP храповика и ZeroMQ, и я в настоящее время имейте рабочую версию учебник по интеграции push.

Я пытаюсь выполнить следующее:

  • сервер zeromq запущен и работает, готов регистрировать подписчиков и отписывающихся
  • пользователь подключается в своем браузере по протоколу websocket
  • A цикл запускается, который отправляет данные в конкретные кто просил его
  • когда пользователь отключается, цикл для данных этого пользователя остановлен

у меня есть пункты (1) и (2), однако проблема у меня с третьим:

во-первых: как я могу отправлять данные только каждому конкретному пользователю? Broadcast отправляет его всем, если только "темы"не становятся индивидуальными идентификаторами пользователей?

во-вторых: у меня большая проблема безопасности. если я отправляю, какой идентификатор пользователя хочет подписаться на стороне клиента, который похоже, мне нужно, тогда пользователь может просто изменить переменную на идентификатор другого пользователя, и вместо этого возвращаются их данные.

в-третьих: мне нужно запустить отдельный PHP-скрипт содержащий код для zeromq, чтобы начать цикл. Я не уверен, что это лучший способ сделать это, и я бы предпочел, чтобы это работало полностью в кодовой базе, а не в отдельном файле php. Это важная область, в которой мне нужно разобраться.

следующий код показывает, что у меня сейчас есть.

сервер, который работает только из консоли

я буквально типа php bin/push-server.php для выполнения этого. Подписки и подписки ООН выводятся на этот терминал для целей отладки.

$loop   = ReactEventLoopFactory::create();
$pusher = Pusher;

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

$webSock = new ReactSocketServer($loop);
$webSock->listen(8080, '0.0.0.0'); // Binding to 0.0.0.0 means remotes can connect
$webServer = new RatchetServerIoServer(
    new RatchetWebSocketWsServer(
        new RatchetWampWampServer(
            $pusher
        )
    ),
    $webSock
);

$loop->run();

толкатель, который отправляет данные через websockets

Я опустил бесполезный материал и сосредоточился на onMessage() и onSubscribe() методы.

public function onSubscribe(ConnectionInterface $conn, $topic) 
{
    $subject = $topic->getId();
    $ip = $conn->remoteAddress;

    if (!array_key_exists($subject, $this->subscribedTopics)) 
    {
        $this->subscribedTopics[$subject] = $topic;
    }

    $this->clients[] = $conn->resourceId;

    echo sprintf("New Connection: %s" . PHP_EOL, $conn->remoteAddress);
}

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

    var_dump($entryData);

    if (!array_key_exists($entryData['topic'], $this->subscribedTopics)) {
        return;
    }

    $topic = $this->subscribedTopics[$entryData['topic']];

    // This sends out everything to multiple users, not what I want!!
    // I can't send() to individual connections from here I don't think :S
    $topic->broadcast($entryData);
}

скрипт для начала использования вышеуказанного кода толкателя в петля

это моя проблема - это отдельный файл php, который, надеюсь, может быть интегрирован в другой код в будущем, но в настоящее время я не уверен, как использовать это правильно. Должен ли я получить идентификатор пользователя из сеанса? Мне все еще нужно отправить его с клиентской стороны...

// Thought sessions might work here but they don't work for subscription
session_start();
$userId = $_SESSION['userId'];

$loop   = ReactEventLoopFactory::create();

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

$i = 0;
$loop->addPeriodicTimer(4, function() use ($socket, $loop, $userId, &$i) {

   $entryData = array(
       'topic'     => 'subscriptionTopicHere',
       'userId'    => $userId
    );
    $i++;

    // So it doesn't go on infinitely if run from browser
    if ($i >= 3)
    {
        $loop->stop();
    }

    // Send stuff to the queue
    $socket->send(json_encode($entryData));
});

наконец, на стороне клиента js подписаться с

$(document).ready(function() { 

    var conn = new ab.Session(
        'ws://localhost:8080' 
      , function() {            
            conn.subscribe('topicHere', function(topic, data) {
                console.log(topic);
                console.log(data);
            });
        }
      , function() {          
            console.warn('WebSocket connection closed');
        }
      , {                       
            'skipSubprotocolCheck': true
        }
    );
});

вывод

вышеуказанное работает, но мне действительно нужно выяснить следующий:

  • как я могу отправлять отдельные сообщения отдельным пользователям? Когда они посещают страницу, которая запускает соединение websocket в JS, должен ли я также запускать скрипт, который пихает вещи в очередь в PHP (zeromq)? Это то, что я сейчас делаю вручную, и это просто неправильно.

  • подписываясь на пользователя из JS, не может быть безопасно захватить идентификатор пользователя из сеанса и отправить его из клиентский. Это может быть подделкой. Скажите, пожалуйста, есть более простой способ, и если да, то как?

2 ответов


Примечание: мой ответ здесь не включить ссылки на ZeroMQ, так как я больше не использую его. Тем не менее, я уверен, что вы сможете выяснить, как использовать ZeroMQ с этим ответом, если вам нужно.

использовать JSON

прежде всего,Websocket RFC и WAMP Spec укажите, что тема для подписки должна быть строка. Я немного жульничаю, но ... все еще придерживаясь спецификации: вместо этого я передаю JSON.

{
    "topic": "subject here",
    "userId": "1",
    "token": "dsah9273bui3f92h3r83f82h3"
}

JSON по-прежнему является строкой, но она позволяет мне передавать больше данных вместо "темы", и для PHP просто сделать json_decode() на другом конце. Конечно, вы должны проверить, что вы действительно получаете JSON, но это зависит от вашей реализации.

  • темы

тема тему пользователь подписывается на. Вы используете это, чтобы решить, какие данные вы передаете пользователю.

  • UserId

очевидно идентификатор пользователя. Вы должны убедитесь, что этот пользователь существует и может подписаться, используя следующую часть:

  • маркер

это должно быть использовать случайно сгенерированный токен, сгенерированный в вашем PHP и переданный Переменной JavaScript. Когда я говорю "одно использование", я имею в виду каждый раз, когда вы перезагружаете страницу (и, по расширению, по каждому HTTP-запросу), ваша переменная JavaScript должна иметь новый токен. Этот маркер должен храниться в базе данных по идентификатору пользователя.

затем, как только запрос websocket сделан, вы сопоставляете токен и идентификатор пользователя с теми, кто в базе данных, чтобы убедиться, что пользователь действительно тот, о ком они говорят, и они не возились с JS переменная.

Примечание: в обработчике событий вы можете использовать $conn->remoteAddress чтобы получить IP-адрес соединения, поэтому, если кто-то пытается подключиться злонамеренно, вы можете заблокировать их (войти в них или что-то еще).

почему это работает?

это работает, потому что каждый раз, когда приходит новое соединение,уникальный маркер гарантирует, что ни один пользователь не будет иметь доступа к чужим данным подписки.

в Сервер

вот что я использую для запуска цикла и обработчика событий. Я создаю цикл, делая все создание объекта стиля декоратора и передавая мой EventHandler (к которому я скоро приду) с циклом там тоже.

$loop = Factory::create();

new IoServer(
    new WsServer(
        new WampServer(
            new EventHandler($loop) // This is my class. Pass in the loop!
        )
    ),
    $webSock
);

$loop->run();

Обработчик События

class EventHandler implements WampServerInterface, MessageComponentInterface
{
    /**
     * @var \React\EventLoop\LoopInterface
     */
    private $loop;

    /**
     * @var array List of connected clients
     */
    private $clients;

    /**
     * Pass in the react event loop here
     */
    public function __construct(LoopInterface $loop)
    {
        $this->loop = $loop;
    }

    /**
     * A user connects, we store the connection by the unique resource id
     */
    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients[$conn->resourceId]['conn'] = $conn;
    }

    /**
     * A user subscribes. The JSON is in $subscription->getId()
     */
    public function onSubscribe(ConnectionInterface $conn, $subscription)
    {
        // This is the JSON passed in from your JavaScript
        // Obviously you need to validate it's JSON and expected data etc...
        $data = json_decode(subscription->getId());

        // Validate the users id and token together against the db values

        // Now, let's subscribe this user only
        // 5 = the interval, in seconds
        $timer = $this->loop->addPeriodicTimer(5, function() use ($subscription) {
            $data = "whatever data you want to broadcast";
            return $subscription->broadcast(json_encode($data));
        });

        // Store the timer against that user's connection resource Id
        $this->clients[$conn->resourceId]['timer'] = $timer;
    }

    public function onClose(ConnectionInterface $conn)
    {
        // There might be a connection without a timer
        // So make sure there is one before trying to cancel it!
        if (isset($this->clients[$conn->resourceId]['timer']))
        {
            if ($this->clients[$conn->resourceId]['timer'] instanceof TimerInterface)
            {
                $this->loop->cancelTimer($this->clients[$conn->resourceId]['timer']);
            }
        }

        unset($this->clients[$conn->resourceId]);
    }

    /** Implement all the extra methods the interfaces say that you must use **/
}

это в основном все. Основные моменты здесь:

  • уникальный токен, идентификатор пользователя и идентификатор соединения обеспечивают уникальную комбинацию, необходимую для обеспечения одного пользователя не удается увидеть данные другого пользователя.
  • уникальный токен означает, что если один и тот же пользователь открывает другую страницу и запрашивает подписку, у них будет свой собственный идентификатор соединения + комбинация токенов, чтобы один и тот же пользователь не имел двойных подписок на одной странице (в основном, каждое соединение имеет свои собственные индивидуальные данные).

расширение

вы должны убедиться, что все данные проверены, а не попытка взлома, прежде чем что-либо с ним делать. Журнал все соединения попытки использовать что-то вроде монолог, и настроить переадресацию электронной почты, если происходят какие-либо критические (например, сервер перестает работать, потому что кто-то ублюдок и пытается взломать ваш сервер).

Закрытие Пунктов

  • Проверить Все. Я не могу подчеркнуть это достаточно. Ваш уникальный токен, который изменяется при каждом запросе, -важно.
  • помните, если вы повторно генерируете токен на каждом HTTP запрос, и вы делаете запрос POST перед попыткой подключения через websockets, вам придется передать повторно сгенерированный токен на ваш JavaScript перед попыткой подключения (в противном случае ваш токен будет недействительным).
  • журнал все. Ведите учет всех, кто подключается, спрашивает, какая тема, и отключается. Монолог отлично подходит для этого.

чтобы отправить определенным пользователям, вам нужен шаблон маршрутизатора-дилера вместо pub-SUB. Это объясняется в руководстве, в главе 3. Безопасность, если вы используете ZMQ v4.0, обрабатывается на уровне провода, поэтому вы не видите его в приложении. Это все еще требует некоторой работы, если вы не используете привязку CZMQ, которая предоставляет структуру аутентификации (zauth).

в основном, для аутентификации вы устанавливаете обработчик на inproc: / / zeromq.убить.01, и отвечать на запросы через сокет. Гуглить ZeroMQ ZAP для RFC; существует также тестовый случай в ядре libzmq/tests / test_security_curve.программа СРР.