[ИНТЕРЕСНО] Server-Sent Events - Уведомления с сервера в браузер в реальном времени

Всем привет! Искал простой способ отправлять уведомления о смене статуса заказа в браузер пользователя. Из вариантов были websocket и сторонние сервисы push-уведомлений. С websocket я разобраться не смог, пробовал запустить workerman на shared-хостинге, но не вышло. Сторонние сервисы вроде sendpulse или comet-сервера не устраивают, потому что они сторонние, их оставлю на крайний случай.

И совершенно случайно я наткнулся на Server-Sent Events. Всё с ними хорошо, кроме одного, опытным путём я понял, что он должен быть запущен всегда, т.е. нельзя запустить его из другого скрипта и передать параметры. Или можно, если кто-то знает как, напишите в комментариях.

Так или иначе я его для своих задач приспособил. Вопрос такой: насколько это будет нагружать сервер и, если никто не знает, то подскажите как можно провести тест под нагрузкой? Предполагается от 1 до 5 тысяч клиентов одновременно.

Собственно кода немного. Плагин на событие msOnCreateOrder, который записывает в cookie номер заказа, который в дальнейшем используется как имя события, на которое подписан клиент.
<?php
$num = str_replace('/', '-', $msOrder->get('num')); // со слэшем в куки нельзя
setcookie('order_num', $num, time()+3600*6, '/', $modx->getOption('http_host'));

Ещё один плагин на событие msOnChangeOrderStatus записывает статус заказа в файл, который потом мы проверять будем. А в файл пишу, потому что не придумал как иначе передать данные. Если делать запросы в БД из основного скрипта, так он срабатывает каждые 10 секунд, при 1000 клиентов боюсь что-нибудь ляжет, но это не точно и проверять не хочется.
<?php
$output = array();
$fileData = array();
$statusObj = $order->getOne('Status');
$path = $_SERVER['DOCUMENT_ROOT'].'/assets/notifydata.json';

$num = str_replace('/', '-', $order->get('num'));
$output[$num]['status'] = $statusObj->get('name');

if(file_exists($path)){
    $fileData = json_decode(file_get_contents($path), 1); 
    if($fileData){
        $output = array_merge($fileData,$output);
    }
}

$data = json_encode($output,JSON_UNESCAPED_UNICODE);
$result = file_put_contents($path, $data, LOCK_EX);

Основной скрипт выглядит так
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo "retry: 10000" . PHP_EOL;
// Функция отправки сообщения
function sendMsg()
{
    $path = $_SERVER['DOCUMENT_ROOT'].'/assets/notifydata.json';
    $event = trim($_COOKIE['order_num']);
    $id = time();

    if(file_exists($path)){
        $fileData = json_decode(file_get_contents($path), 1);
        $msg = $fileData[$event]['status'];
    }
    if($msg){
        echo "event:" . $event . PHP_EOL;
        echo "id: $id" . PHP_EOL;
        echo "data: $msg" . PHP_EOL;
        echo PHP_EOL;
        ob_flush();
        flush();
        die();
    }
}

while(true) {   
    // Отправляем полученное время в сообщении
    sendMsg();
}
Взял его отсюда и немного подправил под свои нужды.

На тот случай если кто-то захочет протестировать у себя, приведу ещё код шаблона, там самое главное это js.
<div id="messageLog">[Лог сообщений:]</div>
<div id="timeDisplay">[Время не получено]</div>
<div id="controls">
    <button onclick="startListening()">Начать прослушивание</button>
    <button onclick="stopListening()">Остановить прослушивание</button>
</div>
<style>
    body {
        font-family: Verdana;
        font-size: 12px;
    }

    #messageLog {
        width: 900px;
        height: 230px;
        border: darkgray 2px solid;
        border-radius: 5px;
        margin: 20px;
        padding: 10px;
        overflow: scroll;
        overflow-x: hidden;
    }

    #timeDisplay {
        color: darkblue;
        font-size: 30px;
        width: 400px;
        font-weight: bold;
        border: black 1px solid;
        border-radius: 10px;
        margin: 10px 20px 10px 20px;
        padding: 10px;
        background-color: #FBF3CB;
    }

    button {
        padding: 8px;
        margin: 5px 0px 0px 20px;
    }
</style>
<script>
    startListening();
    document.cookie = "order_status=0; max-age=0";
    function getCookies(){
        let rawCookies = document.cookie.split(';'),
            cookie = { };
        rawCookies.forEach(function(el){
            el = el.split('=');
            cookie[el[0].trim()] = el[1].trim();
        });
          
        return cookie;
    }
    
    function startListening() {
        let cookie = document.cookie.split(';'),
            cookies = getCookies(),
            orderNum = cookies.order_num;
     
        source = new EventSource("assets/server_events.php");
        source.addEventListener(orderNum, receiveMessage);
        messageLog.innerHTML += "
" + "Начинаем слушать сообщения."
    }

    function receiveMessage(event) {
    let cookies = getCookies(),
        orderStatus = cookies.order_status;
        if(event.data.indexOf(orderStatus) == -1 || !orderStatus){
            messageLog.innerHTML += "
" + event.data;
            timeDisplay.innerHTML = event.data;
            document.cookie = "order_status="+event.data+"; max-age=21600";
        }
    }

    function stopListening() {
        try{
            source.close();
            messageLog.innerHTML += "
" + "Больше не прослушивать сообщения."
        }catch{}
    }
</script>
Артур Шевченко
17 мая 2021, 20:14
modx.pro
1
877
+1
Поблагодарить автора Отправить деньги

Комментарии: 2

Роман
21 мая 2021, 11:08
0
Я правильно понял, что это будет работать, только если клиент находится на сайте?
    Артур Шевченко
    21 мая 2021, 11:38
    0
    Да, правильно, но в случае если клиент сайт покинул, уведомления ему не нужны.
    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
    2