PHP Отправленные на сервер события: запуск пользовательского события из другого файла - PullRequest
0 голосов
/ 10 марта 2020

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

Лог c выглядит следующим образом:

  • Клиент выбирает общий API (скажем, /api.php?action=add&payload=...), и с этой выборкой происходит изменение в БД made.

  • После того, как mysqli_result вернул true, должно быть запущено отправленное сервером событие, чтобы уведомить других клиентов (или их обслуживающий персонал) об изменении БД.

Предостережения:

  • Мой логин SSE c помещен в отдельный файл (/sse.php), чтобы не создавать беспорядок в логах API c и слушать отдельные конечные точки клиентской стороны. Поэтому мне нужно запустить SSE из api.php и pu sh результат оттуда для всех клиентов.

  • В sse.php я определил функцию для отправки, которая принимает сообщение от api.php как парам. Что бы я ни пытался, эту функцию никогда не вызывали.

  • Я очень хочу избежать создания демонов и любых бесконечных циклов . Но без них соединение для клиентского прослушивателя (eventSource) закрывается и открывается каждые 3 секунды.

Текущее состояние всего этого:

api.php

<?php
header("Access-Control-Allow-Origin: *");
include 'connection.php';       // mysqli credentials and connection object
include_once 'functions.php';   // logics

$action = isset($_GET['a']) ? $_GET['a'] : 'fail';
switch ($action) {
    case "add":
        $result = api__handle_add(); // adds data to DB and returns stringified success/failure result
        require "./sse.php";         // file with SSE logic with ev__ namespace
        ev__send_data($result);      // this defined function should send message to clients
        break;
    // ...
    case "testquery":
        require "./sse.php";
        ev__send_data("Test event!!! Ololo");
        break;
    case "fail": default:
        header('HTTP/1.1 403 Forbidden', true, 403);
}

sse.php

<?php
header("Access-Control-Allow-Origin: *");
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");
header("Connection: keep-alive");

$_fired = false;   // flag checking for is anything happening right now

while (1) {        // the infinite loop I strongly want to get rid of
    if (!$_fired) {
        usleep(5000);  // nothing to send if nothing is fired, but I should keep the connection alive somehow
    }  else {
        // here I should call 'ev__send_data($message_from_api_php)'
        // thus I require this file to `api.php` and try to call 'ev__send_data' from there.
    }
    ob_flush();
    flush();
}

function ev__send_data(string $msg) {
    global $_fired;
    $_fired = true;
    echo "data: $msg". PHP_EOL;
    echo PHP_EOL;
    ob_flush();
    flush();
    usleep(1000);
    $_fired = false;
}

Типичная реализация на стороне клиента, с обработчиками onopen, onerror и onmessage eventSource. Я думаю, что мне не нужно брать это здесь, так как оно не отличается от тысяч примеров, которые я видел здесь на SO и в других источниках.

В заключение: я хочу запускать SSE извне SSE- Я передаю фрагменты данных извне, и я хочу избавиться от while (1) {} и генерировать события только тогда, когда что-то действительно происходит. Я наткнулся на тысячи учебных пособий и тем SO и не нашел ничего, кроме типичных «учебных примеров» о том, как отправлять серверное время клиенту каждые n секунд и все такое. И я думаю, что мне не нужны WebSockets (учитывая, что моя локальная среда разработки Win10 + XAMPP, моя среда prod будет чем-то похожим на Linux, и я не думаю, что смогу реализовать свой собственный WebSocket без сторонних зависимостей, которые Я не хочу устанавливать).

Есть ли у меня шанс?

EDIT

Я нашел немного другой подход. Теперь генерация событий на моем сервере зависит от логических флагов, которые были установлены в базе данных MySQL в отдельной таблице flags. Когда мне нужно отправить событие, API меняет определенный флаг (для этого примера, когда один клиент отправил некоторые изменения в БД, API также делает запрос к таблице flags и устанавливает флаг DB_CHANGED в 1 ). И sse.php делает запрос к таблице flags каждую итерацию бесконечного l oop (каждые 5 секунд) и генерирует события в зависимости от состояния флагов, а после генерации устанавливает соответствующий флаг на 0 через еще одну запрос. Это вроде работает.

Пример текущего sse.php:

<?php
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");

require_once "connection.php";  // mysqli connection object
set_time_limit(600);

stream();

function stream() {
    global $connection;

    while (1) {          // oh that infinite loop
        $flags = [];     // there will be fetched flags from DB
        $flags_sqli = $connection->query("SELECT * FROM `flags`");
        while ($flag = $flags_sqli->fetch_assoc()) {
            $flags[$flag['key_']] = ev__h__to_bool_or_string($flag['value_']);
        }

        if ($flags['ON_TEST']) {
            ev__send_data("Test event!!! Ololo");
            ev__update_flag("ON_TEST", "0");
        } elseif ($flags['ON_DB_ADD']) {
            ev__handle_add();
        } else {
            echo PHP_EOL;      // keep connection alive - push single "end of line"
        }

        ob_flush();
        flush();
        sleep(5);    // interval per iteration - 5 secs
    }
}

function ev__send_data(string $msg) : void {
    echo "data: ".date("d-m-Y")." ".date("H:i:s").PHP_EOL;
    echo "data: $msg".PHP_EOL;
    echo PHP_EOL;
}

function ev__handle_add() : void {
    global $connection;
    $table = $connection->query('SELECT `value_` FROM `flags` WHERE `key_` = "LAST_ADDED_TABLE"')->fetch_row()[0];
    $query = "SELECT * FROM `banners_".$table."` WHERE `id` = (SELECT MAX(`id`) FROM `banners_".$table."`)";
    $row = $connection->query($query)->fetch_assoc();

    echo "event: db_add_row".PHP_EOL;
    echo 'data: { "manager": '.$row['manager_id'].', "banner_name": "'.$row['name'].'", "time": "'.date("d-m-Y").' '.date("H:i:s").'" }'.PHP_EOL;
    echo PHP_EOL;

    ev__update_flag("ON_DB_ADD", "0");
}

function ev__update_flag(string $key, string $value) {
    global $connection;
    $query = "UPDATE `flags` SET `value_` = '$value' WHERE `flags`.`key_` = '$key';";
    $connection->query($query);
}

function ev__h__to_bool_or_string($value) {
    return $value === "0" || $value === "1"
        ? boolval(intval($value))
        : $value;
}

Поэтому я не избавился от бесконечного l oop. Учитывая, что одновременных подключений будет очень мало (максимум 3-5 сеансов за один раз) (при использовании prod), этот случай не должен создавать проблем с производительностью. Но что, если это будет какой-то загруженный сервис? В качестве ответа хочу увидеть советы по оптимизации для случая Server-Sent Events.

...