io.muserver.AsyncSsePublisher Maven / Gradle / Ivy
Show all versions of mu-server Show documentation
package io.muserver;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* 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);
/**
* 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");
return new AsyncSsePublisherImpl(request.handleAsync());
}
}
class AsyncSsePublisherImpl implements AsyncSsePublisher {
private final AsyncHandle asyncHandle;
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));
}
private CompletionStage> write(String text) {
CompletableFuture> stage = new CompletableFuture<>();
asyncHandle.write(ByteBuffer.wrap(text.getBytes(UTF_8)), new WriteCallback() {
@Override
public void onFailure(Throwable reason) {
stage.completeExceptionally(reason);
}
@Override
public void onSuccess() {
stage.complete(null);
}
});
return stage;
}
@Override
public void close() {
asyncHandle.complete();
}
}