← Async

ASYNC FEATURE

SSE Stream

This demo now receives real backend-generated SSE messages over one long-lived HTTP connection, not client-side simulated updates.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Server-sent events keep one long-lived HTTP response open and let the backend push named messages to the browser without polling.

How it works

The browser opens an EventSource connection to the SSE endpoint, and the Semitexa SSE runtime streams text/event-stream frames as backend events arrive. The demo keeps this surface authenticated because a persistent stream is a resource multiplier.

Why it matters

SSE is a pragmatic fit for one-way real-time updates. It uses plain HTTP, works well with browsers, and avoids the extra complexity of WebSockets when the client only needs to receive updates.

Key concepts

EventSource
Browser API for opening an SSE connection over plain HTTP.
text/event-stream
HTTP content type used by the SSE protocol.
long-lived response
A response that stays open so the server can push multiple messages over time.

Server Push

Event stream over plain HTTP

Connect once, then watch the backend push named events from the server into this page without WebSockets or manual refresh.

Sign in with Google required

Authorization is required to open the long-lived SSE stream used by this demo.

HTTP Transport
text/event-stream Content type
EventSource Browser API
Authorization required
Next backend minute tick
-- sec

Authorization is required to open the long-lived SSE stream used by this demo.

  • Backend events will appear here…

Sign in with Google to see more.

AsyncResourceSseServer EventSource text/event-stream SSE_PUBLIC_ANONYMOUS SSE_MAX_CONN_PER_IP SSE_MAX_CONN_GLOBAL SSE_MAX_CONNECTION_AGE_SECONDS

Verified against Semitexa Ultimate 2026.09.19.1020

SSE Stream

Real-time server push without WebSockets — a persistent HTTP connection that streams events. The client opens one EventSource connection and the server sends named events as they occur.

How it works

The SSE endpoint holds an open text/event-stream response. The server pushes named event frames over this connection whenever backend activity produces output. The client JavaScript receives each frame and updates the page without polling or a WebSocket handshake.

Collection feed frames

A collection feed (AbstractSseCollectionFeedHandler) streams a fixed, canonical frame vocabulary — the event names are an allow-list, never a client-controlled string:

  • ui.collection.data — carries the canonical { data, meta } collection envelope, the same projection the JSON pull mode returns, so the client renders both transports from one code path. Emitted on initial connect, on rehydration, and on Track-R-driven re-runs.
  • ui.collection.error — collection-level errors on the same stream.

(The legacy ui.grid.data / ui.grid.error frames that carried the v1 UiGridDataResponse shape were removed in the Phase 6 sweep.)

Why this matters

SSE is simpler than WebSockets for unidirectional server-to-client push: plain HTTP and native browser support via EventSource. Semitexa's AsyncResourceSseServer integrates with the event dispatcher so that async and queued listener completions can be streamed to the correct session without the client needing to poll.

Security model

A long-lived SSE connection holds a Swoole coroutine, a file descriptor, and a response handle for as long as the client stays connected. Unbounded public SSE is a DoS vector — an attacker who can open N connections keeps N workers busy. Semitexa treats SSE as a privileged resource.

Defaults:

  • Authentication is required. /__semitexa_kiss — the single SSE stream endpoint, used by Semitexa's browser-side SSE bootstrap — enforces an authenticated session. An unauthenticated request receives 401 Unauthorized.
  • Per-IP connection cap. SSE_MAX_CONN_PER_IP (default 5) bounds concurrent streams from a single client. Exceeding the cap returns 429 Too Many Requests with Retry-After: 30.
  • Per-worker global cap. SSE_MAX_CONN_GLOBAL (default 500) bounds total concurrent streams in one worker. Production deployments should size this against available FD and coroutine budget.
  • Hard connection age. SSE_MAX_CONNECTION_AGE_SECONDS (default 600) forces the server to send a close event and disconnect after ten minutes. Browser EventSource auto-reconnects; long-lived clients must handle the reconnect path.
  • Same-origin handshake. The request must carry Origin or Referer and the host must match the server. Requests with neither header present are rejected with 403 Forbidden.

Opt-in anonymous streams. For public-facing streams (dashboards, ticker widgets, etc.) set SSE_PUBLIC_ANONYMOUS=true. Anonymous requests are then allowed, but the connection caps above still apply unchanged.

Client contract

The framework does not replay missed events on reconnect. When a client reconnects after a network drop or after the server hits the age cap, it receives a fresh stream from the server's current state. If your application needs at-least-once delivery semantics, queue events domain-side and re-emit them on reconnect based on the Last-Event-ID header or your own cursor.

Environment variables

Variable Default Purpose
SSE_PUBLIC_ANONYMOUS false Allow unauthenticated clients to open /__semitexa_kiss.
SSE_MAX_CONN_PER_IP 5 Concurrent SSE connections allowed from one IP per worker.
SSE_MAX_CONN_GLOBAL 500 Concurrent SSE connections allowed per worker.
SSE_MAX_CONNECTION_AGE_SECONDS 600 Seconds before the server force-closes the stream. 0 disables the cap.

© Linus Torvalds: "Talk is cheap. Show me the code."

Handler Application entry point
<?phpdeclare(strict_types=1);namespace App\Application\Handler\Events;use App\Application\Payload\Events\ActivityStreamPayload;use App\Application\Resource\Sse\ActivityStreamResource;use Semitexa\Core\Attribute\AsPayloadHandler;use Semitexa\Core\Contract\TypedHandlerInterface;#[AsPayloadHandler(payload: ActivityStreamPayload::class, resource: ActivityStreamResource::class)]final class SseStreamHandler implements TypedHandlerInterface{    public function handle(ActivityStreamPayload $payload, ActivityStreamResource $resource): ActivityStreamResource    {        return $resource->stream('catalog.activity');    }}

How it works

The browser opens an EventSource connection to the SSE endpoint, and the Semitexa SSE runtime streams text/event-stream frames as backend events arrive. The demo keeps this surface authenticated because a persistent stream is a resource multiplier.

Why it matters

SSE is a pragmatic fit for one-way real-time updates. It uses plain HTTP, works well with browsers, and avoids the extra complexity of WebSockets when the client only needs to receive updates.

Key concepts

EventSource
Browser API for opening an SSE connection over plain HTTP.
text/event-stream
HTTP content type used by the SSE protocol.
long-lived response
A response that stays open so the server can push multiple messages over time.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal