2022-03-27 22:19:01 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\SSE;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Small class to abstract Server-Sent Events (SSE)
|
|
|
|
*
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
|
|
|
|
*/
|
|
|
|
class EventLoop
|
|
|
|
{
|
2022-04-14 20:59:42 +00:00
|
|
|
public int $interval = 3;
|
|
|
|
public int $heartbeat = 5; // send heartbeat every num seconds to ensure connection is still alive
|
|
|
|
public int $time_limit = 3600;
|
|
|
|
public int $exec_limit = 30;
|
2022-03-27 22:19:01 +00:00
|
|
|
|
|
|
|
public function start(callable $callback): void
|
|
|
|
{
|
|
|
|
// as session data is locked to prevent concurrent writes we
|
|
|
|
// make it read only to prevent the server from locking up
|
|
|
|
if (session_status() === PHP_SESSION_ACTIVE)
|
|
|
|
{
|
|
|
|
session_write_close();
|
|
|
|
}
|
|
|
|
|
2022-03-28 08:46:02 +00:00
|
|
|
header('Content-Type: text/event-stream');
|
|
|
|
// explicitly disable caching so varnish and other upstreams won't cache.
|
|
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
|
|
// instruct nginx to disable fastcgi_buffering and disable gzip
|
|
|
|
header('X-Accel-Buffering: no');
|
2022-03-27 22:19:01 +00:00
|
|
|
|
|
|
|
// send headers to client indicating we are now a stream
|
|
|
|
ob_end_flush();
|
|
|
|
flush();
|
|
|
|
|
2022-04-14 20:59:42 +00:00
|
|
|
$expiration_time = time() + $this->time_limit;
|
2022-03-28 15:22:54 +00:00
|
|
|
|
2022-04-14 20:59:42 +00:00
|
|
|
$last_heartbeat = time();
|
2022-03-27 22:19:01 +00:00
|
|
|
|
2022-04-14 20:59:42 +00:00
|
|
|
while (!connection_aborted() && time() < $expiration_time)
|
2022-03-27 22:19:01 +00:00
|
|
|
{
|
2022-04-14 20:59:42 +00:00
|
|
|
set_time_limit($this->exec_limit);
|
2022-03-27 22:19:01 +00:00
|
|
|
try {
|
|
|
|
$data = call_user_func($callback);
|
|
|
|
if ($data !== NULL)
|
|
|
|
{
|
|
|
|
$this->send($data);
|
2022-04-14 20:59:42 +00:00
|
|
|
$last_heartbeat = time();
|
2022-03-27 22:19:01 +00:00
|
|
|
}
|
|
|
|
} catch (StopEventLoopException $th) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// sleep and perform heartbeat to ensure connection is still alive
|
|
|
|
for ($i = 0; $i < $this->interval; $i++)
|
|
|
|
{
|
2022-04-14 20:59:42 +00:00
|
|
|
if (time() >= $last_heartbeat + $this->heartbeat)
|
2022-03-27 22:19:01 +00:00
|
|
|
{
|
|
|
|
echo ": \n\n";
|
|
|
|
ob_end_flush();
|
|
|
|
flush();
|
2022-04-14 20:59:42 +00:00
|
|
|
$last_heartbeat = time();
|
2022-03-27 22:19:01 +00:00
|
|
|
}
|
|
|
|
sleep(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-04-14 20:59:42 +00:00
|
|
|
* Send data to client encoded as JSON
|
2022-03-27 22:19:01 +00:00
|
|
|
*/
|
2022-04-14 20:59:42 +00:00
|
|
|
private function send(mixed $data): void
|
2022-03-27 22:19:01 +00:00
|
|
|
{
|
|
|
|
echo "data: " . json_encode($data);
|
|
|
|
echo "\n\n";
|
|
|
|
// send data to stream
|
|
|
|
ob_end_flush();
|
|
|
|
flush();
|
|
|
|
}
|
|
|
|
}
|