001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020 021import java.util.function.Function; 022 023/** 024 * Broadcasts a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Server-Sent Event</a> payload to all clients listening on a {@link ResourcePath}. 025 * <p> 026 * For example: 027 * <pre>{@code // Acquire our SSE broadcaster (sends to anyone listening to "/examples/123") 028 * ServerSentEventServer server = ...; 029 * ServerSentEventBroadcaster broadcaster = server.acquireBroadcaster(ResourcePath.fromPath("/examples/123")).orElseThrow(); 030 * 031 * // Create our SSE payload 032 * ServerSentEvent event = ServerSentEvent.withEvent("test") 033 * .data("example") 034 * .build(); 035 * 036 * // Publish SSE payload to all listening clients 037 * broadcaster.broadcastEvent(event);}</pre> 038 * <p> 039 * Soklet's default {@link ServerSentEventServer} implementation guarantees at most one broadcaster is registered per {@link ResourcePath} at a time; instances may be recreated after becoming idle. That implementation is responsible for the creation and management of {@link ServerSentEventBroadcaster} instances. 040 * <p> 041 * You may acquire a broadcaster via {@link ServerSentEventServer#acquireBroadcaster(ResourcePath)}. 042 * <p> 043 * See <a href="https://www.soklet.com/docs/server-sent-events">https://www.soklet.com/docs/server-sent-events</a> for detailed documentation. 044 * <p> 045 * Formal specification is available at <a href="https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events">https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events</a>. 046 * 047 * @author <a href="https://www.revetkn.com">Mark Allen</a> 048 */ 049public interface ServerSentEventBroadcaster { 050 /** 051 * The runtime Resource Path with which this broadcaster is associated. 052 * <p> 053 * Soklet guarantees exactly one {@link ServerSentEventBroadcaster} instance exists per {@link ResourcePath}. 054 * <p> 055 * For example, a client may register for SSE broadcasts for <em>Resource Method</em> {@code @ServerSentEventSource("/examples/{exampleId}")} by making a request to {@code GET /examples/123}. 056 * <p> 057 * A broadcaster specific to {@code /examples/123} is then created (if necessary) and managed by Soklet, and can be used to send SSE payloads to all clients via {@link #broadcastEvent(ServerSentEvent)}. 058 * 059 * @return the runtime Resource Path instance with which this broadcaster is associated 060 */ 061 @NonNull 062 ResourcePath getResourcePath(); 063 064 /** 065 * Approximately how many clients are listening to this broadcaster's {@link ResourcePath}? 066 * <p> 067 * For performance reasons, this number may be an estimate, or a snapshot of a recent moment-in-time. 068 * It's possible for some clients to have already disconnected, but we won't know until we attempt to broadcast to them. 069 * 070 * @return the approximate number of clients who will receive a broadcasted event 071 */ 072 @NonNull 073 Long getClientCount(); 074 075 /** 076 * Broadcasts a Server-Sent Event payload to all clients listening to this broadcaster's {@link ResourcePath}. 077 * <p> 078 * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution. 079 * <p> 080 * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing. 081 * 082 * @param serverSentEvent the Server-Sent Event payload to broadcast 083 */ 084 void broadcastEvent(@NonNull ServerSentEvent serverSentEvent); 085 086 /** 087 * Broadcasts a Server-Sent Event where the payload is dynamically generated and memoized based on a specific trait of the client (e.g. {@link java.util.Locale} or User Role). 088 * <p> 089 * This method is designed for high-scale scenarios where generating the payload is expensive (e.g. JSON serialization with localization) and the number of distinct variations (keys) is significantly smaller than the number of clients. 090 * <p> 091 * The implementation guarantees that {@code eventProvider} is called exactly once per unique key derived by {@code keySelector} among the currently active clients. 092 * <p> 093 * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution. 094 * <p> 095 * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing. 096 * 097 * @param <T> the type of the grouping key (e.g. {@link java.util.Locale} or {@link String}) 098 * @param keySelector a function that derives a grouping key from the client's associated context object. 099 * (If the client has no context, the implementation passes {@code null}) 100 * @param eventProvider a function that provides the {@link ServerSentEvent} for a given key 101 */ 102 <T> void broadcastEvent(@NonNull Function<Object, T> keySelector, 103 @NonNull Function<T, ServerSentEvent> eventProvider); 104 105 /** 106 * Broadcasts a single Server-Sent Event comment to all clients listening to this broadcaster's {@link ResourcePath}. 107 * <p> 108 * Use {@link ServerSentEventComment#heartbeatInstance()} to emit a heartbeat comment. 109 * <p> 110 * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution. 111 * <p> 112 * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing. 113 * 114 * @param serverSentEventComment the comment payload to broadcast 115 */ 116 void broadcastComment(@NonNull ServerSentEventComment serverSentEventComment); 117 118 /** 119 * Broadcasts a Server-Sent Event comment where the payload is dynamically generated and memoized based on a specific trait of the client (e.g. {@link java.util.Locale} or User Role). 120 * <p> 121 * This follows the same memoization pattern as {@link #broadcastEvent(Function, Function)}. 122 * <p> 123 * The implementation guarantees that {@code commentProvider} is called exactly once per unique key derived by {@code keySelector} among the currently active clients. 124 * <p> 125 * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution. 126 * <p> 127 * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing. 128 * 129 * @param <T> the type of the grouping key 130 * @param keySelector a function that derives a grouping key from the client's associated context object 131 * @param commentProvider a function that provides the comment payload for a given key 132 */ 133 <T> void broadcastComment(@NonNull Function<Object, T> keySelector, 134 @NonNull Function<T, ServerSentEventComment> commentProvider); 135}