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

io.muserver.SsePublisher Maven / Gradle / Ivy

There is a newer version: 2.0.3
Show newest version
package io.muserver;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 

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

*

The following example creates a publisher and publishes 10 messages to it from another thread:

*

 * server = httpsServer()
 *     .addHandler(Method.GET, "/streamer", (request, response, pathParams) -> {
 *         SsePublisher ssePublisher = SsePublisher.start(request, response);
 *         new Thread(() -> {
 *             try {
 *                 for (int i = 0; i < 100; i++) {
 *                     ssePublisher.send("This is message " + i);
 *                     Thread.sleep(1000);
 *                 }
 *             } catch (Exception e) {
 *                 // the user has probably disconnected; stop publishing
 *             } finally {
 *                 ssePublisher.close();
 *             }
 *         }).start();
 *
 *     })
 *     .start();
 * 
*/ public interface SsePublisher { /** * Sends a message (without an ID or event type) * @param message The message to send * @throws IOException Thrown if there is an error writing to the client, for example if the user has closed their browser. */ void send(String message) throws IOException; /** *

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 * @throws IOException Thrown if there is an error writing to the client, for example if the user has closed their browser. */ void send(String message, String event) throws IOException; /** *

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. * @throws IOException Thrown if there is an error writing to the client, for example if the user has closed their browser. */ void send(String message, String event, String eventID) throws IOException; /** *

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. * @throws IOException Thrown if there is an error writing to the client, for example if the user has closed their browser. */ void sendComment(String comment) throws IOException; /** *

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. * @throws IOException Thrown if there is an error writing to the client, for example if the user has closed their browser. */ void setClientReconnectTime(long timeToWait, TimeUnit unit) throws IOException; /** *

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 SsePublisher start(MuRequest request, MuResponse response) { response.contentType(ContentTypes.TEXT_EVENT_STREAM); response.headers().set(HeaderNames.CACHE_CONTROL, "no-cache, no-transform"); return new SsePublisherImpl(request.handleAsync(), response); } } class SsePublisherImpl implements SsePublisher { private final AsyncHandle asyncHandle; private final MuResponse response; SsePublisherImpl(AsyncHandle asyncHandle, MuResponse response) { this.asyncHandle = asyncHandle; this.response = response; } @Override public void send(String message) throws IOException { send(message, null, null); } @Override public void send(String message, String event) throws IOException { send(message, event, null); } @Override public void send(String message, String event, String eventID) throws IOException { sendChunk(dataText(message, event, eventID)); } @Override public void sendComment(String comment) throws IOException { sendChunk(commentText(comment)); } @Override public void setClientReconnectTime(long timeToWait, TimeUnit unit) throws IOException { sendChunk(clientReconnectText(timeToWait, unit)); } @Override public void close() { asyncHandle.complete(); } private void sendChunk(String text) throws IOException { try { response.sendChunk(text); } catch (Throwable e) { close(); if (e instanceof IllegalStateException) { throw new IOException(e); } throw e; } } private static void ensureNoLineBreaks(String value, String thing) { if (value.contains("\n") || value.contains("\r")) { throw new IllegalArgumentException(thing + " cannot have new line characters in them"); } } static String dataText(String message, String event, String eventID) { StringBuilder raw = new StringBuilder(); if (eventID != null) { ensureNoLineBreaks(eventID, "SSE IDs"); raw.append("id: ").append(eventID).append('\n'); } if (event != null) { ensureNoLineBreaks(event, "SSE event names"); raw.append("event: ").append(event).append('\n'); } String[] lines = message.split("(\r\n)|[\r\n]"); for (String line : lines) { raw.append("data: ").append(line).append('\n'); } raw.append("\n"); return raw.toString(); } static String commentText(String comment) { ensureNoLineBreaks(comment, "SSE Comments"); return ":" + comment + "\n\n"; } static String clientReconnectText(long timeToWait, TimeUnit unit) { return "retry: " + unit.toMillis(timeToWait) + '\n'; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy