Server-Sent Events

Server-Sent Events (SSE) are a simple, one-way channel from your server to the browser. The server holds the connection open and pushes messages whenever it likes; the browser just listens. It is perfect for live clocks, progress updates, notifications, and dashboards, and in Fast it is barely more work than a normal route.

A first stream

Because a long-lived stream is just a fiber, sse() registers as easily as any GET route. Your handler receives an SseConnection and loops, sending messages:

use Fast\Http\Sse\SseConnection;

sse('/clock', function (SseConnection $sse): void {
	while (!$sse->closed()) {
		$sse->send(['time' => time()], event: 'tick');
		$sse->sleep(1);
	}
});

That pushes the current time to every connected client once a second. Notice how flat the code is: a while loop with a sleep() in it, reading top to bottom. The event loop handles the concurrency, so this one handler happily serves many clients at once without blocking anything else.

Sending data

send() is the workhorse. Pass an array and Fast JSON-encodes it; pass a string and it goes as-is. The named arguments let you set the SSE event type, an id, and a reconnection hint:

$sse->send(['user' => auth()->id()], event: 'ready');
$sse->send('plain message');
$sse->send($data, event: 'update', id: $lastId, retry: 3000);

There are a few more methods on the connection:

  • comment() sends an SSE comment line, handy as a keep-alive ping.
  • sleep($seconds) pauses this fiber without blocking the loop.
  • closed() tells you whether the client has gone away.

Detecting disconnects

Clients come and go, and you do not want to keep working for someone who has closed their tab. Check closed() at the top of your loop, and Fast also flags the client as gone if a write fails, so a send() to a departed client will not hang:

sse('/feed', function (SseConnection $sse): void {
	foreach (events() as $event) {
		if ($sse->closed()) {
			return;
		}
		$sse->send($event, event: 'feed');
	}
});

What Fast sets up for you

Behind the scenes, sse() emits the right headers for a well-behaved event stream: Content-Type: text/event-stream, Connection: close, Cache-Control: no-cache, and X-Accel-Buffering: no so proxies do not buffer your stream. You do not have to remember any of that.

One important detail about timeouts:

SSE streams are exempt from the ordinary 60-second response deadline, because they are meant to stay open. Every individual write still respects the writeTimeout, though, so a client whose socket has stalled will not tie up your fiber forever.

SSE versus the alternatives

SSE is one-way (server to browser) and rides plain HTTP, which makes it simple, proxy-friendly, and easy to reason about. If you need the browser to send messages back over the same connection, you want a WebSocket instead; see WebSockets. And if you want to push to browsers from anywhere in your app, across workers, without managing connections yourself, look at Live Streams.