diff --git a/components/http_foundation.rst b/components/http_foundation.rst index 1cb87aafb24..7b524e33fe6 100644 --- a/components/http_foundation.rst +++ b/components/http_foundation.rst @@ -817,6 +817,73 @@ including generators:: return new StreamedJsonResponse(loadArticles()); } +.. _component-http-foundation-sse: + +Streaming Server-Sent Events +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`Symfony\\Component\\HttpFoundation\\EventStreamResponse` class +allows you to implement `Server-Sent Events (SSE)`_ - a standard for pushing +real-time updates from server to client over HTTP. + +.. versionadded:: 7.3 + + The ``EventStreamResponse`` and ``ServerEvent`` classes were introduced in Symfony 7.3. + +Basic usage with a generator:: + + use Symfony\Component\HttpFoundation\EventStreamResponse; + use Symfony\Component\HttpFoundation\ServerEvent; + + $response = new EventStreamResponse(function (): iterable { + yield new ServerEvent('First message'); + sleep(1); + yield new ServerEvent('Second message'); + }); + + $response->send(); + +The ``EventStreamResponse`` automatically sets the required headers:: + + Content-Type: text/event-stream + Cache-Control: no-cache + Connection: keep-alive + +The :class:`Symfony\\Component\\HttpFoundation\\ServerEvent` class represents an +individual SSE event following `the WHATWG SSE specification`_. It accepts the +following constructor arguments: + +``data`` + The event data (string or iterable for multi-line data). + +``type`` + The event type. Clients can listen for specific types using + ``eventSource.addEventListener('type', ...)``. + +``id`` + The event ID. The browser sends this as ``Last-Event-ID`` header when + reconnecting, allowing you to resume from where the client left off. + +``retry`` + The reconnection time in milliseconds. Tells the browser how long to wait + before reconnecting if the connection is lost. + +``comment`` + A comment line (prefixed with ``:`` in the SSE protocol). Useful for + keep-alive messages to prevent connection timeouts. + +.. tip:: + + For usage in Symfony controllers with additional features like automatic + reconnection handling, see :ref:`controller-server-sent-events`. + +.. warning:: + + SSE keeps HTTP connections open, consuming server resources for each client. + For applications with many concurrent connections, consider using + :doc:`Mercure ` instead, which uses a dedicated hub for efficient + connection management. + .. _component-http-foundation-serving-files: Serving Files @@ -1086,3 +1153,5 @@ Learn More .. _RFC 8674: https://tools.ietf.org/html/rfc8674 .. _Doctrine Batch processing: https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/batch-processing.html#iterating-results .. _`CHIPS`: https://developer.mozilla.org/en-US/docs/Web/Privacy/Partitioned_cookies +.. _`Server-Sent Events (SSE)`: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events +.. _`the WHATWG SSE specification`: https://html.spec.whatwg.org/multipage/server-sent-events.html diff --git a/controller.rst b/controller.rst index a3b14416f31..43a425bf7a2 100644 --- a/controller.rst +++ b/controller.rst @@ -951,6 +951,121 @@ This way, browsers can start downloading the assets immediately; like the ``sendEarlyHints()`` method also returns the ``Response`` object, which you must use to create the full response sent from the controller action. +.. _controller-server-sent-events: + +Streaming Server-Sent Events +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`Server-Sent Events (SSE)`_ is a standard that allows a server to push updates +to the client over a single HTTP connection. It's a simple and efficient way +to send real-time updates from the server to the browser, such as live +notifications, progress updates, or data feeds. + +.. versionadded:: 7.3 + + The ``EventStreamResponse`` and ``ServerEvent`` classes were introduced in Symfony 7.3. + +The :class:`Symfony\\Component\\HttpFoundation\\EventStreamResponse` class +allows you to stream events to the client using the SSE protocol. It automatically +sets the required headers (``Content-Type: text/event-stream``, ``Cache-Control: no-cache``, +``Connection: keep-alive``) and provides a simple API to send events:: + + use Symfony\Component\HttpFoundation\EventStreamResponse; + use Symfony\Component\HttpFoundation\ServerEvent; + + // ... + + public function liveNotifications(): EventStreamResponse + { + return new EventStreamResponse(function (): iterable { + foreach ($this->getNotifications() as $notification) { + yield new ServerEvent($notification->toJson()); + + sleep(1); // simulate a delay between events + } + }); + } + +The :class:`Symfony\\Component\\HttpFoundation\\ServerEvent` class is a DTO +that represents an SSE event following `the WHATWG specification`_. You can +customize each event using its constructor arguments:: + + // basic event with just data + yield new ServerEvent('Some message'); + + // event with a custom type (client listens via addEventListener('my-event', ...)) + yield new ServerEvent( + data: json_encode(['status' => 'completed']), + type: 'my-event' + ); + + // event with an ID (useful for resuming streams with Last-Event-ID header) + yield new ServerEvent( + data: 'Update content', + id: 'event-123' + ); + + // event that tells the client to retry after a specific time (in milliseconds) + yield new ServerEvent( + data: 'Retry info', + retry: 5000 + ); + + // event with a comment (can be used for keep-alive) + yield new ServerEvent(comment: 'keep-alive'); + +For use cases where generators are not feasible, you can use the +:method:`Symfony\\Component\\HttpFoundation\\EventStreamResponse::sendEvent` +method for manual control:: + + use Symfony\Component\HttpFoundation\EventStreamResponse; + use Symfony\Component\HttpFoundation\ServerEvent; + + // ... + + public function liveProgress(): EventStreamResponse + { + return new EventStreamResponse(function (EventStreamResponse $response) { + $redis = new \Redis(); + $redis->connect('127.0.0.1'); + $redis->subscribe(['message'], function (/* ... */, string $message) use ($response) { + $response->sendEvent(new ServerEvent($message)); + }); + }); + } + +On the client side, you can listen to events using the native ``EventSource`` API: + +.. code-block:: javascript + + const eventSource = new EventSource('/live-notifications'); + + // listen to all events (without a specific type) + eventSource.onmessage = (event) => { + console.log('Received:', event.data); + }; + + // listen to events with a specific type + eventSource.addEventListener('my-event', (event) => { + console.log('My event:', JSON.parse(event.data)); + }); + + // handle connection errors + eventSource.onerror = (error) => { + console.error('SSE error:', error); + eventSource.close(); + }; + +.. warning:: + + ``EventStreamResponse`` is designed for small applications with limited + concurrent connections. Because SSE keeps HTTP connections open, it consumes + server resources (memory and connection limits) for each connected client. + + For high-traffic applications that need to broadcast updates to many clients + simultaneously, consider using :doc:`Mercure `, which is built on + top of SSE but uses a dedicated hub to manage connections efficiently. + Final Thoughts -------------- @@ -991,3 +1106,5 @@ Learn more about Controllers .. _`Validate Filters`: https://www.php.net/manual/en/filter.constants.php .. _`phpstan/phpdoc-parser`: https://packagist.org/packages/phpstan/phpdoc-parser .. _`phpdocumentor/type-resolver`: https://packagist.org/packages/phpdocumentor/type-resolver +.. _`Server-Sent Events (SSE)`: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events +.. _`the WHATWG specification`: https://html.spec.whatwg.org/multipage/server-sent-events.html diff --git a/mercure.rst b/mercure.rst index 5a34fe7e4bb..d3917e3b81c 100644 --- a/mercure.rst +++ b/mercure.rst @@ -32,6 +32,17 @@ thanks to a specific HTTP header). All these features are supported in the Symfony integration. +.. tip:: + + For simpler use cases with limited concurrent connections (e.g. progress + updates, admin dashboards, or internal tools), you can use native + Server-Sent Events directly via Symfony's ``EventStreamResponse`` class. + See :ref:`controller-server-sent-events` for more information. + + Use Mercure when you need its advanced features like authorization, + automatic reconnection with message recovery, broadcasting to many clients, + or high-traffic scalability. + `In this recording`_ you can see how a Symfony web API leverages Mercure and API Platform to update in live a React app and a mobile app (React Native) generated using the API Platform client generator.