Contents

Overview

Server-sent events (SSE) enable servers to push data to clients (e.g. Web browsers) using standard HTTP or HTTPS through a unidirectional server-to-client connection. In the server-sent events communication model, the client establishes the initial connection, and the server provides the data in the form of event streams. For more information about server-sent events, see the Server-sent events specification.

SSE is an alternative technology to WebSockets when only server-to-client messaging is required and can be accomplished without the need to switch protocols (upgrades) and without using imperfect solutions such as long polling. A server-sent connection is typically a long-lived connection in which messages are sent to the client over a longer period of time compared to a normal request-response connection. It is useful for updating live data such as stock tickers, results of live events, etc.

Helidon provides support for server and client APIs, although Web browsers are popular client alternatives. The following sections describe these APIs in more detail.

Server API

The Server API is available as a loadable service in the Helidon WebServer. The following additional dependency is required to find and load the SSE service in the WebServer.

Maven Coordinates

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver-sse</artifactId>
</dependency>
Copied

Usage

Sending events is accomplished by obtaining an SseSink instance from a ServerResponse using the SseSink.TYPE constant. The following example converts the response into an SseSink, emits two string messages and then closes the connection.

try (SseSink sseSink = res.sink(SseSink.TYPE)) {
    sseSink.emit(SseEvent.create("hello"))
            .emit(SseEvent.create("world"));
}
Copied

Once an SseSink is obtained from a ServerResponse, the latter is no longer usable to send additional data to the client given that response Content-Type will be automatically set to text/event-stream. Note that an SseSink is auto closeable, so it can be part of a try-with-resources block as shown above.

Events can be created using any of the static create methods in SseEvent as well as via a builder obtained by calling SseEvent.builder(). For more information see the Javadocs for those classes. In the example above, a simple create method with a string param is used to showcase a very common use case. The API supports integration with media type providers, so the event data may actually be of any type as long as it is possible to convert it to a string value.

Integration with Media Types

It is possible to serialize event data using the media support. For example, if JSON-P is available in your class path, you can create an SSE event from a JsonObject and Helidon will find the appropriate media converter and serialize the event data on your behalf.

JsonObject json = Json.createObjectBuilder()
        .add("hello", "world")
        .build();
try (SseSink sseSink = res.sink(SseSink.TYPE)) {
    sseSink.emit(SseEvent.create(json));
}
Copied

Similarly, if JSON-B support is available in your class path, an event can be created from an arbitrary Java class and serialized as shown next:

class HelloWorld {

    private String hello;

    public String getHello() {
        return hello;
    }

    public void setHello(String hello) {
        this.hello = hello;
    }
}

void handle(ServerRequest req, ServerResponse res) {
    HelloWorld json = new HelloWorld();
    json.setHello("world");
    try (SseSink sseSink = res.sink(SseSink.TYPE)) {
        sseSink.emit(SseEvent.create(json));
    }
}
Copied

An optional media type can be specified alongside the event’s data, in case a different type of serialization is required or when multiple media converters are available in the class path. For example, when passing a Java instance, you may request XML instead of JSON serialization by using application/xml as the event’s media type.

Client API

The Client API is available as a loadable service in the Helidon WebClient. The following additional dependency is required to find and load the service in the WebClient.

Maven Coordinates

<dependency>
    <groupId>io.helidon.webclient</groupId>
    <artifactId>helidon-webclient-sse</artifactId>
</dependency>
Copied

Usage

Receiving events is accomplished by providing an SseSource handler using the source type SseSource.TYPE. An SseSource is a functional interface defined for the purpose of processing events. The following example, obtains an Http1ClientResponse from a request and registers an SseSource to process a single event.

try (Http1ClientResponse r = client.get("/sseJson")
        .header(ACCEPT_EVENT_STREAM)
        .request()) {
    CountDownLatch latch = new CountDownLatch(1);
    r.source(SseSource.TYPE, event -> {
        // ...
        latch.countDown();
    });
}
Copied

The SseSource type defines other methods such as onOpen, onClose and onError. The following example waits for zero or more string events until the connection is closed. A CountDownLatch is a convenient way to asynchronously wait until all the events are received.

try (Http1ClientResponse r = client.get("/sseString")
        .header(ACCEPT_EVENT_STREAM)
        .request()) {
    CountDownLatch latch = new CountDownLatch(1);
    r.source(SseSource.TYPE, new SseSource() {
        @Override
        public void onEvent(SseEvent event) {
            // ...
        }

        @Override
        public void onClose() {
            latch.countDown();
        }
    });
    assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
Copied

Integration with Media Types

The Client API is also integrated with media type support. The data received as part of an event can be deserialized using any of the media converters available in your class path. There are special methods in SseEvent for this purpose. Without a parameter, the method data() in SseEvent will always return a string. Other types can be requested using data(Class<T>) and data(Class<T>, MediaType). The latter is necessary to select the correct media converter given that there is no (standard) content type available as part of each event --but only a single text/event-stream content type for the whole response.

For example, to convert an event into a Java instance using JSON-B, the application/json media type is required as a second parameter --the first parameter HelloWorld.class simply does not convey sufficient information to select the appropriate converter for the event’s data in this case.

try (Http1ClientResponse r = client.get("/sseJson")
        .header(ACCEPT_EVENT_STREAM)
        .request()) {
    CountDownLatch latch = new CountDownLatch(1);
    r.source(SseSource.TYPE, event -> {
        HelloWorld json = event.data(HelloWorld.class, MediaTypes.APPLICATION_JSON);
        // ...
        latch.countDown();
    });
}
Copied

Additional Information

The Server-sent events specification.