|  | <?xml version="1.0"?> | 
|  | <!-- | 
|  |  | 
|  | Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. | 
|  |  | 
|  | This program and the accompanying materials are made available under the | 
|  | terms of the Eclipse Public License v. 2.0, which is available at | 
|  | http://www.eclipse.org/legal/epl-2.0. | 
|  |  | 
|  | This Source Code may also be made available under the following Secondary | 
|  | Licenses when the conditions for such availability set forth in the | 
|  | Eclipse Public License v. 2.0 are satisfied: GNU General Public License, | 
|  | version 2 with the GNU Classpath Exception, which is available at | 
|  | https://www.gnu.org/software/classpath/license.html. | 
|  |  | 
|  | SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | 
|  |  | 
|  | --> | 
|  |  | 
|  | <!DOCTYPE chapter [<!ENTITY % ents SYSTEM "jersey.ent" > %ents; ]> | 
|  | <chapter xmlns="http://docbook.org/ns/docbook" | 
|  | version="5.0" | 
|  | xml:lang="en" | 
|  | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 
|  | xmlns:xi="http://www.w3.org/2001/XInclude" | 
|  | xmlns:xlink="http://www.w3.org/1999/xlink" | 
|  | xsi:schemaLocation="http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd | 
|  | http://www.w3.org/1999/xlink http://www.w3.org/1999/xlink.xsd" | 
|  | xml:id="sse"> | 
|  |  | 
|  | <title>Server-Sent Events (SSE) Support</title> | 
|  |  | 
|  | <section> | 
|  | <title>What are Server-Sent Events</title> | 
|  |  | 
|  | <para> | 
|  | In a standard HTTP request-response scenario a client opens a connection, sends a HTTP request to the server (for | 
|  | example a HTTP &lit.http.GET; request), then receives a HTTP response back and the server closes the connection once | 
|  | the response is fully sent/received. The initiative <emphasis>always</emphasis> comes from a client when the client | 
|  | requests all the data. In contrast, <emphasis>Server-Sent Events (SSE)</emphasis> is a mechanism that allows server | 
|  | to asynchronously push the data from the server to the client once the client-server connection is established by the | 
|  | client. Once the connection is established by the client, it is the server who provides the data and decides | 
|  | to send it to the client whenever new "chunk" of data is available. When a new data event occurs on the server, | 
|  | the data event is sent by the server to the client. Thus the name Server-Sent Events. Note that at high level there | 
|  | are more technologies working on this principle, a short overview of the technologies supporting server-to-client | 
|  | communication is in this list: | 
|  |  | 
|  | <variablelist> | 
|  | <varlistentry> | 
|  | <term>Polling</term> | 
|  | <listitem> | 
|  | <para> | 
|  | With polling a client repeatedly sends new requests to a server. If the server has no new data, | 
|  | then it send appropriate indication and closes the connection. The client then waits a bit and sends | 
|  | another request after some time (after one second, for example). | 
|  | </para> | 
|  | </listitem> | 
|  | </varlistentry> | 
|  | <varlistentry> | 
|  | <term>Long-polling</term> | 
|  | <listitem> | 
|  | <para> | 
|  | With long-polling a client sends a request to a server. If the server has no new data, | 
|  | it just holds the connection open and waits until data is available. Once the server has data | 
|  | (message) for the client, it uses the connection and sends it back to the client. Then the connection | 
|  | is closed. | 
|  | </para> | 
|  | </listitem> | 
|  | </varlistentry> | 
|  | <varlistentry> | 
|  | <term>Server-Sent events</term> | 
|  | <listitem> | 
|  | <para> | 
|  | SSE is similar to the long-polling mechanism, except it does not send only one message per connection. | 
|  | The client sends a request and server holds a connection until a new message is ready, then it sends | 
|  | the message back to the client while still keeping the connection open so that it can be used | 
|  | for another message once it becomes available. Once a new message is ready, it is sent back to the | 
|  | client on the same initial connection. Client processes the messages sent back from the server | 
|  | individually without closing the connection after processing each message. | 
|  | So, SSE typically reuses one connection for more messages (called events). SSE also defines a | 
|  | dedicated media type that describes a simple format of individual events sent from the server to the | 
|  | client. SSE also offers standard javascript client API implemented most modern browsers. For more | 
|  | information about SSE, see the | 
|  | <link xlink:href='https://www.w3.org/TR/eventsource/'>SSE API specification</link>. | 
|  | </para> | 
|  | </listitem> | 
|  | </varlistentry> | 
|  | <varlistentry> | 
|  | <term>WebSocket</term> | 
|  | <listitem> | 
|  | <para> | 
|  | WebSocket technology is different from previous technologies as it provides a real full duplex | 
|  | connection. The initiator is again a client which sends a request to a server with a special HTTP | 
|  | header that informs the server that the HTTP connection may be "upgraded" to a full duplex TCP/IP | 
|  | WebSocket connection. If server supports WebSocket, it may choose to do so. Once a WebSocket | 
|  | connection is established, it can be used for bi-directional communication between the client and the | 
|  | server. Both client and server can then send data to the other party at will whenever it is needed. | 
|  | The communication on the new WebSocket connection is no longer based on HTTP protocol and can be | 
|  | used for example for for online gaming or any other applications that require fast exchange of small | 
|  | chunks of data in flowing in both directions. | 
|  | </para> | 
|  | </listitem> | 
|  | </varlistentry> | 
|  | </variablelist> | 
|  | </para> | 
|  | </section> | 
|  |  | 
|  | <section> | 
|  | <title>When to use Server-Sent Events</title> | 
|  |  | 
|  | <para> | 
|  | As explained above, SSE is a technology that allows clients to subscribe to event notifications that originate on | 
|  | a server. Server generates new events and sends these events back to the clients subscribed to receive the | 
|  | notifications. In other words, SSE offers a solution for a one-way publish-subscribe model. | 
|  | </para> | 
|  | <para> | 
|  | A good example of the use case where SSE can be used is a simple message exchange RESTful service. Clients | 
|  | &lit.http.POST; new messages to the service and subscribe to receive messages from other clients. | 
|  | Let's call the resource <literal>messages</literal>. While &lit.http.POST;ing a new message to this resource involves | 
|  | a typical HTTP request-response communication between a client and the <literal>messages</literal> resource, | 
|  | subscribing to receive all new message notifications would be hard and impractical to model with a sequence of | 
|  | standard request-response message exchanges. Using Server-sent events provides a much more practical approach here. | 
|  | You can use SSE to let clients subscribe to the <literal>messages</literal> resource via standard &lit.http.GET; | 
|  | request (use a SSE client API, for example javascript API or Jersey Client SSE API) and let the server broadcast | 
|  | new messages to all connected clients in the form of individual events (in our case using Jersey Server SSE API). | 
|  | Note that with Jersey a SSE support is implemented as an usual JAX-RS resource method. There's no need to do anything | 
|  | special to provide a SSE support in your Jersey/JAX-RS applications, your SSE-enabled resources are a standard part of | 
|  | your RESTful Web application that defines the REST API of your application. The following chapters describes SSE | 
|  | support in Jersey in more details. | 
|  | </para> | 
|  |  | 
|  | </section> | 
|  |  | 
|  | <section xml:id="jaxrs-sse-api-overview"> | 
|  | <title>Server-Sent Events API</title> | 
|  | <para> | 
|  | In previous JAX-RS versions, no standard API for server-sent events was defined. The SSE support bundled with | 
|  | Jersey was Jersey-specific. With JAX-RS 2.1 (with respect to namespace change after jakartification), | 
|  | situation changed and SSE API is well defined in the <literal>jakarta.ws.rs.sse</literal> package. | 
|  | </para> | 
|  | <para>Following chapters will describe the new SSE API. For backwards compatibility reasons, the original | 
|  | Jersey-specific API remains valid and will be described in | 
|  | <xref linkend="overview-jersey-specific"/> | 
|  | </para> | 
|  | <para> | 
|  | Jersey contains support for SSE for both - server and client. SSE in Jersey is implemented as an extension | 
|  | supporting a new media type using existing "chunked" messages support. However, in contrast to the original API, | 
|  | the instances of SSE related classes are not to be obtained manually by invoking constructors, nor to be directly | 
|  | returned from the resource methods. | 
|  | Actually, the implementing classes in the <literal>jersey.media.sse.internal</literal> package should never be needed | 
|  | to be imported. The only API to be used is directly in the JAX-RS package (<literal>jakarta.ws.rs.sse</literal>). | 
|  | Only builders in the API along with dependency injection should be used and provides access to the entire | 
|  | functionality. | 
|  | </para> | 
|  | <para> | 
|  | In order to take advantage of the SSE support, the <literal>jersey-media-sse</literal> module has to be on classpath. | 
|  | In maven, this can be achieved by adding the dependency to the <emphasis>SSE media type module</emphasis>: | 
|  | <example xml:id="sse-dependency-jaxrs"> | 
|  | <title>Adding the SSE dependency</title> | 
|  | <programlisting language="xml" linenumbering="numbered"><![CDATA[<dependency> | 
|  | <groupId>org.glassfish.jersey.media</groupId> | 
|  | <artifactId>jersey-media-sse</artifactId> | 
|  | </dependency>]]></programlisting> | 
|  | </example> | 
|  | The &lit.jaxrs.core.Feature; defined in the module is (forced) auto-discoverable, which means having the module on | 
|  | classpath is sufficient, no need to further register it in the code. | 
|  | </para> | 
|  | </section> | 
|  | <section> | 
|  | <title>Implementing SSE support in a JAX-RS resource (with JAX-RS SSE API)</title> | 
|  | <section> | 
|  | <title>Simple SSE resource method</title> | 
|  | <example xml:id="example-simple-sse-jaxrs"> | 
|  | <title>Simple SSE resource method</title> | 
|  | As mentioned above, the SSE related are not instantiated directly. In this case, Jersey takes care of the | 
|  | dependencies and injects the &jaxrs21.sse.SseEventSink; (represents the output) and &jaxrs21.sse.Sse; (provides | 
|  | factory methods for other SSE related types, in this case it is used to retrieve the event builder). | 
|  | <programlisting language="java" linenumbering="numbered">... | 
|  | import jakarta.ws.rs.sse.Sse; | 
|  | import jakarta.ws.rs.sse.SseEventSink; | 
|  | import jakarta.ws.rs.sse.OutboundSseEvent; | 
|  | ... | 
|  |  | 
|  | @Path("events") | 
|  | public static class SseResource { | 
|  |  | 
|  | @GET | 
|  | @Produces(MediaType.SERVER_SENT_EVENTS) | 
|  | public void getServerSentEvents(@Context SseEventSink eventSink, @Context Sse sse) { | 
|  | new Thread(() -> { | 
|  | for (int i = 0; i < 10; i++) { | 
|  | // ... code that waits 1 second | 
|  | final OutboundSseEvent event = sse.newEventBuilder() | 
|  | .name("message-to-client") | 
|  | .data(String.class, "Hello world " + i + "!") | 
|  | .build(); | 
|  | eventSink.send(event); | 
|  | } | 
|  | }).start(); | 
|  | } | 
|  | } | 
|  | </programlisting> | 
|  | </example> | 
|  | <para> | 
|  | The code above defines the resource deployed on URI "/events". This resource has a single | 
|  | <literal>@GET</literal> | 
|  | resource method which <emphasis>returns void</emphasis>. This is an imported difference | 
|  | against the original API. It is Jersey's responsibility to bind the injected <literal>SseEventSink</literal> to | 
|  | the output chain. | 
|  | </para> | 
|  | <para> | 
|  | After the <literal>SseEventInput</literal> is "returned" from the method, the Jersey runtime recognizes that this | 
|  | is a &lit.jersey.server.ChunkedOutput; extension and does not close the client connection immediately. Instead, it | 
|  | writes the HTTP headers to the response stream and waits for more chunks (SSE events) to be sent. At this point | 
|  | the client can read headers and starts listening for individual events. | 
|  | </para> | 
|  | <para> | 
|  | In the <xref linkend="example-simple-sse-jaxrs"/>, the resource method creates a new thread that sends a | 
|  | sequence of 10 events. There is a 1 second delay between two subsequent events as indicated in a comment. Each | 
|  | event is represented by <literal>jakarta.ws.rs.sse.OutboundSseEvent</literal> type and is built with a help of a | 
|  | provided <literal>Builder</literal>. The <literal>Builder</literal> is obtain via the injected instance | 
|  | (actually, it is a singleton) of <literal>jakarta.ws.rs.sse.Sse</literal> (the | 
|  | <literal>newEventBuilder()</literal> | 
|  | method). The <literal>OutboundSseEvent</literal> implementation reflects the standardized format of | 
|  | SSE messages and contains properties that represent <literal>name</literal> (for named events), | 
|  | <literal>comment</literal>, <literal>data</literal> or <literal>id</literal>. The code also sets the | 
|  | event data media type using the <literal>mediaType(MediaType)</literal> method on the | 
|  | <literal>eventBuilder</literal>. The media type, together with the data type set by the | 
|  | <literal>data(Class, Object)</literal> | 
|  | method (in our case <literal>String.class</literal>), is used | 
|  | for serialization of the event data. Note that the event data media type will not be written to any headers as | 
|  | the response <literal>Content-type</literal> header is already defined by the &lit.jaxrs.Produces; and set to | 
|  | <literal>"text/event-stream"</literal> | 
|  | using constant from the &lit.jaxrs.core.MediaType;. | 
|  | The event media type is used for serialization of event <literal>data</literal>. Event data media type and Java | 
|  | type are used to select the proper &jaxrs.ext.MessageBodyWriter; for event data serialization and are passed | 
|  | to the selected writer that serializes the event <literal>data</literal> content. In our case the string | 
|  | <literal>"Hello world " + i + "!"</literal> | 
|  | is serialized as <literal>"text/plain"</literal>. In event | 
|  | <literal>data</literal> | 
|  | you can send any Java entity and associate it with any media type that you would be able | 
|  | to serialize with an available &lit.jaxrs.ext.MessageBodyWriter;. Typically, you may want to send e.g. JSON data, | 
|  | so you would fill the <literal>data</literal> with a JAXB annotated bean instance and define media type to JSON. | 
|  | <note> | 
|  | <para> | 
|  | If the event media type is not set explicitly, the <literal>"text/plain"</literal> media type is used | 
|  | by default. | 
|  | </para> | 
|  | </note> | 
|  | </para> | 
|  | <para> | 
|  | Once an outbound event is ready, it can be written to the <literal>EventSink</literal>. At that point the event | 
|  | is serialized by internal &lit.jersey.sse.OutboundEventWriter; which uses an appropriate | 
|  | &lit.jaxrs.ext.MessageBodyWriter; to serialize the <literal>"Hello world " + i + "!"</literal> string. You can | 
|  | send as many messages as you like. At the end of the thread execution the response is closed which also closes | 
|  | the connection to the client. After that, no more messages can be sent to the client on this connection. If the | 
|  | client would like to receive more messages, it would have to send a new request to the server to initiate a | 
|  | new SSE streaming connection. | 
|  | </para> | 
|  | <para> | 
|  | A client connecting to our SSE-enabled resource will receive the following data from the entity stream: | 
|  |  | 
|  | <screen language="text" linenumbering="unnumbered">event: message-to-client | 
|  | data: Hello world 0! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 1! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 2! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 3! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 4! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 5! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 6! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 7! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 8! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 9! | 
|  | </screen> | 
|  |  | 
|  | Each message is received with a delay of one second. | 
|  | </para> | 
|  | <note> | 
|  | <para> | 
|  | If you have worked with streams in JAX-RS, you may wonder what is the difference between | 
|  | &jersey.server.ChunkedOutput; and &jaxrs.core.StreamingOutput;. | 
|  | </para> | 
|  | <para> | 
|  | &lit.jersey.server.ChunkedOutput; is Jersey-specific API. It lets you send "chunks" of data without closing | 
|  | the client connection using series of convenient calls to <literal>ChunkedOutput.write</literal> methods | 
|  | that take POJO + chunk media type as an input and then use the configured JAX-RS | 
|  | &lit.jaxrs.ext.MessageBodyWriter; providers to figure out the proper way of serializing each chunk POJO | 
|  | to bytes. Additionally, &lit.jersey.server.ChunkedOutput; writes can be invoked multiple times on the same | 
|  | outbound response connection, i.e. individual chunks are written in each write, not the full response entity. | 
|  | </para> | 
|  | <para> | 
|  | &lit.jaxrs.core.StreamingOutput; is, on the other hand, a low level JAX-RS API that works with bytes | 
|  | directly. You have to implement &lit.jaxrs.core.StreamingOutput; interface yourself. Also, its | 
|  | <literal>write(OutputStream)</literal> | 
|  | method will be invoked by JAX-RS runtime only once per response | 
|  | and the call to this method is blocking, i.e. the method is expected to write the entire entity body | 
|  | before returning. | 
|  | </para> | 
|  | </note> | 
|  | </section> | 
|  | <section> | 
|  | <title>Broadcasting with Jersey SSE</title> | 
|  |  | 
|  | <para> | 
|  | JAX-RS SSE API defines &jersey.sse.SseBroadcaster; which allows to broadcast individual events to multiple | 
|  | clients. A simple broadcasting implementation is shown in the following example: | 
|  |  | 
|  | <example> | 
|  | <title>Broadcasting SSE messages (with JAX-RS 3.0 API)</title> | 
|  | <programlisting language="java" linenumbering="numbered">... | 
|  | import jakarta.ws.rs.sse.Sse; | 
|  | import jakarta.ws.rs.sse.SseEventSink; | 
|  | import jakarta.ws.rs.sse.SseBroadcaster; | 
|  | ... | 
|  |  | 
|  | @Singleton | 
|  | @Path("broadcast") | 
|  | public static class BroadcasterResource { | 
|  | private Sse sse; | 
|  | private SseBroadcaster broadcaster; | 
|  |  | 
|  | public BroadcasterResource(@Context final Sse sse) { | 
|  | this.sse = sse; | 
|  | this.broadcaster = sse.newBroadcaster(); | 
|  | } | 
|  |  | 
|  | @POST | 
|  | @Produces(MediaType.TEXT_PLAIN) | 
|  | @Consumes(MediaType.TEXT_PLAIN) | 
|  | public String broadcastMessage(String message) { | 
|  | final OutboundSseEvent event = sse.newEventBuilder() | 
|  | .name("message") | 
|  | .mediaType(MediaType.TEXT_PLAIN_TYPE) | 
|  | .data(String.class, message) | 
|  | .build(); | 
|  |  | 
|  | broadcaster.broadcast(event); | 
|  |  | 
|  | return "Message '" + message + "' has been broadcast."; | 
|  | } | 
|  |  | 
|  | @GET | 
|  | @Produces(MediaType.SERVER_SENT_EVENTS) | 
|  | public void listenToBroadcast(@Context SseEventSink eventSink) { | 
|  | this.broadcaster.register(eventSink); | 
|  | } | 
|  | } | 
|  | </programlisting> | 
|  | </example> | 
|  | Let's explore the example together. The <literal>BroadcasterResource</literal> resource class is annotated with | 
|  | &jee9.inject.Singleton; annotation which tells Jersey runtime that only a single instance of the resource | 
|  | class should be used to serve all the incoming requests to <literal>/broadcast</literal> path. This is needed as | 
|  | we want to keep an application-wide single reference to the private <literal>broadcaster</literal> field so | 
|  | we can use the same instance for all requests. Clients that want to listen to SSE events first send a | 
|  | &lit.http.GET; request to the <literal>BroadcasterResource</literal>, that is handled by the | 
|  | <literal>listenToBroadcast()</literal> | 
|  | resource method. | 
|  | The method is injected with a new <literal>SseEventSink</literal> representing the connection to the | 
|  | requesting client and registers this <literal>eventSink</literal> instance with the singleton | 
|  | <literal>broadcaster</literal> | 
|  | by calling its <literal>subscribe()</literal> method. | 
|  | The method then, as already explained returns <literal>void</literal> and Jersey runtime is responsible for | 
|  | binding the injected <literal>EventSink</literal> instance so as it would have been returned from the resource | 
|  | method (note that really returning the <literal>EventSink</literal> from the resource method will cause | 
|  | failure) and to bind the <literal>eventSink</literal> instance with the requesting client and send the | 
|  | response HTTP headers to the client. The client connection remains open and the client is now waiting ready to | 
|  | receive new SSE events. All the events are written to the <literal>eventSink</literal> by | 
|  | <literal>broadcaster</literal> | 
|  | later on. This way developers can conveniently handle sending new events to | 
|  | all the clients that subscribe to them. | 
|  | </para> | 
|  | <para> | 
|  | When a client wants to broadcast new message to all the clients listening on their SSE connections, | 
|  | it sends a &lit.http.POST; request to <literal>BroadcasterResource</literal> resource with the message content. | 
|  | The method <literal>broadcastMessage(String)</literal> is invoked on | 
|  | <literal>BroadcasterResource</literal> | 
|  | resource with the message content as an input parameter. A new SSE outbound event is built in the standard way | 
|  | and passed to the broadcaster. The broadcaster internally invokes <literal>write(OutboundEvent)</literal> on all | 
|  | registered <literal>EventSink</literal>s. After that the method just returns a standard text response | 
|  | to the &lit.http.POST;ing client to inform the client that the message was successfully broadcast. As you can see, | 
|  | the <literal>broadcastMessage(String)</literal> resource method is just a simple JAX-RS resource method. | 
|  | </para> | 
|  | <!-- TODO continue here --> | 
|  | <para> | 
|  | In order to implement such a scenario, you may have noticed, that the | 
|  | <literal>SseBroadcaster</literal> | 
|  | is not mandatory to complete the use case. Individual <literal>EventSink</literal>s can be just stored in | 
|  | a collection and iterated over in the <literal>broadcastMessage</literal> method. However, the | 
|  | <literal>SseBroadcaster</literal> | 
|  | internally identifies and handles also client disconnects. When a client | 
|  | closes the connection, the broadcaster detects this and removes the stale connection from the internal collection | 
|  | of the registered <literal>EventSink</literal>s as well as it frees all the server-side resources associated with | 
|  | the stale connection. | 
|  | Additionally, the <literal>SseBroadcaster</literal> is implemented to be thread-safe, so that clients can connect | 
|  | and disconnect at any time and <literal>SseBroadcaster</literal> will always broadcast messages to the most recent | 
|  | collection of registered and active set of clients. | 
|  | </para> | 
|  | </section> | 
|  | </section> | 
|  | <section xml:id="sse-client-jaxrs"> | 
|  | <title>Consuming SSE events within Jersey clients</title> | 
|  | <para> | 
|  | On the client side, push programming model is used (event consumer / client) gets asynchronously notified about | 
|  | incoming events by subscribing custom listener to <literal>jakarta.ws.rs.sse.SseEventSource</literal>. This happens by | 
|  | invoking one of its <literal>subscribe()</literal> methods. | 
|  | </para> | 
|  | <para> | 
|  | The usage of <literal>SseEventSource</literal> is shown in the following example. | 
|  | <example xml:id="sse-event-source-example"> | 
|  | <title>Consuming SSE events with SseEventSource</title> | 
|  | <programlisting language="java" linenumbering="numbered">import jakarta.ws.rs.sse.SseEventSource; | 
|  | ... | 
|  | Client client = ClientBuilder.newBuilder().build(); | 
|  | WebTarget target = client.target("http://example.com/events"); | 
|  | SseEventSource sseEventSource = SseEventSource.target(target).build(); | 
|  | sseEventSource.register(event -> System.out.println(event.getName() + "; " | 
|  | + event.readData(String.class))); | 
|  | sseEventSource.open(); | 
|  |  | 
|  | // do other stuff, block here and continue when done | 
|  |  | 
|  | sseEventSource.close(); | 
|  | </programlisting> | 
|  | </example> | 
|  | In this example, the client code connects to the server where the <literal>SseResource</literal> from the | 
|  | <xref linkend="example-simple-sse-jaxrs"/> | 
|  | is deployed. The &jaxrs.client.Client; instance | 
|  | is created (and initialized with &jersey.sse.SseFeature; automatically). Then the &jaxrs.client.WebTarget; is built. | 
|  | In this case a request to the web target is not made directly in the code, instead, the web target instance | 
|  | is used to initialize a new &jakarta.ws.rs.sse.SseEventSource.Builder; instance that is used to build a new | 
|  | &jakarta.ws.rs.sse.SseEventSource;. The choice of <literal>build()</literal> method is important, as it tells | 
|  | the <literal>SseEventSource.Builder</literal> to create a new <literal>SseEventSource</literal> that is not | 
|  | automatically connected to the <literal>target</literal>. The connection is established only later by manually | 
|  | invoking the <literal>sseEventSource.open()</literal> method. A custom | 
|  | <literal>java.util.function.Consumer<InboundSseEvent></literal> | 
|  | implementation is used to listen to and | 
|  | process incoming SSE events. The method readData(Class) says that the | 
|  | event data should be de-serialized from a received &jakarta.ws.rs.sse.InboundSseEvent; instance into a | 
|  | <literal>String</literal> | 
|  | Java type. This method call internally executes &jaxrs.ext.MessageBodyReader; which | 
|  | de-serializes the event data. This is similar to reading an entity from the &jaxrs.core.Response; by | 
|  | <literal>readEntity(Class)</literal>. The method <literal>readData</literal> can throw a | 
|  | &jaxrs.ProcessingException;. | 
|  | </para> | 
|  | <para> | 
|  | After a connection to the server is opened by calling the <literal>open()</literal> method on the event source, | 
|  | the <literal>eventSource</literal> starts listening to events. When an event comes, the listener will be executed | 
|  | by the event source. Once the client is done with processing and does not want to receive events the connection by | 
|  | calling the <literal>close()</literal> method on the event source. | 
|  | </para> | 
|  | <para> | 
|  | The listener from the example above will print the following output: | 
|  | <screen language="text" linenumbering="unnumbered">message-to-client; Hello world 0! | 
|  | message-to-client; Hello world 1! | 
|  | message-to-client; Hello world 2! | 
|  | message-to-client; Hello world 3! | 
|  | message-to-client; Hello world 4! | 
|  | message-to-client; Hello world 5! | 
|  | message-to-client; Hello world 6! | 
|  | message-to-client; Hello world 7! | 
|  | message-to-client; Hello world 8! | 
|  | message-to-client; Hello world 9! | 
|  | </screen> | 
|  | </para> | 
|  | <para> | 
|  | There are other events than the incoming data that also may occur. The <literal>SseEventSource</literal> for | 
|  | instance always signals, that it has finished processing events, or there might also be an error while processing the | 
|  | messages. <literal>SseEventSource</literal>. There are total of four overloaded | 
|  | <literal>subscribe()</literal> | 
|  | methods defined in the API. | 
|  | </para> | 
|  | <para> | 
|  | <example xml:id="sse-event-source-subscribe-methods"> | 
|  | <title>SseEventSource subscribe() methods</title> | 
|  | <programlisting language="java" linenumbering="numbered">// 1. basic one - the one we used in the example | 
|  | void subscribe(Consumer<InboundSseEvent> onEvent); | 
|  |  | 
|  | // 2. with an error callback | 
|  | void subscribe(Consumer<InboundSseEvent> onEvent, Consumer<Throwable> onError); | 
|  |  | 
|  | // 3. with an error callback and completion callback | 
|  | void subscribe(Consumer<InboundSseEvent> onEvent, Consumer<Throwable> onError, Runnable onComplete) | 
|  |  | 
|  | // 4. complete one - with error callback, completion callback an onSubscribe callback | 
|  | void subscribe(Consumer<SseSubscription> onSubscribe, Consumer<InboundSseEvent> onEvent, Consumer<Throwable> | 
|  | onError, | 
|  | Runnable | 
|  | onComplete); | 
|  | </programlisting> | 
|  | </example> | 
|  |  | 
|  | Few notes to the <literal>subscribe()</literal> methods: | 
|  | <itemizedlist> | 
|  | <listitem> | 
|  | <para> | 
|  | All the overloaded methods have the <literal>onEvent</literal> handler. As shown in the example, | 
|  | this parameter is used to consume the SSE events with data. | 
|  | </para> | 
|  | </listitem> | 
|  | <listitem> | 
|  | <para> | 
|  | Except the basic one-arg method, all the others contain an <literal>onError</literal> handler. In | 
|  | case of error, the <literal>SseEventSource</literal> invokes the <literal>onError</literal> method | 
|  | of all its subscribers, that registered the handler. This makes it possible to react to the error | 
|  | conditions in a custom manner. | 
|  | </para> | 
|  | </listitem> | 
|  | <listitem> | 
|  | <para> | 
|  | Another possible argument is the <literal>onComplete</literal> handler. If registered (used an | 
|  | appropriate <literal>subscribe()</literal> method, that has the | 
|  | <literal>onComplete</literal> | 
|  | argument), it is invoked (for all the subscribers) every time when the | 
|  | <literal>SseEventSource</literal> | 
|  | terminates normally. Either <literal>onComplete</literal> or | 
|  | <literal>onError</literal> | 
|  | should be called every time. | 
|  | </para> | 
|  | </listitem> | 
|  | <listitem> | 
|  | <para> | 
|  | The complete <literal>subscribe()</literal> method adds the <literal>onSubscribe()</literal> callback. | 
|  | This gives the subscriber a tool to manage the load and do a back-pressure by incrementally | 
|  | requesting only certain amount of items. When <literal>SseEventSource</literal> registers a new | 
|  | subscriber, it calls its <literal>onSubscribe</literal> handler and hands over the | 
|  | <literal>jakarta.ws.rs.sse.SseSubscription</literal> | 
|  | instance. This class only has two methods - | 
|  | <literal>request(long)</literal> | 
|  | for asking for a certain amount of events (often used as | 
|  | <literal>request(Long.MAX_VALUE)</literal> | 
|  | when no back-pressure is needed) and | 
|  | <literal>cancel()</literal> | 
|  | to stop receiving further events. | 
|  | </para> | 
|  | </listitem> | 
|  | <listitem> | 
|  | <para> | 
|  | When using the full-arg version of <literal>subscribe()</literal>, it is the caller's | 
|  | responsibility to manage the amount of data it can handle. The | 
|  | <literal>sseSubscription.request()</literal> | 
|  | method <emphasis>MUST</emphasis> be called, otherwise | 
|  | the subscriber will not receive ANY data. Furthermore, in the current | 
|  | <literal>SseEventSource</literal> | 
|  | implementation, such a subscriber will block a thread and will | 
|  | occasionally lead to overflow of an internal buffer in <literal>SseEventSource</literal>. As | 
|  | mentioned, calling <literal>subscription.request(Long.MAX_VALUE)</literal>, e.g. in the registered | 
|  | <literal>onSubscribe</literal> | 
|  | handler is sufficient (and is also a default behaviour for all the | 
|  | other overloaded methods). | 
|  | </para> | 
|  | </listitem> | 
|  | </itemizedlist> | 
|  |  | 
|  |  | 
|  |  | 
|  | </para> | 
|  | <section xml:id="sse-event-source-reconnect"> | 
|  | <title> | 
|  | <literal>SseEventSource</literal> | 
|  | reconnect support | 
|  | </title> | 
|  | <para> | 
|  | The &jakarta.ws.rs.sse.SseEventSource; implementation supports automated recuperation | 
|  | from a connection loss, including negotiation of delivery of any missed events based on the last received | 
|  | SSE event <literal>id</literal> field value, provided this field is set by the server and the negotiation | 
|  | facility is supported by the server. In case of a connection loss, the last received SSE event | 
|  | <literal>id</literal> | 
|  | field value is sent in the <literal>Last-Event-ID</literal> HTTP request | 
|  | header as part of a new connection request sent to the SSE endpoint. Upon a receipt of such reconnect request, | 
|  | the SSE endpoint that supports this negotiation facility is expected to replay all missed events. | 
|  | </para> | 
|  | <note> | 
|  | <para> | 
|  | Note, that SSE lost event negotiation facility is a best-effort mechanism which does not provide | 
|  | any guarantee that all events would be delivered without a loss. You should therefore not | 
|  | rely on receiving every single event and design your client application code accordingly. | 
|  | </para> | 
|  | </note> | 
|  | <para> | 
|  | By default, when a connection to the SSE endpoint is lost, the event source will use a default delay | 
|  | before attempting to reconnect to the SSE endpoint. The SSE endpoint can however control the client-side | 
|  | retry delay by including a special <literal>retry</literal> field value in any event sent to the client. | 
|  | Jersey &jakarta.ws.rs.sse.SseEventSource; implementation automatically tracks any received SSE event | 
|  | <literal>retry</literal> | 
|  | field values set by the endpoint and adjusts the reconnect delay accordingly, | 
|  | using the last received <literal>retry</literal> field value as the new reconnect delay. | 
|  | </para> | 
|  | <para> | 
|  | In addition to handling the standard connection losses, Jersey &jakarta.ws.rs.sse.SseEventSource; automatically | 
|  | deals with any <literal>HTTP 503 Service Unavailable</literal> responses received from the SSE endpoint, | 
|  | that include a <literal>Retry-After</literal> HTTP header with a valid value. The | 
|  | <literal>HTTP 503 + Retry-After</literal> | 
|  | technique is often used by HTTP endpoints as a means of | 
|  | connection and traffic throttling. In case a <literal>HTTP 503 + Retry-After</literal> response is received | 
|  | in return to a connection request from SSE endpoint, Jersey <literal>SseEventSource</literal> will automatically | 
|  | schedule a reconnect attempt and use the received <literal>Retry-After</literal> HTTP header value as a | 
|  | one-time override of the reconnect delay. | 
|  | </para> | 
|  |  | 
|  | </section> | 
|  | </section> | 
|  |  | 
|  | <section xml:id="overview-jersey-specific"> | 
|  | <title>Jersey-specific Server-Sent Events API</title> | 
|  | <important> | 
|  | <para> | 
|  | Prior to JAX-RS 2.1, server-sent events was not standardized and was optional and implementation-specific. | 
|  | Jersey provided its own, specific version of SSE implementation, that remains valid and functional to achieve | 
|  | backwards compatibility. This implementation is a Jersey-specific extension of JAX-RS (2.0) standard. It works | 
|  | with common JAX-RS resources the same way as the JAX-RS 2.1 based implementation does. | 
|  | </para> | 
|  | <para> | 
|  | After the jakartification Jersey complies with JAX-RS 3.0 and SSE implementation may not be backward compatible due to | 
|  | namespace change. When the 3.x version is being used, it's required to comply with JAX-RS 3.0 SSE spec. | 
|  | </para> | 
|  | </important> | 
|  | <para> | 
|  | This chapter briefly describes the Jersey-specific support for SSE, focusing on the differences against the new SSE | 
|  | implementation described in | 
|  | <xref linkend="overview-jaxrs"/> | 
|  | </para> | 
|  | <para> | 
|  | The API contains SSE support for both - server and client. To use the Jersey-specific SSE API, you need to | 
|  | add the dependency to the | 
|  | </para> | 
|  | <para> | 
|  | In order to add support for this SSE implementation, you also need to include the dependency to the | 
|  | <emphasis>SSE media type module</emphasis> | 
|  | the same way as for the JAX-RS SSE implementation. | 
|  | <example xml:id="sse.dependency"> | 
|  | <title>Add <literal>jersey-media-sse</literal> dependency. | 
|  | </title> | 
|  | <programlisting language="xml" linenumbering="numbered"><![CDATA[<dependency> | 
|  | <groupId>org.glassfish.jersey.media</groupId> | 
|  | <artifactId>jersey-media-sse</artifactId> | 
|  | </dependency>]]></programlisting> | 
|  | </example> | 
|  | <note> | 
|  | <para> | 
|  | Prior to Jersey 2.8, you had to manually register &jersey.sse.SseFeature; in your application. | 
|  | (The &lit.jersey.sse.SseFeature; is a feature that can be registered for both, the client and the server.) | 
|  | Since Jersey 2.8, the feature gets automatically discovered and registered when Jersey SSE module is | 
|  | put on the application's classpath. The automatic discovery and registration of SSE feature can be suppressed | 
|  | by setting &jersey.sse.SseFeature.DISABLE_SSE; property to <literal>true</literal>. | 
|  | The behavior can also be selectively suppressed in either client or server runtime by setting | 
|  | &jersey.sse.SseFeature.DISABLE_SSE_CLIENT; or &jersey.sse.SseFeature.DISABLE_SSE_SERVER; property | 
|  | respectively. | 
|  | </para> | 
|  | </note> | 
|  | </para> | 
|  |  | 
|  | <section> | 
|  | <title>Implementing SSE support in a JAX-RS resource</title> | 
|  | <section> | 
|  | <title>Simple SSE resource method</title> | 
|  | <para> | 
|  | <example xml:id="example-simple-sse"> | 
|  | <title>Simple SSE resource method</title> | 
|  | <programlisting language="java" linenumbering="numbered">... | 
|  | import org.glassfish.jersey.media.sse.EventOutput; | 
|  | import org.glassfish.jersey.media.sse.OutboundEvent; | 
|  | import org.glassfish.jersey.media.sse.SseFeature; | 
|  | ... | 
|  |  | 
|  | @Path("events") | 
|  | public static class SseResource { | 
|  |  | 
|  | @GET | 
|  | @Produces(SseFeature.SERVER_SENT_EVENTS) | 
|  | public EventOutput getServerSentEvents() { | 
|  | final EventOutput eventOutput = new EventOutput(); | 
|  | new Thread(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | try { | 
|  | for (int i = 0; i < 10; i++) { | 
|  | // ... code that waits 1 second | 
|  | final OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder(); | 
|  | eventBuilder.name("message-to-client"); | 
|  | eventBuilder.data(String.class, "Hello world " + i + "!"); | 
|  | final OutboundEvent event = eventBuilder.build(); | 
|  | eventOutput.write(event); | 
|  | } | 
|  | } catch (IOException e) { | 
|  | throw new RuntimeException("Error when writing the event.", e); | 
|  | } finally { | 
|  | try { | 
|  | eventOutput.close(); | 
|  | } catch (IOException ioClose) { | 
|  | throw new RuntimeException("Error when closing the event output.", ioClose); | 
|  | } | 
|  | } | 
|  | } | 
|  | }).start(); | 
|  | return eventOutput; | 
|  | } | 
|  | } | 
|  | </programlisting> | 
|  | </example> | 
|  |  | 
|  | The code above defines the resource deployed on URI <literal>"/events"</literal>. This resource has a single | 
|  | &jaxrs.GET; resource method which returns as an entity &jersey.sse.EventOutput; - an extension of generic | 
|  | Jersey | 
|  | &jersey.server.ChunkedOutput; API for output chunked message processing. | 
|  | </para> | 
|  | <para> | 
|  | In the <xref linkend="example-simple-sse"/>, the resource method creates a new thread that sends a sequence of | 
|  | 10 events. There is a 1 second delay between two subsequent events as indicated in a comment. Each event is | 
|  | represented by &lit.jersey.sse.OutboundEvent; type and is built with a help of an outbound event | 
|  | <literal>Builder</literal>. The &lit.jersey.sse.OutboundEvent; reflects the standardized format of SSE | 
|  | messages | 
|  | and contains properties that represent <literal>name</literal> (for named | 
|  | events), <literal>comment</literal>, <literal>data</literal> or <literal>id</literal>. The code also sets the | 
|  | event data media type using the <literal>mediaType(MediaType)</literal> method on the | 
|  | <literal>eventBuilder</literal>. The media type, together with the data type set by the | 
|  | <literal>data(Class, Object></literal> | 
|  | method (in our case <literal>String.class</literal>), is used | 
|  | for serialization of the event data. Note that the event data media type will not be written to any headers as | 
|  | the response <literal>Content-type</literal> header is already defined by the &lit.jaxrs.Produces; and set to | 
|  | <literal>"text/event-stream"</literal> | 
|  | using constant from the &lit.jersey.sse.SseFeature;. | 
|  | The event media type is used for serialization of event <literal>data</literal>. Event data media type and | 
|  | Java | 
|  | type are used to select the proper &jaxrs.ext.MessageBodyWriter; for event data serialization and are passed | 
|  | to the selected writer that serializes the event <literal>data</literal> content. In our case the string | 
|  | <literal>"Hello world " + i + "!"</literal> | 
|  | is serialized as <literal>"text/plain"</literal>. In event | 
|  | <literal>data</literal> | 
|  | you can send any Java entity and associate it with any media type that you would be able | 
|  | to serialize with an available &lit.jaxrs.ext.MessageBodyWriter;. Typically, you may want to send e.g. JSON | 
|  | data, | 
|  | so you would fill the <literal>data</literal> with a JAXB annotated bean instance and define media type to | 
|  | JSON. | 
|  | <note> | 
|  | <para> | 
|  | If the event media type is not set explicitly, the <literal>"text/plain"</literal> media type is used | 
|  | by default. | 
|  | </para> | 
|  | </note> | 
|  | </para> | 
|  | <para> | 
|  | Once an outbound event is ready, it can be written to the <literal>eventOutput</literal>. At that point the | 
|  | event | 
|  | is serialized by internal &lit.jersey.sse.OutboundEventWriter; which uses an appropriate | 
|  | &lit.jaxrs.ext.MessageBodyWriter; to serialize the <literal>"Hello world " + i + "!"</literal> string. You can | 
|  | send as many messages as you like. At the end of the thread execution the response is closed which also closes | 
|  | the connection to the client. After that, no more messages can be sent to the client on this connection. If | 
|  | the | 
|  | client would like to receive more messages, it would have to send a new request to the server to initiate a | 
|  | new SSE streaming connection. | 
|  | </para> | 
|  | <para> | 
|  | A client connecting to our SSE-enabled resource will receive the exact same output as in the corresponding | 
|  | example | 
|  | in the JAX-RS implementation example. | 
|  |  | 
|  | <screen language="text" linenumbering="unnumbered">event: message-to-client | 
|  | data: Hello world 0! | 
|  |  | 
|  | event: message-to-client | 
|  | data: Hello world 1! | 
|  |  | 
|  | ... | 
|  | </screen> | 
|  | </para> | 
|  | </section> | 
|  |  | 
|  | <section> | 
|  | <title>Broadcasting</title> | 
|  |  | 
|  | <para> | 
|  | Jersey SSE server API defines &jersey.sse.SseBroadcaster; which allows to broadcast individual events to | 
|  | multiple | 
|  | clients. A simple broadcasting implementation is shown in the following example: | 
|  |  | 
|  | <example> | 
|  | <title>Broadcasting SSE messages</title> | 
|  | <programlisting language="java" linenumbering="numbered">... | 
|  | import org.glassfish.jersey.media.sse.SseBroadcaster; | 
|  | ... | 
|  |  | 
|  | @Singleton | 
|  | @Path("broadcast") | 
|  | public static class BroadcasterResource { | 
|  |  | 
|  | private SseBroadcaster broadcaster = new SseBroadcaster(); | 
|  |  | 
|  | @POST | 
|  | @Produces(MediaType.TEXT_PLAIN) | 
|  | @Consumes(MediaType.TEXT_PLAIN) | 
|  | public String broadcastMessage(String message) { | 
|  | OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder(); | 
|  | OutboundEvent event = eventBuilder.name("message") | 
|  | .mediaType(MediaType.TEXT_PLAIN_TYPE) | 
|  | .data(String.class, message) | 
|  | .build(); | 
|  |  | 
|  | broadcaster.broadcast(event); | 
|  | return "Message '" + message + "' has been broadcast."; | 
|  | } | 
|  |  | 
|  | @GET | 
|  | @Produces(SseFeature.SERVER_SENT_EVENTS) | 
|  | public EventOutput listenToBroadcast() { | 
|  | final EventOutput eventOutput = new EventOutput(); | 
|  | this.broadcaster.add(eventOutput); | 
|  | return eventOutput; | 
|  | } | 
|  | } | 
|  | </programlisting> | 
|  | </example> | 
|  | The example is similar to its relevant JAX-RS counterpart. The <literal>listenToBroadcast()</literal> resource | 
|  | method creates a new &lit.jersey.sse.EventOutput; representing the connection to the requesting client | 
|  | and registers this <literal>eventOutput</literal> instance with the singleton <literal>broadcaster</literal>, | 
|  | using its <literal>add(EventOutput)</literal> method. The method then returns the | 
|  | <literal>eventOutput</literal> | 
|  | which causes Jersey to bind the <literal>eventOutput</literal> instance with the requesting client and send | 
|  | the | 
|  | response HTTP headers to the client. The client connection remains open and the client is now waiting ready to | 
|  | receive new SSE events. All the events are written to the <literal>eventOutput</literal> by | 
|  | <literal>broadcaster</literal> | 
|  | later on. | 
|  | </para> | 
|  | <para> | 
|  | When a client wants to broadcast new message to all the clients listening on their SSE connections, | 
|  | it sends a &lit.http.POST; request to <literal>BroadcasterResource</literal> resource with the message | 
|  | content. | 
|  | The method <literal>broadcastMessage(String)</literal> is invoked on | 
|  | <literal>BroadcasterResource</literal> | 
|  | resource with the message content as an input parameter. A new SSE outbound event is built in the standard way | 
|  | and passed to the broadcaster. The broadcaster internally invokes <literal>write(OutboundEvent)</literal> on | 
|  | all | 
|  | registered &lit.jersey.sse.EventOutput;s. After that the method just return a standard text response | 
|  | to the &lit.http.POST;ing client to inform the client that the message was successfully broadcast. | 
|  | </para> | 
|  | </section> | 
|  | </section> | 
|  |  | 
|  | <section> | 
|  | <title>Consuming SSE events with Jersey clients</title> | 
|  |  | 
|  | <para> | 
|  | On the client side, Jersey exposes APIs that support receiving and processing SSE events using two programming | 
|  | models: | 
|  |  | 
|  | <simplelist> | 
|  | <member>Pull model - pulling events from a &jersey.sse.EventInput;, or | 
|  | </member> | 
|  | <member>Push model - listening for asynchronous notifications of &lit.jersey.sse.EventSource; | 
|  | </member> | 
|  | </simplelist> | 
|  | The push model is similar to what is implemented in the JAX-RS SSE API. The pull model does not have a direct | 
|  | counterpart in the JAX-RS API and has to be implemented by the developer, if required. | 
|  | </para> | 
|  | <section> | 
|  | <title>Reading SSE events with &lit.jersey.sse.EventInput; | 
|  | </title> | 
|  | <para> | 
|  | The events can be read on the client side from a &jersey.sse.EventInput;. See the following code: | 
|  |  | 
|  | <programlisting language="java" linenumbering="numbered">Client client = ClientBuilder.newBuilder() | 
|  | .register(SseFeature.class).build(); | 
|  | WebTarget target = client.target("http://localhost:9998/events"); | 
|  |  | 
|  | EventInput eventInput = target.request().get(EventInput.class); | 
|  | while (!eventInput.isClosed()) { | 
|  | final InboundEvent inboundEvent = eventInput.read(); | 
|  | if (inboundEvent == null) { | 
|  | // connection has been closed | 
|  | break; | 
|  | } | 
|  | System.out.println(inboundEvent.getName() + "; " + inboundEvent.readData(String.class)); | 
|  | } | 
|  | </programlisting> | 
|  |  | 
|  | In this example, a client connects to the server where the <literal>SseResource</literal> from the | 
|  | <xref linkend="example-simple-sse"/> | 
|  | is deployed. At first, a new JAX-RS/Jersey | 
|  | <literal>client</literal> | 
|  | instance is created with a &lit.jersey.sse.SseFeature; registered. Then a &jaxrs.client.WebTarget; instance is | 
|  | retrieved from the <literal>client</literal> and is used to invoke a HTTP request. The returned response | 
|  | entity | 
|  | is directly read as a &lit.jersey.sse.EventInput; Java type, which is an extension of Jersey | 
|  | &lit.jersey.client.ChunkedInput; that provides generic support for consuming chunked message payloads. The | 
|  | code in the example then process starts a loop to process the inbound SSE events read from the | 
|  | <literal>eventInput</literal> | 
|  | response stream. Each chunk read from the input is a &lit.jersey.sse.InboundEvent;. | 
|  | The method <literal>InboundEvent.readData(Class)</literal> provides a way for the client to indicate what Java | 
|  | type | 
|  | should be used for the event data de-serialization. In our example, individual events are de-serialized as | 
|  | <literal>String</literal> | 
|  | Java type instances. This method internally finds and executes a proper | 
|  | &jaxrs.ext.MessageBodyReader; which is the used to do the actual de-serialization. This is similar to reading | 
|  | an | 
|  | entity from the &jaxrs.core.Response; by <literal>readEntity(Class)</literal>. The method | 
|  | <literal>readData</literal> | 
|  | can also throw a &jaxrs.ProcessingException;. | 
|  | </para> | 
|  | <para> | 
|  | The &lit.null; check on <literal>inboundEvent</literal> is necessary to make sure that the chunk was properly | 
|  | read and connection has not been closed by the server. Once the connection is closed, the loop terminates and | 
|  | the program completes execution. The client code produces the following console output: | 
|  |  | 
|  | <screen language="text" linenumbering="unnumbered">message-to-client; Hello world 0! | 
|  | message-to-client; Hello world 1! | 
|  | message-to-client; Hello world 2! | 
|  | message-to-client; Hello world 3! | 
|  | message-to-client; Hello world 4! | 
|  | message-to-client; Hello world 5! | 
|  | message-to-client; Hello world 6! | 
|  | message-to-client; Hello world 7! | 
|  | message-to-client; Hello world 8! | 
|  | message-to-client; Hello world 9! | 
|  | </screen> | 
|  | </para> | 
|  | </section> | 
|  |  | 
|  | <section> | 
|  | <title>Asynchronous SSE processing with &lit.jersey.sse.EventSource; | 
|  | </title> | 
|  |  | 
|  | <para> | 
|  | The main Jersey-specific SSE client API component used to read SSE events asynchronously is | 
|  | &jersey.sse.EventSource;. The usage of the &lit.jersey.sse.EventSource; is shown on the following example. | 
|  | <example xml:id="sse.ex.client.eventListener"> | 
|  | <title>Registering &lit.jersey.sse.EventListener; with &lit.jersey.sse.EventSource; | 
|  | </title> | 
|  | <programlisting language="java" linenumbering="numbered">Client client = ClientBuilder.newBuilder() | 
|  | .register(SseFeature.class).build(); | 
|  | WebTarget target = client.target("http://example.com/events"); | 
|  | EventSource eventSource = EventSource.target(target).build(); | 
|  | EventListener listener = new EventListener() { | 
|  | @Override | 
|  | public void onEvent(InboundEvent inboundEvent) { | 
|  | System.out.println(inboundEvent.getName() + "; " + inboundEvent.readData(String.class)); | 
|  | } | 
|  | }; | 
|  | eventSource.register(listener, "message-to-client"); | 
|  | eventSource.open(); | 
|  | ... | 
|  | eventSource.close(); | 
|  | </programlisting> | 
|  | </example> | 
|  |  | 
|  | In this example, the client code again connects to the server where the <literal>SseResource</literal> from | 
|  | the | 
|  | <xref linkend="example-simple-sse"/> | 
|  | is deployed. The &jaxrs.client.Client; instance | 
|  | is again created and initialized with &jersey.sse.SseFeature;. Then the &jaxrs.client.WebTarget; is built. | 
|  | In this case a request to the web target is not made directly in the code, instead, the web target instance | 
|  | is used to initialize a new &jersey.sse.EventSource.Builder; instance that is used to build a new | 
|  | &lit.jersey.sse.EventSource;. The choice of <literal>build()</literal> method is important, as it tells | 
|  | the &lit.jersey.sse.EventSource.Builder; to create a new &lit.jersey.sse.EventSource; that is not | 
|  | automatically | 
|  | connected to the <literal>target</literal>. The connection is established only later by manually invoking | 
|  | the <literal>eventSource.open()</literal> method. A custom &jersey.sse.EventListener; | 
|  | implementation is used to listen to and process incoming SSE events. The method readData(Class) says that the | 
|  | event data should be de-serialized from a received &jersey.sse.InboundEvent; instance into a | 
|  | <literal>String</literal> | 
|  | Java type. This method call internally executes &jaxrs.ext.MessageBodyReader; which | 
|  | de-serializes the event data. This is similar to reading an entity from the &jaxrs.core.Response; by | 
|  | <literal>readEntity(Class)</literal>. The method <literal>readData</literal> can throw a | 
|  | &jaxrs.ProcessingException;. | 
|  | </para> | 
|  | <para> | 
|  | The custom event source listener is registered in the event source via | 
|  | &lit.jersey.sse.EventSource;<literal>.register(EventListener, String)</literal> | 
|  | method. The next method | 
|  | arguments define the names of the events to receive and can be omitted. If names are defined, the listener | 
|  | will be associated with the named events and will only be invoked for events with a name from the set of defined | 
|  | event names. It will not be invoked for events with any other names or for events without a name. | 
|  |  | 
|  | <important> | 
|  | <para> | 
|  | It is a common mistake to think that unnamed events will be processed by listeners that are registered | 
|  | to process events from a particular name set. That is NOT the case! Unnamed events are only processed | 
|  | by listeners that are not name-bound. The same limitation applied to HTML5 Javascript SSE Client API | 
|  | supported by modern browsers. | 
|  | </para> | 
|  | </important> | 
|  |  | 
|  | After a connection to the server is opened by calling the <literal>open()</literal> method on the event | 
|  | source, | 
|  | the <literal>eventSource</literal> starts listening to events. When an event named | 
|  | <literal>"message-to-client"</literal> | 
|  | comes, the listener will be executed by the event source. If any other | 
|  | event comes (with a name different from <literal>"message-to-client"</literal>), the registered listener is | 
|  | not | 
|  | invoked. Once the client is done with processing and does not want to receive events anymore, it closes the | 
|  | connection by calling the <literal>close()</literal> method on the event source. | 
|  | </para> | 
|  | <para> | 
|  | The listener from the example above will print the following output: | 
|  | <screen language="text" linenumbering="unnumbered">message-to-client; Hello world 0! | 
|  | message-to-client; Hello world 1! | 
|  | message-to-client; Hello world 2! | 
|  | message-to-client; Hello world 3! | 
|  | message-to-client; Hello world 4! | 
|  | message-to-client; Hello world 5! | 
|  | message-to-client; Hello world 6! | 
|  | message-to-client; Hello world 7! | 
|  | message-to-client; Hello world 8! | 
|  | message-to-client; Hello world 9! | 
|  | </screen> | 
|  | </para> | 
|  | <para> | 
|  | When browsing through the Jersey SSE API documentation, you may have noticed that the &jersey.sse.EventSource; | 
|  | implements &jersey.sse.EventListener; and provides an empty implementation for the | 
|  | <literal>onEvent(InboundEvent inboundEvent)</literal> | 
|  | listener method. This adds more flexibility to the | 
|  | Jersey client-side SSE API. Instead of defining and registering a separate event listener, in simple scenarios | 
|  | you can also choose to derive directly from the &lit.jersey.sse.EventSource; and override the empty listener | 
|  | method to handle the incoming events. This programming model is shown in the following example: | 
|  |  | 
|  | <example> | 
|  | <title>Overriding <literal>EventSource.onEvent(InboundEvent)</literal> method | 
|  | </title> | 
|  | <programlisting language="java" linenumbering="numbered">Client client = ClientBuilder.newBuilder() | 
|  | .register(SseFeature.class).build(); | 
|  | WebTarget target = client.target("http://example.com/events"); | 
|  | EventSource eventSource = new EventSource(target) { | 
|  | @Override | 
|  | public void onEvent(InboundEvent inboundEvent) { | 
|  | if ("message-to-client".equals(inboundEvent.getName())) { | 
|  | System.out.println(inboundEvent.getName() + "; " + inboundEvent.readData(String.class)); | 
|  | } | 
|  | } | 
|  | }; | 
|  | ... | 
|  | eventSource.close(); | 
|  | </programlisting> | 
|  | </example> | 
|  |  | 
|  | The code above is very similar to the code in <xref linkend="sse.ex.client.eventListener"/>. In this example | 
|  | however, the &lit.jersey.sse.EventSource; is constructed directly using a single-parameter constructor. | 
|  | This way, the connection to the SSE endpoint is by default automatically opened at the event source | 
|  | creation. The implementation of the &lit.jersey.sse.EventListener; has been moved into the overridden | 
|  | <literal>EventSource.onEvent(...)</literal> | 
|  | method. However, this time, the listener method will be executed for | 
|  | all events - unnamed as well as with any <literal>name</literal>. Therefore the code checks the name whether | 
|  | it is | 
|  | an event with the name "message-to-client" that we want to handle. Note that you can still register | 
|  | additional &lit.jersey.sse.EventListener;s later on. The overridden method on the event source allows you to | 
|  | handle messages even when no additional listeners are registered yet. | 
|  | </para> | 
|  |  | 
|  | <section> | 
|  | <title>&lit.jersey.sse.EventSource; reconnect support | 
|  | </title> | 
|  | <para> | 
|  | Reconnect support in Jersey-specific &lit.jersey.sse.EventSource; works the same way as in the | 
|  | implementation of the JAX-RS &jakarta.ws.rs.sse.SseEventSource;. | 
|  | </para> | 
|  | </section> | 
|  | </section> | 
|  | </section> | 
|  | </section> | 
|  | </chapter> |