Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New document that describes Nima's SSE APIs #6332

Merged
merged 4 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions docs/nima/sse/sse.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
///////////////////////////////////////////////////////////////////////////////

Copyright (c) 2023 Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

///////////////////////////////////////////////////////////////////////////////

= Server-Sent Events in Níma
:description: Helidon Nima SSE Support
:keywords: helidon, nima, sse
:feature-name: SSE
:rootdir: {docdir}/../..

== Contents

- <<Overview, Overview>>
- <<Server API, Server API>>
spericas marked this conversation as resolved.
Show resolved Hide resolved
- <<Maven Coordinates, Maven Coordinates>>
- <<Usage, Usage>>
- <<Client API, Client API>>
spericas marked this conversation as resolved.
Show resolved Hide resolved
- <<Maven Coordinates, Maven Coordinates>>
- <<Usage, Usage>>
- <<Additional Information, Additional Information>>

== 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
link:https://html.spec.whatwg.org/multipage/server-sent-events.html[`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.

N&iacute;ma 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 N&iacute;ma Webserver. The following additional
dependency is required to find and load the SSE service in the Webserver.

spericas marked this conversation as resolved.
Show resolved Hide resolved
=== Maven Coordinates

[source,xml,subs="attributes+"]
----
<dependency>
<groupId>io.helidon.nima.sse</groupId>
<artifactId>helidon-nima-sse-webserver</artifactId>
</dependency>
----

spericas marked this conversation as resolved.
Show resolved Hide resolved
=== 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.

[source,java]
----
void sseString(ServerRequest req, ServerResponse res) {
try (SseSink sseSink = res.sink(SseSink.TYPE)) {
sseSink.emit(SseEvent.create("hello"))
.emit(SseEvent.create("world"));
}
}
----

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 Nima's 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 in N&iacute;ma. For example, if JSON-P is available
in your class path, you can create an SSE event from a `JsonObject` and N&iacute;ma will find the appropriate media
converter and serialize the event data on your behalf.

[source,java]
----
void sseJsonp(ServerRequest req, ServerResponse res) {
JsonObject json = Json.createObjectBuilder()
.add("hello", "world")
.build();
try (SseSink sseSink = res.sink(SseSink.TYPE)) {
sseSink.emit(SseEvent.create(json));
}
}
----

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:

[source,java]
----
class HelloWorld {

private String hello;

public String getHello() {
return hello;
}

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

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

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 Nima Webclient. The following additional
dependency is required to find and load the service in the Webclient.

spericas marked this conversation as resolved.
Show resolved Hide resolved
=== Maven Coordinates

[source,xml,subs="attributes+"]
----
<dependency>
<groupId>io.helidon.nima.sse</groupId>
<artifactId>helidon-N&iacute;ma-sse-webclient</artifactId>
</dependency>
----

spericas marked this conversation as resolved.
Show resolved Hide resolved
=== 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.

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

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.

[source,java]
----
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));
}
----

=== Integration with Media Types

spericas marked this conversation as resolved.
Show resolved Hide resolved
The Client API is also integrated with N&iacute;ma's 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.

[source,java]
----
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();
});
}
----

== Additional Information
The link:https://html.spec.whatwg.org/multipage/server-sent-events.html[`Server-sent events`] specification.
6 changes: 6 additions & 0 deletions docs/sitegen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ backend:
glyph:
type: "icon"
value: "lightbulb"
- type: "PAGE"
title: "Server-Sent Events"
source: "sse/sse.adoc"
glyph:
type: "icon"
value: "reply-all"
- type: "MENU"
title: "Guides"
dir: "guides"
Expand Down