{"id":21673624,"url":"https://github.com/aliakh/demo-spring-sse","last_synced_at":"2025-08-21T00:30:39.102Z","repository":{"id":40633333,"uuid":"70407459","full_name":"aliakh/demo-spring-sse","owner":"aliakh","description":"'Server-Sent Events (SSE) in Spring 5 with Web MVC and Web Flux' article and source code.","archived":false,"fork":false,"pushed_at":"2020-12-06T12:37:21.000Z","size":422,"stargazers_count":178,"open_issues_count":4,"forks_count":60,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-12-09T09:39:44.692Z","etag":null,"topics":["eventsource","server-sent-events","serversentevent","sse","sseemitter"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aliakh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-09T14:12:57.000Z","updated_at":"2024-12-03T17:35:34.000Z","dependencies_parsed_at":"2022-09-09T02:11:11.884Z","dependency_job_id":null,"html_url":"https://github.com/aliakh/demo-spring-sse","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliakh%2Fdemo-spring-sse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliakh%2Fdemo-spring-sse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliakh%2Fdemo-spring-sse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliakh%2Fdemo-spring-sse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aliakh","download_url":"https://codeload.github.com/aliakh/demo-spring-sse/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230471175,"owners_count":18231193,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eventsource","server-sent-events","serversentevent","sse","sseemitter"],"created_at":"2024-11-25T13:40:11.288Z","updated_at":"2024-12-19T17:08:48.877Z","avatar_url":"https://github.com/aliakh.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Server-Sent Events (SSE) in Spring 5 with Web MVC and Web Flux\n\n## Introduction\n\nThere are no simple, general-purpose methods to implement asynchronous _server-to-client_ communication in web applications with acceptable performance. \n\nHTTP is a request-response protocol in the _client-server_ computing model. To start an exchange, a client submits a request to a server. To finish the exchange, the server returns a response to the client. The server can send a response to only _one_ client - the one that made the request. In the HTTP protocol, _a client_ is the initiator of messages exchange. \n\nThere are cases when _a server_ should be the initiator of exchange. One of the methods to implement this is to allow the server to push messages to clients in the _publish/subscribe_ computing model. To start an exchange, a client subscribes to messages from the server. During the exchange, the server sends messages (as soon as they become available) to _many_ subscribed clients. To finish the exchange, the client cancels the subscription.\n\nServer-Sent Events (SSE) is a _simple_ technology to implement asynchronous _server-to-client_ communication for _specific_ web applications.\n\n## Overview\n\nThere are several technologies that allow a client to receive messages about asynchronous updates from a server. They can be divided into two categories: _client pull_ and _server push_. \n\n### Client pull\n\nIn _client pull_ technologies, a client periodically requests a server for updates. The server can respond with updates or with a special response that it has not yet been updated. There are two types of _client pull_: _short polling_ and _long polling_.\n\n#### Short polling\n\nA client periodically sends requests to a server. If the server has updates, it sends a response to the client and closes the connection. If the server has no updates, it sends a special response to the client and also closes the connection.\n\n#### Long polling\n\nA client sends a request to a server. If the server has updates, it sends a response to the client and closes the connection. If the server has no updates, it holds the connection until updates become available. When updates are available, the server sends a response to the client and closes the connection. If updates are not available for some timeout, the server sends a special response to the client and also closes the connection.\n\n### Server push\n\nIn _server push_ technologies, a server proactively sends messages to clients immediately after they are available. Among others, there are two types of _server push_: Server-Sent Events and WebSocket.\n\n#### Server-Sent Events\n\nServer-Sent Events is a technology to send text messages only from a server to clients in browser-based web applications. Server-Sent Events is based on _persistent connections_ in the HTTP protocol. Server-Sent Events has the network protocol and the EventSource client interface [standardized](https://html.spec.whatwg.org/multipage/server-sent-events.html) by W3C as part of HTML5 standards suite.\n\n#### WebSocket\n\nWebSocket is a technology to implement simultaneous, bi-directional, real-time communication in web applications. WebSocket is based on a protocol other than HTTP, so it can require additional setup of network infrastructure (proxy servers, NATs, firewalls, etc). However, WebSocket can provide performance that is difficult to achieve with HTTP-based technologies.\n\n## SSE network protocol\n\nTo subscribe to server events, a client should make a `GET` request with the headers:\n\n*   `Accept: text/event-stream` indicates _media type_ of events required by the standard\n*   `Cache-Control: no-cache` disables any events caching\n*   `Connection: keep-alive` indicates that a _persistent connection_ is being used\n\n```\nGET /sse HTTP/1.1\nAccept: text/event-stream\nCache-Control: no-cache\nConnection: keep-alive\n```\n\nA server should confirm the subscription with a response with the headers: \n\n*   `Content-Type: text/event-stream;charset=UTF-8` indicates _media type_ and _encoding_ of events required by the standard\n*   `Transfer-Encoding: chunked` indicates that the server streams dynamically generated content and therefore the content size is not known in advance\n\n```\nHTTP/1.1 200\nContent-Type: text/event-stream;charset=UTF-8\nTransfer-Encoding: chunked\n```\n\nAfter subscribing, the server sends messages as soon as they become available. Events are text messages in `UTF-8` encoding. Events are separated one from another by two newline characters `\\n\\n`. Each event consists of one or many `name: value` fields, separated by a single newline character `\\n`.\n\nIn the `data` field, the server can send event data. \n\n```\ndata: The first event.\n\ndata: The second event.\n```\n\nThe server can split the `data` field into several lines by a single newline character `\\n`.\n\n```\ndata: The third\ndata: event.\n```\n\nIn the `id` field the server can send a unique event identifier. If a connection is broken, the client should automatically reconnect and send the last received event `id` with the header `Last-Event-ID`.\n\n```\nid: 1\ndata: The first event.\n\nid: 2 \ndata: The second event.\n```\n\nIn the `event` field the server can send event type. The server can send events of different types, as well as without any type, in the same subscription.\n\n```\nevent: type1\ndata: An event of type1.\n\nevent: type2\ndata: An event of type2.\n\ndata: An event without any type.\n```\n\nIn the `retry` field the server can send timeout (in milliseconds), after which the client should automatically reconnect when a connection is broken. If this field is not specified, by the standard it should be 3000 milliseconds.\n\n```\nretry: 1000\n```\n\nIf a line begins with a colon character `:`, it should be ignored by the client. This can be used to send comments from the server or to prevent some proxy servers from closing the connection by timeout.\n\n```\n: ping\n```\n\n## SSE client: EventSource interface\n\nTo open a connection, it should be created an `EventSource` object.\n\n```\nvar eventSource = new EventSource('/sse);\n```\n\nDespite Server-Sent Events is designed to send events _from server to client_ the it’s possible to use `GET` query parameters to pass data _from client to server_.\n\n```\nvar eventSource = new EventSource('/sse?event=type1); \n...\neventSource.close();\neventSource = new EventSource('/sse?event=type1\u0026event=type2);\n...\n```\n\nTo close the connection, it should be called method `close()`.\n\n```\neventSource.close();\n```\n\nThere is the `readyState` attribute that represents the state of the connection:\n\n*   `EventSource.CONNECTING = 0` - the connection has not yet been established, or it was closed and the client is reconnecting\n*   `EventSource.OPEN = 1` - the client has an open connection and is handling events as it receives them\n*   `EventSource.CLOSED = 2`- the connection is not open, and the client is not trying to reconnect either there was a fatal error or the `close()` method was called\n\nTo handle an establishment of a connection, it should be subscribed to the `onopen` event handler. \n\n```\neventSource.onopen = function () {\n   console.log('connection is established');\n};\n```\n\nTo handle _some_ changes in the connection state _or_ fatal errors, it should be subscribed to the `onerrror` event handler.\n\n```\neventSource.onerror = function (event) {\n    console.log('connection state: ' + eventSource.readyState + ', error: ' + event);\n};\n```\n\nTo handle receiving events without the `event` field, it should be subscribed to the `onmessage` event handler.\n\n```\neventSource.onmessage = function (event) {\n    console.log('id: ' + event.lastEventId + ', data: ' + event.data);\n};\n```\n\nTo handle receiving events with the `event` field, it should be subscribed to an event handler for such an event.\n\n```\neventSource.addEventListener('type1', function (event) {\n    console.log('id: ' + event.lastEventId + ', data: ' + event.data);\n}, false);\n```\n\nEventSource client interface is implemented in [most modern browsers](https://caniuse.com/#feat=eventsource).\n\n![https://caniuse.com/#feat=eventsource](/.images/caniuse.com-eventsource.png)\n\n## SSE Java server: Spring Web MVC\n\n### Introduction\n\nSpring Web MVC framework 5.2.0 is based on Servlet 3.1 API and uses _thread pools_ to implement asynchronous Java web applications. Such applications can be run on Servlet 3.1+ containers such as Tomcat 8.5 and Jetty 9.3.\n\n### Overview\n\nTo implement sending events with Spring Web MVC framework:\n\n1. create a controller class and mark it with the `@RestController` annotation\n2. create a method to create a client connection, that returns a [SseEmitter](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html), handles `GET` requests and produces `text/event-stream`\n    1. create a new `SseEmitter`, to save it and to return it from the method\n3. send events asynchronously, in another thread, get the saved `SseEmitter` and call a `SseEmitter.send` method as many times as necessary\n    1. to finish sending events, call the [SseEmitter.complete()](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.html#complete--) method\n    2. to finish sending events exceptionally, call the [SseEmitter.completeWithError()](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.html#completeWithError-java.lang.Throwable-) method\n\nA simplified controller source:\n\n```\n@RestController\npublic class SseWebMvcController\n\n    private SseEmitter emitter;\n\n    @GetMapping(path=\"/sse\", produces=MediaType.TEXT_EVENT_STREAM_VALUE)\n    SseEmitter createConnection() {\n        emitter = new SseEmitter();\n        return emitter;\n    }\n\n    // in another thread\n    void sendEvents() {\n        try {\n            emitter.send(\"Alpha\");\n            emitter.send(\"Omega\");\n\n            emitter.complete();\n        } catch(Exception e) {\n            emitter.completeWithError(e);\n        }\n    }\n}\n```\n\nTo send events with only the `data` field, it should be used the [SseEmitter.send(Object object)](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html#send-java.lang.Object-) method. To send events with the fields `data`, `id`, `event`, `retry` and comments, it should be used the  [SseEmitter.send(SseEmitter.SseEventBuilder builder)](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html#send-org.springframework.web.servlet.mvc.method.annotation.SseEmitter.SseEventBuilder-) method.\n\nIn the examples below, to send the same events to many clients, the [SseEmitters](https://github.com/aliakh/demo-spring-sse/blob/master/server-mvc/src/main/java/demo/sse/server/mvc/controller/SseEmitters.java) class was implemented. To create a client connection, there is the `add(SseEmitter emitter)` method that saves a `SseEmitter` in a thread-safe container. To send events asynchronously, there is the `send(Object obj)` method that sends the same event to all connected clients.\n\nA simplified class source:\n\n```\nclass SseEmitters {\n\n    private final List\u003cSseEmitter\u003e emitters = new CopyOnWriteArrayList\u003c\u003e();\n\n    SseEmitter add(SseEmitter emitter) {\n        this.emitters.add(emitter);\n\n        emitter.onCompletion(() -\u003e {\n            this.emitters.remove(emitter);\n        });\n        emitter.onTimeout(() -\u003e {\n            emitter.complete();\n            this.emitters.remove(emitter);\n        });\n\n        return emitter;\n    }\n\n    void send(Object obj) {\n        List\u003cSseEmitter\u003e failedEmitters = new ArrayList\u003c\u003e();\n\n        this.emitters.forEach(emitter -\u003e {\n            try {\n                emitter.send(obj);\n            } catch (Exception e) {\n                emitter.completeWithError(e);\n                failedEmitters.add(emitter);\n            }\n        });\n\n        this.emitters.removeAll(failedEmitters);\n    }\n}\n```\n\n### Handling short-lasting periodic events stream\n\nIn this example, a server sends a _short-lasting periodic events_ stream - a finite stream of words (_[The quick brown fox jumps over the lazy dog](https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog)_ pangram) every second, until the words are finished. \n\nTo implement this, the mentioned [SseEmitters](https://github.com/aliakh/demo-spring-sse/blob/master/server-mvc/src/main/java/demo/sse/server/mvc/controller/SseEmitters.java) class was used. To send events asynchronously and periodically, a _cached thread pool_ has been created. Because the events stream is short-lasting, _each client connection_ submits a _separate task_ to the thread pool, right inside the controller method. \n\nA simplified controller source:\n\n```\n@Controller\n@RequestMapping(\"/sse/mvc\")\npublic class WordsController {\n\n   private static final String[] WORDS = \"The quick brown fox jumps over the lazy dog.\".split(\" \");\n\n   private final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();\n\n   @GetMapping(path = \"/words\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n   SseEmitter getWords() {\n       SseEmitter emitter = new SseEmitter();\n\n       cachedThreadPool.execute(() -\u003e {\n           try {\n               for (int i = 0; i \u003c WORDS.length; i++) {\n                   emitter.send(WORDS[i]);\n                   TimeUnit.SECONDS.sleep(1);\n               }\n\n               emitter.complete();\n           } catch (Exception e) {\n               emitter.completeWithError(e);\n           }\n       });\n\n       return emitter;\n   }\n}\n```\n\nAn events client example with `curl` command-line tool.\n\n```\ncurl -v http://localhost:8080/sse/mvc/words\n```\n\n![An events client example with curl command-line tool](/.images/words-curl.png)\n\nAn events client example with SSE URL in a browser. \n\n```\nhttp://localhost:8080/sse/mvc/words\n```\n\n![An events client example with SSE URL in a browser](/.images/words-browser.png)\n\nAn events client source with `EventSource` JavaScript client.\n\n```\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n   \u003cmeta charset=\"UTF-8\"\u003e\n   \u003ctitle\u003eServer-Sent Events client example with EventSource\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cscript\u003e\n   if (window.EventSource == null) {\n       alert('The browser does not support Server-Sent Events');\n   } else {\n       var eventSource = new EventSource('/sse/mvc/words');\n\n       eventSource.onopen = function () {\n           console.log('connection is established');\n       };\n\n       eventSource.onerror = function (error) {\n           console.log('connection state: ' + eventSource.readyState + ', error: ' + event);\n       };\n\n       eventSource.onmessage = function (event) {\n           console.log('id: ' + event.lastEventId + ', data: ' + event.data);\n\n           if (event.data.endsWith('.')) {\n               eventSource.close();\n               console.log('connection is closed');\n           }\n       };\n   }\n\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nAn events client example with `EventSource` JavaScript client in a browser. There is used automatic reconnect on the client-side and the [implemented reconnect](https://github.com/aliakh/demo-spring-sse/blob/master/server-web-mvc/src/main/java/demo/sse/server/web/mvc/controller/WordsController.java) on the server-side.\n\n![An events client example with EventSource JavaScript client in a browser](/.images/words-eventsource.png)\n\n### Handling long-lasting periodic events\n\nIn this example, a server sends _long-lasting periodic events_ stream - a potentially infinite stream of server performance information every second: \n\n*   committed virtual memory size\n*   total swap space size\n*   free swap space size\n*   total physical memory size\n*   free physical memory size\n*   system CPU load\n*   process CPU load\n\nTo implement this the [PerformanceService](https://github.com/aliakh/demo-spring-sse/blob/master/server-common/src/main/java/demo/sse/server/common/management/PerformanceService.java) class was implemented which uses the [OperatingSystemMXBean](https://docs.oracle.com/en/java/javase/12/docs/api/jdk.management/com/sun/management/OperatingSystemMXBean.html) class to read performance information from an operations system. Also was used the mentioned [SseEmitters](https://github.com/aliakh/demo-spring-sse/blob/master/server-mvc/src/main/java/demo/sse/server/mvc/controller/SseEmitters.java) class. To send events asynchronously and periodically, a _scheduled thread pool_ has been created. Because the events stream is long-lasting, _a single task_ is submitted to the thread pool to send events to _all clients_ simultaneously. \n\nA simplified controller example:\n\n```\n@RestController\n@RequestMapping(\"/sse/mvc\")\npublic class PerformanceController {\n\n   private final PerformanceService performanceService;\n\n   PerformanceController(PerformanceService performanceService) {\n       this.performanceService = performanceService;\n   }\n\n   private final AtomicInteger id = new AtomicInteger();\n\n   private final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);\n\n   private final SseEmitters emitters = new SseEmitters();\n\n   @PostConstruct\n   void init() {\n       scheduledThreadPool.scheduleAtFixedRate(() -\u003e {\n           emitters.send(performanceService.getPerformance());\n       }, 0, 1, TimeUnit.SECONDS);\n   }\n\n   @GetMapping(path = \"/performance\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n   SseEmitter getPerformance() {\n       return emitters.add();\n   }\n}\n```\n\nAn events client example with [Highcharts](https://www.highcharts.com/) JavaScript library to draw charts of server performance.\n\n![An events client example with Highcharts JavaScript library](/.images/performance-highcharts.png)\n\n### Handling aperiodic events\n\nIn this example, a server sends aperiodic events stream about changes of files (create, modify, delete) in a folder being watched. As the folder is used the current user’s home folder available by the `System.getProperty(\"user.home\")` property. \n\nTo implement this the [FolderWatchService](https://github.com/aliakh/demo-spring-sse/blob/master/server-common/src/main/java/demo/sse/server/common/file/FolderWatchService.java) class was implemented which uses Java NIO files watch features. Also was used the mentioned [SseEmitters](https://github.com/aliakh/demo-spring-sse/blob/master/server-mvc/src/main/java/demo/sse/server/mvc/controller/SseEmitters.java) class. To send events asynchronously and aperiodically, the [FolderWatchService](https://github.com/aliakh/demo-spring-sse/blob/master/server-common/src/main/java/demo/sse/server/common/file/FolderWatchService.java) class produces Spring applications events, that are consumed by the controller (by implementing a listener method).\n\nA simplified server example:\n\n```\n@RestController\n@RequestMapping(\"/sse/mvc\")\npublic class FolderWatchController implements ApplicationListener\u003cFolderChangeEvent\u003e {\n\n   private final FolderWatchService folderWatchService;\n\n   FolderWatchController(FolderWatchService folderWatchService) {\n       this.folderWatchService = folderWatchService;\n   }\n\n   private final SseEmitters emitters = new SseEmitters();\n\n   @PostConstruct\n   void init() {\n       folderWatchService.start(System.getProperty(\"user.home\"));\n   }\n\n   @GetMapping(path = \"/folder-watch\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n   SseEmitter getFolderWatch() {\n       return emitters.add(new SseEmitter());\n   }\n\n   @Override\n   public void onApplicationEvent(FolderChangeEvent event) {\n       emitters.send(event.getEvent());\n   }\n}\n```\n\nAn events client example using `EventSource` JavaScript client.\n\n![An events client example using EventSource JavaScript client](/.images/folder-watch-eventsource.png)\n\n## SSE Java server: Spring Web Flux\n\n### Introduction\n\nSpring Web Flux framework 5.2.0 is based on Reactive Streams API and uses the _event-loop_ computing model to implement asynchronous Java web applications. Such applications can be run on non-blocking web servers such as Netty 4.1 and Undertow 1.4 _and_ on Servlet 3.1+ containers such as Tomcat 8.5 and Jetty 9.3.\n\n### Overview\n\nTo implement sending events with Spring Web Flux framework:\n\n1. create a controller class and mark it with the `@RestController` annotation\n2. create a method to create a client connection and to send events, that returns a [Flux](https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html), handles `GET` requests and produces `text/event-stream`\n    1. create a new `Flux` and return it from the method\n\nA simplified controller source:\n\n```\n@RestController\npublic class ExampleController\n\n    @GetMapping(path=\"/sse\", produces=MediaType.TEXT_EVENT_STREAM_VALUE)\n    public Flux\u003cString\u003e createConnectionAndSendEvents() {\n        return Flux.just(\"Alpha\", \"Omega\");\n    }\n}\n```\n\nTo send events with only the `data` field, it should be used the `Flux\u003cT\u003e` type. To send events with the fields `data`, `id`, `event`, `retry` and comments, it should be used the `Flux\u003cServerSentEvent\u003cT\u003e\u003e` type.\n\n### Handling short-lasting periodic events stream\n\nIn this example, a server sends a _short-lasting periodic events_ stream - a finite stream of words (_[The quick brown fox jumps over the lazy dog](https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog)_ pangram) every second, until the words are finished. \n\nTo implement this:\n\n*   create a `Flux` of the words `Flux.just(WORDS)` of type `Flux\u003cString\u003e`\n*   create a `Flux` that emits incrementing `long` values every second `Flux.interval(Duration.ofSeconds(1))`  of type `Flux\u003cLong\u003e`\n*   combine them together by `zip` method to type `Flux\u003cTuple2\u003cString,Long\u003e\u003e`\n*   extract the first element of the tuple by `map(Tuple2::getT1)` of type `Flux\u003cString\u003e`\n\nA simplified controller source:\n\n```\n@RestController\n@RequestMapping(\"/sse/flux\")\npublic class WordsController {\n\n   private static final String[] WORDS = \"The quick brown fox jumps over the lazy dog.\".split(\" \");\n\n   @GetMapping(path = \"/words\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n   Flux\u003cString\u003e getWords() {\n       return Flux\n               .zip(Flux.just(WORDS), Flux.interval(Duration.ofSeconds(1)))\n               .map(Tuple2::getT1);\n   }\n}\n```\n\nThe event clients for this example are identical to those used in the Web MVC example.\n\n### Handling long-lasting periodic events\n\nIn this example, a server sends _long-lasting periodic events_ stream - a potentially infinite stream of server performance information every second.\n\nTo implement this:\n\n*   create a `Flux` that emits incrementing `long` values every second `Flux.interval(Duration.ofSeconds(1))` of type `Flux\u003cLong\u003e`\n*   convert it by `map(sequence -\u003e performanceService.getPerformance())` method to type `Flux\u003cPerformance\u003e`\n\nA simplified controller example:\n\n```\n@RestController\n@RequestMapping(\"/sse/flux\")\npublic class PerformanceController {\n\n   private final PerformanceService performanceService;\n\n   PerformanceController(PerformanceService performanceService) {\n       this.performanceService = performanceService;\n   }\n\n   @GetMapping(path = \"/performance\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n   Flux\u003cPerformance\u003e getPerformance() {\n       return Flux\n               .interval(Duration.ofSeconds(1))\n               .map(sequence -\u003e performanceService.getPerformance());\n   }\n}\n```\n\nThe event client for this example is identical to that used in the Web MVC example.\n\n### Handling aperiodic events\n\nIn this example, a server sends aperiodic events stream about changes of files (create, modify, delete) in a folder being watched. As the folder is used the current user’s home folder available by the `System.getProperty(\"user.home\")` property. \n\nTo implement this the [FolderWatchService](https://github.com/aliakh/demo-spring-sse/blob/master/server-common/src/main/java/demo/sse/server/common/file/FolderWatchService.java) class was implemented which uses Java NIO files watch features. To send events asynchronously and aperiodically, the [FolderWatchService](https://github.com/aliakh/demo-spring-sse/blob/master/server-common/src/main/java/demo/sse/server/common/file/FolderWatchService.java) class produces Spring applications events, that are consumed by the controller (by implementing a listener method). The controller listener method sends events to a `SubscribableChannel`, that is subscribed in a controller method to produce `Flux` of events.\n\nA simplified controller example:\n\n```\n@RestController\n@RequestMapping(\"/sse/flux\")\npublic class FolderWatchController implements ApplicationListener\u003cFolderChangeEvent\u003e {\n\n   private final FolderWatchService folderWatchService;\n\n   FolderWatchController(FolderWatchService folderWatchService) {\n       this.folderWatchService = folderWatchService;\n   }\n\n   private final SubscribableChannel subscribableChannel = MessageChannels.publishSubscribe().get();\n\n   @PostConstruct\n   void init() {\n       folderWatchService.start(System.getProperty(\"user.home\"));\n   }\n\n   @GetMapping(path = \"/folder-watch\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n   Flux\u003cFolderChangeEvent.Event\u003e getFolderWatch() {\n       return Flux.create(sink -\u003e {\n           MessageHandler handler = message -\u003e sink.next(FolderChangeEvent.class.cast(message.getPayload()).getEvent());\n           sink.onCancel(() -\u003e subscribableChannel.unsubscribe(handler));\n           subscribableChannel.subscribe(handler);\n       }, FluxSink.OverflowStrategy.LATEST);\n   }\n\n   @Override\n   public void onApplicationEvent(FolderChangeEvent event) {\n       subscribableChannel.send(new GenericMessage\u003c\u003e(event));\n   }\n}\n```\n\nThe event client for this example is identical to that used in the Web MVC example.\n\n## SSE limitations\n\nThere are limitations of SSE _by design_:\n\n*   it's possible to send messages in only one direction, from server to clients\n*   it's possible to send only text messages; despite it’s possible to use `Base64` encoding and `gzip` compression to send binary messages, it can be inefficient.\n\nBut there are also limitations of SSE  _by implementation_:\n\n*   Internet Explorer/Edge and many mobile browsers don’t support SSE; despite it’s possible to use _polyfills_, they can be inefficient\n*   many browsers allow opening a very limited number of SSE connections (up to 6 connections _per browser_ for Chrome, Firefox)\n\n## Conclusion\n\nComplete code examples are available in the [GitHub repository](https://github.com/aliakh/demo-spring-sse).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliakh%2Fdemo-spring-sse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faliakh%2Fdemo-spring-sse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliakh%2Fdemo-spring-sse/lists"}