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