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.core;
018
019import com.soklet.SokletConfiguration;
020
021import javax.annotation.Nonnull;
022import javax.annotation.Nullable;
023import java.util.Optional;
024import java.util.function.Consumer;
025
026/**
027 * 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.
028 * <p>
029 * A Soklet application which supports Server-Sent Events will be configured with both a {@link Server} and a {@link ServerSentEventServer}.
030 * <p>
031 * See <a href="https://www.soklet.com/docs/server-sent-events">https://www.soklet.com/docs/server-sent-events</a> for detailed documentation.
032 *
033 * @author <a href="https://www.revetkn.com">Mark Allen</a>
034 */
035public interface ServerSentEventServer extends AutoCloseable {
036        /**
037         * Starts the SSE server, which makes it able to accept requests from clients.
038         * <p>
039         * If the server is already started, no action is taken.
040         * <p>
041         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
042         */
043        void start();
044
045        /**
046         * Stops the SSE server, which makes it unable to accept requests from clients.
047         * <p>
048         * If the server is already stopped, no action is taken.
049         * <p>
050         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
051         */
052        void stop();
053
054        /**
055         * Is this SSE server started (that is, able to handle requests from clients)?
056         *
057         * @return {@code true} if the server is started, {@code false} otherwise
058         */
059        @Nonnull
060        Boolean isStarted();
061
062        /**
063         * {@link AutoCloseable}-enabled synonym for {@link #stop()}.
064         * <p>
065         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
066         *
067         * @throws Exception if an exception occurs while stopping the server
068         */
069        @Override
070        default void close() throws Exception {
071                stop();
072        }
073
074        /**
075         * 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.
076         * <p>
077         * Soklet guarantees exactly one {@link ServerSentEventBroadcaster} instance exists per {@link ResourcePath}.  Soklet is responsible for the creation and management of {@link ServerSentEventBroadcaster} instances.
078         * <p>
079         * See <a href="https://www.soklet.com/docs/server-sent-events">https://www.soklet.com/docs/server-sent-events</a> for detailed documentation.
080         *
081         * @param resourcePath the {@link com.soklet.annotation.ServerSentEventSource}-annotated <em>Resource Method</em> for which to acquire a broadcaster
082         * @return a broadcaster for the given {@link ResourcePath}, or {@link Optional#empty()} if there is no broadcaster available
083         */
084        @Nonnull
085        Optional<? extends ServerSentEventBroadcaster> acquireBroadcaster(@Nullable ResourcePath resourcePath);
086
087        /**
088         * 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}.
089         * <p>
090         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
091         *
092         * @param sokletConfiguration configuration for the Soklet instance that controls this server
093         * @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
094         */
095        void initialize(@Nonnull SokletConfiguration sokletConfiguration,
096                                                                        @Nonnull RequestHandler requestHandler);
097
098        /**
099         * Request/response processing contract for {@link ServerSentEventServer} implementations.
100         * <p>
101         * This is used internally by {@link com.soklet.Soklet} instances to "talk" to a {@link ServerSentEventServer} via {@link ServerSentEventServer#initialize(SokletConfiguration, RequestHandler)}.
102         * 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.
103         * <p>
104         * <strong>Most Soklet applications will use {@link com.soklet.core.impl.DefaultServerSentEventServer} and therefore do not need to implement this interface directly.</strong>
105         *
106         * @author <a href="https://www.revetkn.com">Mark Allen</a>
107         */
108        @FunctionalInterface
109        interface RequestHandler {
110                /**
111                 * 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.
112                 * <p>
113                 * <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>
114                 * <p>
115                 * 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.
116                 * 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)}.
117                 * <p>
118                 * 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.
119                 * <p>
120                 * 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.
121                 *
122                 * @param request               a Soklet {@link Request} representation of the {@link ServerSentEventServer}'s internal HTTP request data
123                 * @param requestResultConsumer invoked by {@link com.soklet.Soklet} when it's time for the {@link ServerSentEventServer} to write HTTP response data to the client
124                 */
125                void handleRequest(@Nonnull Request request,
126                                                                                         @Nonnull Consumer<RequestResult> requestResultConsumer);
127        }
128}