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) 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        @Nonnull
123        static Builder withPort(@Nonnull Integer port) {
124                requireNonNull(port);
125                return new Builder(port);
126        }
127
128        /**
129         * Builder used to construct a standard implementation of {@link Server}.
130         * <p>
131         * This class is intended for use by a single thread.
132         *
133         * @author <a href="https://www.revetkn.com">Mark Allen</a>
134         */
135        @NotThreadSafe
136        final class Builder {
137                @Nonnull
138                Integer port;
139                @Nullable
140                String host;
141                @Nullable
142                Integer concurrency;
143                @Nullable
144                Duration requestTimeout;
145                @Nullable
146                Duration socketSelectTimeout;
147                @Nullable
148                Duration shutdownTimeout;
149                @Nullable
150                Integer maximumRequestSizeInBytes;
151                @Nullable
152                Integer requestReadBufferSizeInBytes;
153                @Nullable
154                Integer socketPendingConnectionLimit;
155                @Nullable
156                MultipartParser multipartParser;
157                @Nullable
158                Supplier<ExecutorService> requestHandlerExecutorServiceSupplier;
159
160                @Nonnull
161                private Builder(@Nonnull Integer port) {
162                        requireNonNull(port);
163                        this.port = port;
164                }
165
166                @Nonnull
167                public Builder port(@Nonnull Integer port) {
168                        requireNonNull(port);
169                        this.port = port;
170                        return this;
171                }
172
173                @Nonnull
174                public Builder host(@Nullable String host) {
175                        this.host = host;
176                        return this;
177                }
178
179                @Nonnull
180                public Builder concurrency(@Nullable Integer concurrency) {
181                        this.concurrency = concurrency;
182                        return this;
183                }
184
185                @Nonnull
186                public Builder requestTimeout(@Nullable Duration requestTimeout) {
187                        this.requestTimeout = requestTimeout;
188                        return this;
189                }
190
191                @Nonnull
192                public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) {
193                        this.socketSelectTimeout = socketSelectTimeout;
194                        return this;
195                }
196
197                @Nonnull
198                public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) {
199                        this.socketPendingConnectionLimit = socketPendingConnectionLimit;
200                        return this;
201                }
202
203                @Nonnull
204                public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) {
205                        this.shutdownTimeout = shutdownTimeout;
206                        return this;
207                }
208
209                @Nonnull
210                public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) {
211                        this.maximumRequestSizeInBytes = maximumRequestSizeInBytes;
212                        return this;
213                }
214
215                @Nonnull
216                public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) {
217                        this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes;
218                        return this;
219                }
220
221                @Nonnull
222                public Builder multipartParser(@Nullable MultipartParser multipartParser) {
223                        this.multipartParser = multipartParser;
224                        return this;
225                }
226
227                @Nonnull
228                public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) {
229                        this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier;
230                        return this;
231                }
232
233                @Nonnull
234                public Server build() {
235                        return new DefaultServer(this);
236                }
237        }
238}