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}