80 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			80 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?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 
 | 
						|
{
 | 
						|
    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;
 | 
						|
 | 
						|
    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();
 | 
						|
        }
 | 
						|
 | 
						|
        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');
 | 
						|
 | 
						|
        // send headers to client indicating we are now a stream
 | 
						|
        ob_end_flush();
 | 
						|
        flush();
 | 
						|
 | 
						|
        $expiration_time = time() + $this->time_limit;
 | 
						|
 | 
						|
        $last_heartbeat = time();
 | 
						|
 | 
						|
        while (!connection_aborted() && time() < $expiration_time)
 | 
						|
        {
 | 
						|
            set_time_limit($this->exec_limit);
 | 
						|
            try {
 | 
						|
                $data = call_user_func($callback);
 | 
						|
                if ($data !== NULL)
 | 
						|
                {
 | 
						|
                    $this->send($data);
 | 
						|
                    $last_heartbeat = time();
 | 
						|
                }
 | 
						|
            } catch (StopEventLoopException $th) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            // sleep and perform heartbeat to ensure connection is still alive
 | 
						|
            for ($i = 0; $i < $this->interval; $i++)
 | 
						|
            {
 | 
						|
                if (time() >= $last_heartbeat + $this->heartbeat)
 | 
						|
                {
 | 
						|
                    echo ": \n\n";
 | 
						|
                    ob_end_flush();
 | 
						|
                    flush();
 | 
						|
                    $last_heartbeat = time();
 | 
						|
                }
 | 
						|
                sleep(1);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Send data to client encoded as JSON 
 | 
						|
     */
 | 
						|
    private function send(mixed $data): void
 | 
						|
    {
 | 
						|
        echo "data: " . json_encode($data);
 | 
						|
        echo "\n\n";
 | 
						|
        // send data to stream
 | 
						|
        ob_end_flush();
 | 
						|
        flush();
 | 
						|
    }
 | 
						|
} |