001/*
002 * Copyright 2022-2025 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 javax.annotation.Nonnull;
020import javax.annotation.Nullable;
021import javax.annotation.concurrent.NotThreadSafe;
022import java.time.Duration;
023import java.util.Optional;
024import java.util.concurrent.ExecutorService;
025import java.util.function.Consumer;
026import java.util.function.Supplier;
027
028import static java.util.Objects.requireNonNull;
029
030/**
031 * A special HTTP server whose only purpose is to provide <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Server-Sent Event</a> functionality.
032 * <p>
033 * A Soklet application which supports Server-Sent Events will be configured with both a {@link Server} and a {@link ServerSentEventServer}.
034 * <p>
035 * See <a href="https://www.soklet.com/docs/server-sent-events">https://www.soklet.com/docs/server-sent-events</a> for detailed documentation.
036 *
037 * @author <a href="https://www.revetkn.com">Mark Allen</a>
038 */
039public interface ServerSentEventServer extends AutoCloseable {
040        /**
041         * Starts the SSE server, which makes it able to accept requests from clients.
042         * <p>
043         * If the server is already started, no action is taken.
044         * <p>
045         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
046         */
047        void start();
048
049        /**
050         * Stops the SSE server, which makes it unable to accept requests from clients.
051         * <p>
052         * If the server is already stopped, no action is taken.
053         * <p>
054         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
055         */
056        void stop();
057
058        /**
059         * Is this SSE server started (that is, able to handle requests from clients)?
060         *
061         * @return {@code true} if the server is started, {@code false} otherwise
062         */
063        @Nonnull
064        Boolean isStarted();
065
066        /**
067         * {@link AutoCloseable}-enabled synonym for {@link #stop()}.
068         * <p>
069         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
070         *
071         * @throws Exception if an exception occurs while stopping the server
072         */
073        @Override
074        default void close() throws Exception {
075                stop();
076        }
077
078        /**
079         * Given a {@link ResourcePath} that corresponds to a <em>Resource Method</em> annotated with {@link com.soklet.annotation.ServerSentEventSource}, acquire a {@link ServerSentEventBroadcaster} which is capable of "pushing" messages to all connected Server-Sent Event clients.
080         * <p>
081         * Soklet guarantees exactly one {@link ServerSentEventBroadcaster} instance exists per {@link ResourcePath}.  Soklet is responsible for the creation and management of {@link ServerSentEventBroadcaster} instances.
082         * <p>
083         * See <a href="https://www.soklet.com/docs/server-sent-events">https://www.soklet.com/docs/server-sent-events</a> for detailed documentation.
084         *
085         * @param resourcePath the {@link com.soklet.annotation.ServerSentEventSource}-annotated <em>Resource Method</em> for which to acquire a broadcaster
086         * @return a broadcaster for the given {@link ResourcePath}, or {@link Optional#empty()} if there is no broadcaster available
087         */
088        @Nonnull
089        Optional<? extends ServerSentEventBroadcaster> acquireBroadcaster(@Nullable ResourcePath resourcePath);
090
091        /**
092         * The {@link com.soklet.Soklet} instance which manages this {@link ServerSentEventServer} will invoke this method exactly once at initialization time - this allows {@link com.soklet.Soklet} to "talk" to your {@link ServerSentEventServer}.
093         * <p>
094         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
095         *
096         * @param sokletConfig configuration for the Soklet instance that controls this server
097         * @param requestHandler      a {@link com.soklet.Soklet}-internal request handler which takes a {@link ServerSentEventServer}-provided request as input and supplies a {@link MarshaledResponse} as output for the {@link ServerSentEventServer} to write back to the client
098         */
099        void initialize(@Nonnull SokletConfig sokletConfig,
100                                                                        @Nonnull RequestHandler requestHandler);
101
102        /**
103         * Request/response processing contract for {@link ServerSentEventServer} implementations.
104         * <p>
105         * This is used internally by {@link com.soklet.Soklet} instances to "talk" to a {@link ServerSentEventServer} via {@link ServerSentEventServer#initialize(SokletConfig, RequestHandler)}.
106         * It's the responsibility of the {@link ServerSentEventServer} to implement HTTP mechanics: read bytes from the request, write bytes to the response, and so forth.
107         * <p>
108         * <strong>Most Soklet applications will use Soklet's default {@link ServerSentEventServer} implementation and therefore do not need to implement this interface directly.</strong>
109         *
110         * @author <a href="https://www.revetkn.com">Mark Allen</a>
111         */
112        @FunctionalInterface
113        interface RequestHandler {
114                /**
115                 * Callback to be invoked by a {@link ServerSentEventServer} implementation after it has received a Server-Sent Event Source HTTP request but prior to writing initial data to the HTTP response.
116                 * <p>
117                 * <strong>Note: this method is only invoked during the initial request "handshake" - it is not called for subsequent Server-Sent Event stream writes performed via {@link ServerSentEventBroadcaster#broadcast(ServerSentEvent)} invocations.</strong>
118                 * <p>
119                 * For example, when a Server-Sent Event Source HTTP request is received, you might immediately write an HTTP 200 OK response if all looks good, or reject with a 401 due to invalid credentials.
120                 * That is the extent of the request-handling logic performed here.  The Server-Sent Event stream then remains open and can be written to via {@link ServerSentEventBroadcaster#broadcast(ServerSentEvent)}.
121                 * <p>
122                 * The {@link ServerSentEventServer} is responsible for converting its internal request representation into a {@link Request}, which a {@link com.soklet.Soklet} instance consumes and performs Soklet application request processing logic.
123                 * <p>
124                 * The {@link com.soklet.Soklet} instance will generate a {@link MarshaledResponse} for the request, which it "hands back" to the {@link ServerSentEventServer} to be sent over the wire to the client.
125                 *
126                 * @param request               a Soklet {@link Request} representation of the {@link ServerSentEventServer}'s internal HTTP request data
127                 * @param requestResultConsumer invoked by {@link com.soklet.Soklet} when it's time for the {@link ServerSentEventServer} to write HTTP response data to the client
128                 */
129                void handleRequest(@Nonnull Request request,
130                                                                                         @Nonnull Consumer<RequestResult> requestResultConsumer);
131        }
132
133        @Nonnull
134        static Builder withPort(@Nonnull Integer port) {
135                requireNonNull(port);
136                return new Builder(port);
137        }
138
139        /**
140         * Builder used to construct a standard implementation of {@link ServerSentEventServer}.
141         * <p>
142         * This class is intended for use by a single thread.
143         *
144         * @author <a href="https://www.revetkn.com">Mark Allen</a>
145         */
146        @NotThreadSafe
147        final class Builder {
148                @Nonnull
149                Integer port;
150                @Nullable
151                String host;
152                @Nullable
153                Duration requestTimeout;
154                @Nullable
155                Duration shutdownTimeout;
156                @Nullable
157                Duration heartbeatInterval;
158                @Nullable
159                Integer maximumRequestSizeInBytes;
160                @Nullable
161                Integer requestReadBufferSizeInBytes;
162                @Nullable
163                Supplier<ExecutorService> requestHandlerExecutorServiceSupplier;
164                @Nullable
165                Integer concurrentConnectionLimit;
166
167                @Nonnull
168                protected Builder(@Nonnull Integer port) {
169                        requireNonNull(port);
170                        this.port = port;
171                }
172
173                @Nonnull
174                public Builder port(@Nonnull Integer port) {
175                        requireNonNull(port);
176                        this.port = port;
177                        return this;
178                }
179
180                @Nonnull
181                public Builder host(@Nullable String host) {
182                        this.host = host;
183                        return this;
184                }
185
186                @Nonnull
187                public Builder requestTimeout(@Nullable Duration requestTimeout) {
188                        this.requestTimeout = requestTimeout;
189                        return this;
190                }
191
192                @Nonnull
193                public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) {
194                        this.shutdownTimeout = shutdownTimeout;
195                        return this;
196                }
197
198                @Nonnull
199                public Builder heartbeatInterval(@Nullable Duration heartbeatInterval) {
200                        this.heartbeatInterval = heartbeatInterval;
201                        return this;
202                }
203
204                @Nonnull
205                public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) {
206                        this.maximumRequestSizeInBytes = maximumRequestSizeInBytes;
207                        return this;
208                }
209
210                @Nonnull
211                public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) {
212                        this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes;
213                        return this;
214                }
215
216                @Nonnull
217                public Builder concurrentConnectionLimit(@Nullable Integer concurrentConnectionLimit) {
218                        this.concurrentConnectionLimit = concurrentConnectionLimit;
219                        return this;
220                }
221
222                @Nonnull
223                public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) {
224                        this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier;
225                        return this;
226                }
227
228                @Nonnull
229                public ServerSentEventServer build() {
230                        return new DefaultServerSentEventServer(this);
231                }
232        }
233}