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;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.NotThreadSafe;
023import java.time.Duration;
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 * Contract for HTTP server implementations that are designed to be managed by a {@link com.soklet.Soklet} instance.
032 * <p>
033 * <strong>Most Soklet applications will use the default {@link Server} (constructed via the {@link #withPort(Integer)} builder factory method) and therefore do not need to implement this interface directly.</strong>
034 * <p>
035 * For example:
036 * <pre>{@code  SokletConfig config = SokletConfig.withServer(
037 *   Server.fromPort(8080)
038 * ).build();
039 *
040 * try (Soklet soklet = Soklet.fromConfig(config)) {
041 *   soklet.start();
042 *   System.out.println("Soklet started, press [enter] to exit");
043 *   soklet.awaitShutdown(ShutdownTrigger.ENTER_KEY);
044 * }}</pre>
045 *
046 * @author <a href="https://www.revetkn.com">Mark Allen</a>
047 */
048public interface Server extends AutoCloseable {
049        /**
050         * Starts the server, which makes it able to accept requests from clients.
051         * <p>
052         * If the server is already started, 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 start();
057
058        /**
059         * Stops the server, which makes it unable to accept requests from clients.
060         * <p>
061         * If the server is already stopped, no action is taken.
062         * <p>
063         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
064         */
065        void stop();
066
067        /**
068         * Is this server started (that is, able to handle requests from clients)?
069         *
070         * @return {@code true} if the server is started, {@code false} otherwise
071         */
072        @NonNull
073        Boolean isStarted();
074
075        /**
076         * The {@link com.soklet.Soklet} instance which manages this {@link Server} will invoke this method exactly once at initialization time - this allows {@link com.soklet.Soklet} to "talk" to your {@link Server}.
077         * <p>
078         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
079         *
080         * @param sokletConfig   configuration for the Soklet instance that controls this server
081         * @param requestHandler a {@link com.soklet.Soklet}-internal request handler which takes a {@link Server}-provided request as input and supplies a {@link MarshaledResponse} as output for the {@link Server} to write back to the client
082         */
083        void initialize(@NonNull SokletConfig sokletConfig,
084                                                                        @NonNull RequestHandler requestHandler);
085
086        /**
087         * {@link AutoCloseable}-enabled synonym for {@link #stop()}.
088         * <p>
089         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
090         *
091         * @throws Exception if an exception occurs while stopping the server
092         */
093        @Override
094        default void close() throws Exception {
095                stop();
096        }
097
098        /**
099         * Request/response processing contract for {@link Server} implementations.
100         * <p>
101         * This is used internally by {@link com.soklet.Soklet} instances to "talk" to a {@link Server} via {@link Server#initialize(SokletConfig, RequestHandler)}.  It's the responsibility of the {@link Server} to implement HTTP mechanics: read bytes from the request, write bytes to the response, and so forth.
102         * <p>
103         * <strong>Most Soklet applications will use Soklet's default {@link Server} implementation and therefore do not need to implement this interface directly.</strong>
104         *
105         * @author <a href="https://www.revetkn.com">Mark Allen</a>
106         */
107        @FunctionalInterface
108        interface RequestHandler {
109                /**
110                 * Callback to be invoked by a {@link Server} implementation after it has received an HTTP request but prior to writing an HTTP response.
111                 * <p>
112                 * The {@link Server} 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.
113                 * <p>
114                 * The {@link com.soklet.Soklet} instance will generate a {@link MarshaledResponse} for the request, which it "hands back" to the {@link Server} to be sent over the wire to the client.
115                 *
116                 * @param request               a Soklet {@link Request} representation of the {@link Server}'s internal HTTP request data
117                 * @param requestResultConsumer invoked by {@link com.soklet.Soklet} when it's time for the {@link Server} to write HTTP response data to the client
118                 */
119                void handleRequest(@NonNull Request request,
120                                                                                         @NonNull Consumer<RequestResult> requestResultConsumer);
121        }
122
123        /**
124         * Acquires a builder for {@link Server} instances.
125         *
126         * @param port the port number on which the server should listen
127         * @return the builder
128         */
129        @NonNull
130        static Builder withPort(@NonNull Integer port) {
131                requireNonNull(port);
132                return new Builder(port);
133        }
134
135        /**
136         * Creates a {@link Server} configured with the given port and default settings.
137         *
138         * @param port the port number on which the server should listen
139         * @return a {@link Server} instance
140         */
141        @NonNull
142        static Server fromPort(@NonNull Integer port) {
143                return withPort(port).build();
144        }
145
146        /**
147         * Builder used to construct a standard implementation of {@link Server}.
148         * <p>
149         * This class is intended for use by a single thread.
150         *
151         * @author <a href="https://www.revetkn.com">Mark Allen</a>
152         */
153        @NotThreadSafe
154        final class Builder {
155                @NonNull
156                Integer port;
157                @Nullable
158                String host;
159                @Nullable
160                Integer concurrency;
161                @Nullable
162                Duration requestTimeout;
163                @Nullable
164                Duration requestHandlerTimeout;
165                @Nullable
166                Integer requestHandlerConcurrency;
167                @Nullable
168                Integer requestHandlerQueueCapacity;
169                @Nullable
170                Duration socketSelectTimeout;
171                @Nullable
172                Duration shutdownTimeout;
173                @Nullable
174                Integer maximumRequestSizeInBytes;
175                @Nullable
176                Integer requestReadBufferSizeInBytes;
177                @Nullable
178                Integer socketPendingConnectionLimit;
179                @Nullable
180                Integer maximumConnections;
181                @Nullable
182                MultipartParser multipartParser;
183                @Nullable
184                Supplier<ExecutorService> requestHandlerExecutorServiceSupplier;
185                @Nullable
186                IdGenerator<?> idGenerator;
187
188                @NonNull
189                private Builder(@NonNull Integer port) {
190                        requireNonNull(port);
191                        this.port = port;
192                }
193
194                @NonNull
195                public Builder port(@NonNull Integer port) {
196                        requireNonNull(port);
197                        this.port = port;
198                        return this;
199                }
200
201                @NonNull
202                public Builder host(@Nullable String host) {
203                        this.host = host;
204                        return this;
205                }
206
207                @NonNull
208                public Builder concurrency(@Nullable Integer concurrency) {
209                        this.concurrency = concurrency;
210                        return this;
211                }
212
213                @NonNull
214                public Builder requestTimeout(@Nullable Duration requestTimeout) {
215                        this.requestTimeout = requestTimeout;
216                        return this;
217                }
218
219                @NonNull
220                public Builder requestHandlerTimeout(@Nullable Duration requestHandlerTimeout) {
221                        this.requestHandlerTimeout = requestHandlerTimeout;
222                        return this;
223                }
224
225                @NonNull
226                public Builder requestHandlerConcurrency(@Nullable Integer requestHandlerConcurrency) {
227                        this.requestHandlerConcurrency = requestHandlerConcurrency;
228                        return this;
229                }
230
231                @NonNull
232                public Builder requestHandlerQueueCapacity(@Nullable Integer requestHandlerQueueCapacity) {
233                        this.requestHandlerQueueCapacity = requestHandlerQueueCapacity;
234                        return this;
235                }
236
237                @NonNull
238                public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) {
239                        this.socketSelectTimeout = socketSelectTimeout;
240                        return this;
241                }
242
243                @NonNull
244                public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) {
245                        this.socketPendingConnectionLimit = socketPendingConnectionLimit;
246                        return this;
247                }
248
249                @NonNull
250                public Builder maximumConnections(@Nullable Integer maximumConnections) {
251                        this.maximumConnections = maximumConnections;
252                        return this;
253                }
254
255                @NonNull
256                public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) {
257                        this.shutdownTimeout = shutdownTimeout;
258                        return this;
259                }
260
261                @NonNull
262                public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) {
263                        this.maximumRequestSizeInBytes = maximumRequestSizeInBytes;
264                        return this;
265                }
266
267                @NonNull
268                public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) {
269                        this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes;
270                        return this;
271                }
272
273                @NonNull
274                public Builder multipartParser(@Nullable MultipartParser multipartParser) {
275                        this.multipartParser = multipartParser;
276                        return this;
277                }
278
279                @NonNull
280                public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) {
281                        this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier;
282                        return this;
283                }
284
285                @NonNull
286                public Builder idGenerator(@Nullable IdGenerator<?> idGenerator) {
287                        this.idGenerator = idGenerator;
288                        return this;
289                }
290
291                @NonNull
292                public Server build() {
293                        return new DefaultServer(this);
294                }
295        }
296}