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.concurrent.ExecutorService;
024import java.util.function.Consumer;
025import java.util.function.Supplier;
026
027import static java.util.Objects.requireNonNull;
028
029/**
030 * Contract for HTTP server implementations that are designed to be managed by a {@link com.soklet.Soklet} instance.
031 * <p>
032 * <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>
033 * <p>
034 * For example:
035 * <pre>{@code  SokletConfig config = SokletConfig.withServer(
036 *   Server.withPort(8080).build()
037 * ).build();
038 *
039 * try (Soklet soklet = Soklet.withConfig(config)) {
040 *   soklet.start();
041 *   System.out.println("Soklet started, press [enter] to exit");
042 *   soklet.awaitShutdown(ShutdownTrigger.ENTER_KEY);
043 * }}</pre>
044 *
045 * @author <a href="https://www.revetkn.com">Mark Allen</a>
046 */
047public interface Server extends AutoCloseable {
048        /**
049         * Starts the server, which makes it able to accept requests from clients.
050         * <p>
051         * If the server is already started, no action is taken.
052         * <p>
053         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
054         */
055        void start();
056
057        /**
058         * Stops the server, which makes it unable to accept requests from clients.
059         * <p>
060         * If the server is already stopped, no action is taken.
061         * <p>
062         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
063         */
064        void stop();
065
066        /**
067         * Is this server started (that is, able to handle requests from clients)?
068         *
069         * @return {@code true} if the server is started, {@code false} otherwise
070         */
071        @Nonnull
072        Boolean isStarted();
073
074        /**
075         * 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}.
076         * <p>
077         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
078         *
079         * @param sokletConfig   configuration for the Soklet instance that controls this server
080         * @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
081         */
082        void initialize(@Nonnull SokletConfig sokletConfig,
083                                                                        @Nonnull RequestHandler requestHandler);
084
085        /**
086         * {@link AutoCloseable}-enabled synonym for {@link #stop()}.
087         * <p>
088         * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong>
089         *
090         * @throws Exception if an exception occurs while stopping the server
091         */
092        @Override
093        default void close() throws Exception {
094                stop();
095        }
096
097        /**
098         * Request/response processing contract for {@link Server} implementations.
099         * <p>
100         * 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.
101         * <p>
102         * <strong>Most Soklet applications will use Soklet's default {@link Server} implementation and therefore do not need to implement this interface directly.</strong>
103         *
104         * @author <a href="https://www.revetkn.com">Mark Allen</a>
105         */
106        @FunctionalInterface
107        interface RequestHandler {
108                /**
109                 * Callback to be invoked by a {@link Server} implementation after it has received an HTTP request but prior to writing an HTTP response.
110                 * <p>
111                 * 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.
112                 * <p>
113                 * 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.
114                 *
115                 * @param request               a Soklet {@link Request} representation of the {@link Server}'s internal HTTP request data
116                 * @param requestResultConsumer invoked by {@link com.soklet.Soklet} when it's time for the {@link Server} to write HTTP response data to the client
117                 */
118                void handleRequest(@Nonnull Request request,
119                                                                                         @Nonnull Consumer<RequestResult> requestResultConsumer);
120        }
121
122        /**
123         * Acquires a builder for {@link Server} instances.
124         *
125         * @param port the port number on which the server should listen
126         * @return the builder
127         */
128        @Nonnull
129        static Builder withPort(@Nonnull Integer port) {
130                requireNonNull(port);
131                return new Builder(port);
132        }
133
134        /**
135         * Builder used to construct a standard implementation of {@link Server}.
136         * <p>
137         * This class is intended for use by a single thread.
138         *
139         * @author <a href="https://www.revetkn.com">Mark Allen</a>
140         */
141        @NotThreadSafe
142        final class Builder {
143                @Nonnull
144                Integer port;
145                @Nullable
146                String host;
147                @Nullable
148                Integer concurrency;
149                @Nullable
150                Duration requestTimeout;
151                @Nullable
152                Duration socketSelectTimeout;
153                @Nullable
154                Duration shutdownTimeout;
155                @Nullable
156                Integer maximumRequestSizeInBytes;
157                @Nullable
158                Integer requestReadBufferSizeInBytes;
159                @Nullable
160                Integer socketPendingConnectionLimit;
161                @Nullable
162                MultipartParser multipartParser;
163                @Nullable
164                Supplier<ExecutorService> requestHandlerExecutorServiceSupplier;
165                @Nullable
166                IdGenerator<?> idGenerator;
167
168                @Nonnull
169                private Builder(@Nonnull Integer port) {
170                        requireNonNull(port);
171                        this.port = port;
172                }
173
174                @Nonnull
175                public Builder port(@Nonnull Integer port) {
176                        requireNonNull(port);
177                        this.port = port;
178                        return this;
179                }
180
181                @Nonnull
182                public Builder host(@Nullable String host) {
183                        this.host = host;
184                        return this;
185                }
186
187                @Nonnull
188                public Builder concurrency(@Nullable Integer concurrency) {
189                        this.concurrency = concurrency;
190                        return this;
191                }
192
193                @Nonnull
194                public Builder requestTimeout(@Nullable Duration requestTimeout) {
195                        this.requestTimeout = requestTimeout;
196                        return this;
197                }
198
199                @Nonnull
200                public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) {
201                        this.socketSelectTimeout = socketSelectTimeout;
202                        return this;
203                }
204
205                @Nonnull
206                public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) {
207                        this.socketPendingConnectionLimit = socketPendingConnectionLimit;
208                        return this;
209                }
210
211                @Nonnull
212                public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) {
213                        this.shutdownTimeout = shutdownTimeout;
214                        return this;
215                }
216
217                @Nonnull
218                public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) {
219                        this.maximumRequestSizeInBytes = maximumRequestSizeInBytes;
220                        return this;
221                }
222
223                @Nonnull
224                public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) {
225                        this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes;
226                        return this;
227                }
228
229                @Nonnull
230                public Builder multipartParser(@Nullable MultipartParser multipartParser) {
231                        this.multipartParser = multipartParser;
232                        return this;
233                }
234
235                @Nonnull
236                public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) {
237                        this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier;
238                        return this;
239                }
240
241                @Nonnull
242                public Builder idGenerator(@Nullable IdGenerator<?> idGenerator) {
243                        this.idGenerator = idGenerator;
244                        return this;
245                }
246
247                @Nonnull
248                public Server build() {
249                        return new DefaultServer(this);
250                }
251        }
252}