| <?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, situation changed and SSE API is well defined in the |
| <literal>javax.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>javax.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 javax.ws.rs.sse.Sse; |
| import javax.ws.rs.sse.SseEventSink; |
| import javax.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>javax.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>javax.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 2.1 API)</title> |
| <programlisting language="java" linenumbering="numbered">... |
| import javax.ws.rs.sse.Sse; |
| import javax.ws.rs.sse.SseEventSink; |
| import javax.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 |
| &jee6.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>javax.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 javax.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 &javax.ws.rs.sse.SseEventSource.Builder; instance that is used to build a new |
| &javax.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 &javax.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>javax.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 &javax.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 &javax.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 &javax.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> |
| |
| <!--TODO - describes the Jersey-specific legacy API, change and only mention the differences --> |
| |
| |
| <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> |
| Both implementations are compatible, which means client based on Jersey-specific SSE implementation can "talk" |
| to server resource implemetned using JAX-RS 2.1 based implementation and vice versa. |
| </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 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 &javax.ws.rs.sse.SseEventSource;. |
| </para> |
| </section> |
| </section> |
| </section> |
| </section> |
| </chapter> |