All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.muserver.AsyncSsePublisher Maven / Gradle / Ivy

The newest version!
package io.muserver;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;

/**
 * 

An interface for sending Server-Sent Events (SSE) to a client with async callbacks.

*

This is preferrable to the (blocking) {@link SsePublisher} when you have a large number of subscribers as * messages will be sent in a non-blocking fashion.

*

The usage is that same as for the synchronous version except that each send method returns a {@link CompletionStage} * which contains completion or exception info.

* * @see SsePublisher */ public interface AsyncSsePublisher { /** * Sends a message (without an ID or event type) * * @param message The message to send * @return completion stage that completes when the event has been sent. If there is a problem during sending of * an event, completion stage will be completed exceptionally. */ CompletionStage send(String message); /** *

Sends a message with an event type (without an ID).

*

Clients can use the event type to listen to different types of events, for example if the event type is pricechange * the the following JavaScript can be used:

*

     *     var source = new EventSource('/streamer');
     *     source.addEventListener('pricechange', e => console.log(e.data));
     * 
* * @param message The message to send * @param event An event name. If null is specified, clients default to a message type of message * @return completion stage that completes when the event has been sent. If there is a problem during sending of * an event, completion stage will be completed exceptionally. */ CompletionStage send(String message, String event); /** *

Sends a message with an event type and ID.

*

Clients can use the event type to listen to different types of events, for example if the event type is pricechange * the the following JavaScript can be used:

*

     *     var source = new EventSource('/streamer');
     *     source.addEventListener('pricechange', e => console.log(e.data));
     * 
* * @param message The message to send * @param event An event name. If null is specified, clients default to a message type of message * @param eventID An identifier for the message. If set, and the browser reconnects, then the last event ID will be * sent by the browser in the Last-Event-ID request header. * @return completion stage that completes when the event has been sent. If there is a problem during sending of * an event, completion stage will be completed exceptionally. */ CompletionStage send(String message, String event, String eventID); /** *

Stops the event stream.

*

Warning: most clients will reconnect several seconds after this message is called. To prevent that * happening, close the stream from the client or on the next request return a 204 No Content to the client.

*/ void close(); /** * Sends a comment to the client. Clients will ignore this, however it can be used as a way to keep the connection alive. * * @param comment A single-line string to send as a comment. * @return completion stage that completes when the comment has been sent. If there is a problem during sending of * an event, completion stage will be completed exceptionally. */ CompletionStage sendComment(String comment); /** *

Sends a message to the client instructing it to reconnect after the given time period in case of any disconnection * (including calling {@link #close()} from the server). A common default (controlled by the client) is several seconds.

*

Note: clients could ignore this value.

* * @param timeToWait The time the client should wait before attempting to reconnect in case of any disconnection. * @param unit The unit of time. * @return completion stage that completes when the event has been sent. If there is a problem during sending of * an event, completion stage will be completed exceptionally. */ CompletionStage setClientReconnectTime(long timeToWait, TimeUnit unit); /** * Add a listener for when request processing is complete. One use of this is to detect early client disconnects * so that expensive operations can be cancelled. *

Check {@link ResponseInfo#completedSuccessfully()} for false for SSE streams that did not complete.

* @param responseCompleteListener The handler to invoke when the request is complete. */ void setResponseCompleteHandler(ResponseCompleteListener responseCompleteListener); /** * Checks if this publisher has been closed. *

This will be true if the server or the client closes the SSE stream.

* @return true if it is closed; otherwise false */ boolean isClosed(); /** *

Creates a new Server-Sent Events publisher. This is designed by be called from within a MuHandler.

*

This will set the content type of the response to text/event-stream and disable caching.

*

The request will also switch to async mode, which means you can use the returned publisher in another thread.

*

IMPORTANT: The {@link #close()} method must be called when publishing is complete.

* * @param request The current MuRequest * @param response The current MuResponse * @return Returns a publisher that can be used to send messages to the client. */ static AsyncSsePublisher start(MuRequest request, MuResponse response) { response.contentType(ContentTypes.TEXT_EVENT_STREAM); response.headers().set(HeaderNames.CACHE_CONTROL, "no-cache, no-transform"); AsyncHandle asyncHandle = request.handleAsync(); AsyncSsePublisherImpl ssePublisher = new AsyncSsePublisherImpl(asyncHandle); asyncHandle.addResponseCompleteHandler(info -> ssePublisher.close()); return ssePublisher; } } class AsyncSsePublisherImpl implements AsyncSsePublisher { private final AsyncHandle asyncHandle; private volatile boolean closed = false; AsyncSsePublisherImpl(AsyncHandle asyncHandle) { this.asyncHandle = asyncHandle; } @Override public CompletionStage send(String message) { return send(message, null, null); } @Override public CompletionStage send(String message, String event) { return send(message, event, null); } @Override public CompletionStage send(String message, String event, String eventID) { return write(SsePublisherImpl.dataText(message, event, eventID)); } @Override public CompletionStage sendComment(String comment) { return write(SsePublisherImpl.commentText(comment)); } @Override public CompletionStage setClientReconnectTime(long timeToWait, TimeUnit unit) { return write(SsePublisherImpl.clientReconnectText(timeToWait, unit)); } @Override public void setResponseCompleteHandler(ResponseCompleteListener responseCompleteListener) { asyncHandle.addResponseCompleteHandler(responseCompleteListener); } @Override public boolean isClosed() { return closed; } private CompletionStage write(String text) { CompletableFuture stage = new CompletableFuture<>(); if (closed) { stage.completeExceptionally(new IllegalStateException("The SSE stream was already closed")); } else { asyncHandle.write(Mutils.toByteBuffer(text), error -> { if (error == null) { stage.complete(null); } else { stage.completeExceptionally(error); } }); } return stage; } @Override public void close() { if (!closed) { closed = true; asyncHandle.complete(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy