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 HttpServer} (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.withHttpServer(
037 *   HttpServer.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 HttpServer 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 HttpServer} will invoke this method exactly once at initialization time - this allows {@link com.soklet.Soklet} to "talk" to your {@link HttpServer}.
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 HttpServer}-provided request as input and supplies a {@link MarshaledResponse} as output for the {@link HttpServer} 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 HttpServer} implementations.
100         * <p>
101         * This is used internally by {@link com.soklet.Soklet} instances to "talk" to a {@link HttpServer} via {@link HttpServer#initialize(SokletConfig, RequestHandler)}.  It's the responsibility of the {@link HttpServer} 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 HttpServer} 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 HttpServer} implementation after it has received an HTTP request but prior to writing an HTTP response.
111                 * <p>
112                 * The {@link HttpServer} 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 HttpServer} to be sent over the wire to the client.
115                 *
116                 * @param request               a Soklet {@link Request} representation of the {@link HttpServer}'s internal HTTP request data
117                 * @param requestResultConsumer invoked by {@link com.soklet.Soklet} when it's time for the {@link HttpServer} to write HTTP response data to the client
118                 */
119                void handleRequest(@NonNull Request request,
120                                                                                         @NonNull Consumer<HttpRequestResult> requestResultConsumer);
121        }
122
123        /**
124         * Acquires a builder for {@link HttpServer} 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 HttpServer} 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 HttpServer} instance
140         */
141        @NonNull
142        static HttpServer fromPort(@NonNull Integer port) {
143                return withPort(port).build();
144        }
145
146        /**
147         * Builder used to construct a standard implementation of {@link HttpServer}.
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 requestHeaderTimeout;
163                @Nullable
164                Duration requestBodyTimeout;
165                @Nullable
166                Duration responseWriteIdleTimeout;
167                @Nullable
168                ResponseGzipPolicy responseGzipPolicy;
169                @Nullable
170                RequestDecompressionPolicy requestDecompressionPolicy;
171                @Nullable
172                Duration requestHandlerTimeout;
173                @Nullable
174                Integer requestHandlerConcurrency;
175                @Nullable
176                Integer requestHandlerQueueCapacity;
177                @Nullable
178                Duration socketSelectTimeout;
179                @Nullable
180                Duration shutdownTimeout;
181                @Nullable
182                Integer maximumRequestSizeInBytes;
183                @Nullable
184                Integer maximumHeaderCount;
185                @Nullable
186                Integer maximumHeadersSizeInBytes;
187                @Nullable
188                Integer maximumRequestTargetLengthInBytes;
189                @Nullable
190                Integer requestReadBufferSizeInBytes;
191                @Nullable
192                Integer socketPendingConnectionLimit;
193                @Nullable
194                Integer concurrentConnectionLimit;
195                @Nullable
196                MultipartParser multipartParser;
197                @Nullable
198                Supplier<ExecutorService> requestHandlerExecutorServiceSupplier;
199                @Nullable
200                Supplier<ExecutorService> streamingExecutorServiceSupplier;
201                @Nullable
202                Integer streamingQueueCapacityInBytes;
203                @Nullable
204                Integer streamingChunkSizeInBytes;
205                @Nullable
206                Duration streamingResponseTimeout;
207                @Nullable
208                Duration streamingResponseIdleTimeout;
209                @Nullable
210                IdGenerator<?> idGenerator;
211
212                private Builder(@NonNull Integer port) {
213                        requireNonNull(port);
214                        this.port = port;
215                }
216
217                @NonNull
218                public Builder port(@NonNull Integer port) {
219                        requireNonNull(port);
220                        this.port = port;
221                        return this;
222                }
223
224                @NonNull
225                public Builder host(@Nullable String host) {
226                        this.host = host;
227                        return this;
228                }
229
230                @NonNull
231                public Builder concurrency(@Nullable Integer concurrency) {
232                        this.concurrency = concurrency;
233                        return this;
234                }
235
236                /**
237                 * Sets the maximum duration for reading the HTTP request line and headers.
238                 * <p>
239                 * If this value is not specified, Soklet uses the server default.
240                 *
241                 * @param requestHeaderTimeout the request header timeout, or {@code null} for the default
242                 * @return this builder
243                 */
244                @NonNull
245                public Builder requestHeaderTimeout(@Nullable Duration requestHeaderTimeout) {
246                        this.requestHeaderTimeout = requestHeaderTimeout;
247                        return this;
248                }
249
250                /**
251                 * Sets the maximum duration for reading the HTTP request body after the request
252                 * line and headers have been received.
253                 * <p>
254                 * If this value is not specified, Soklet uses the server default.
255                 *
256                 * @param requestBodyTimeout the request body timeout, or {@code null} for the default
257                 * @return this builder
258                 */
259                @NonNull
260                public Builder requestBodyTimeout(@Nullable Duration requestBodyTimeout) {
261                        this.requestBodyTimeout = requestBodyTimeout;
262                        return this;
263                }
264
265                /**
266                 * Sets the maximum idle duration while writing a non-streaming HTTP response.
267                 * <p>
268                 * The timeout is reset each time response bytes are written to the socket.
269                 * Use {@link Duration#ZERO} to disable this timeout.
270                 * <p>
271                 * If this value is not specified, Soklet uses the server default.
272                 *
273                 * @param responseWriteIdleTimeout the response write idle timeout, or {@code null} for the default
274                 * @return this builder
275                 */
276                @NonNull
277                public Builder responseWriteIdleTimeout(@Nullable Duration responseWriteIdleTimeout) {
278                        this.responseWriteIdleTimeout = responseWriteIdleTimeout;
279                        return this;
280                }
281
282                /**
283                 * Sets the policy used by the standard HTTP server to decide whether eligible finalized
284                 * in-memory responses should be gzipped.
285                 * <p>
286                 * Soklet invokes this policy only after its own HTTP protocol checks pass. For example,
287                 * {@code Accept-Encoding} must permit {@code gzip}, and Soklet will skip streaming, file,
288                 * range, already-encoded, transfer-encoded, bodyless, and otherwise ineligible responses.
289                 * If this value is not specified, response gzip is disabled.
290                 *
291                 * @param responseGzipPolicy the response gzip policy to use, or {@code null} for the default
292                 * @return this builder
293                 */
294                @NonNull
295                public Builder responseGzipPolicy(@Nullable ResponseGzipPolicy responseGzipPolicy) {
296                        this.responseGzipPolicy = responseGzipPolicy;
297                        return this;
298                }
299
300                /**
301                 * Sets the policy used by the standard HTTP server to decide whether and how gzip-compressed
302                 * request bodies are transparently decompressed before request handling.
303                 * <p>
304                 * If this value is not specified, request decompression is disabled and request bodies are passed
305                 * to handlers exactly as received. See {@link RequestDecompressionPolicy} for enabled-mode behavior,
306                 * including decompression-bomb limits and rejection status codes.
307                 *
308                 * @param requestDecompressionPolicy the request decompression policy to use, or {@code null} for the default
309                 * @return this builder
310                 */
311                @NonNull
312                public Builder requestDecompressionPolicy(@Nullable RequestDecompressionPolicy requestDecompressionPolicy) {
313                        this.requestDecompressionPolicy = requestDecompressionPolicy;
314                        return this;
315                }
316
317                @NonNull
318                public Builder requestHandlerTimeout(@Nullable Duration requestHandlerTimeout) {
319                        this.requestHandlerTimeout = requestHandlerTimeout;
320                        return this;
321                }
322
323                @NonNull
324                public Builder requestHandlerConcurrency(@Nullable Integer requestHandlerConcurrency) {
325                        this.requestHandlerConcurrency = requestHandlerConcurrency;
326                        return this;
327                }
328
329                @NonNull
330                public Builder requestHandlerQueueCapacity(@Nullable Integer requestHandlerQueueCapacity) {
331                        this.requestHandlerQueueCapacity = requestHandlerQueueCapacity;
332                        return this;
333                }
334
335                @NonNull
336                public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) {
337                        this.socketSelectTimeout = socketSelectTimeout;
338                        return this;
339                }
340
341                @NonNull
342                public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) {
343                        this.socketPendingConnectionLimit = socketPendingConnectionLimit;
344                        return this;
345                }
346
347                @NonNull
348                public Builder concurrentConnectionLimit(@Nullable Integer concurrentConnectionLimit) {
349                        this.concurrentConnectionLimit = concurrentConnectionLimit;
350                        return this;
351                }
352
353                @NonNull
354                public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) {
355                        this.shutdownTimeout = shutdownTimeout;
356                        return this;
357                }
358
359                /**
360                 * Sets the maximum accepted HTTP request size in bytes.
361                 * <p>
362                 * This limit applies to the whole received HTTP request, including request line,
363                 * headers, transfer framing, and body bytes. Applications that think in terms of
364                 * payload size should leave room for request metadata and protocol framing.
365                 *
366                 * @param maximumRequestSizeInBytes the maximum request size, or {@code null} for the default
367                 * @return this builder
368                 */
369                @NonNull
370                public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) {
371                        this.maximumRequestSizeInBytes = maximumRequestSizeInBytes;
372                        return this;
373                }
374
375                /**
376                 * Sets the maximum number of HTTP header fields accepted in one request.
377                 *
378                 * @param maximumHeaderCount the maximum header count, or {@code null} for the default
379                 * @return this builder
380                 */
381                @NonNull
382                public Builder maximumHeaderCount(@Nullable Integer maximumHeaderCount) {
383                        this.maximumHeaderCount = maximumHeaderCount;
384                        return this;
385                }
386
387                /**
388                 * Sets the maximum accepted HTTP header-section size in bytes.
389                 * <p>
390                 * This limit applies to the header bytes after the request line, including
391                 * header-field line endings and the terminating blank line.
392                 *
393                 * @param maximumHeadersSizeInBytes the maximum headers size, or {@code null} for the default
394                 * @return this builder
395                 */
396                @NonNull
397                public Builder maximumHeadersSizeInBytes(@Nullable Integer maximumHeadersSizeInBytes) {
398                        this.maximumHeadersSizeInBytes = maximumHeadersSizeInBytes;
399                        return this;
400                }
401
402                /**
403                 * Sets the maximum request-target length accepted in bytes.
404                 *
405                 * @param maximumRequestTargetLengthInBytes the maximum request-target length, or {@code null} for the default
406                 * @return this builder
407                 */
408                @NonNull
409                public Builder maximumRequestTargetLengthInBytes(@Nullable Integer maximumRequestTargetLengthInBytes) {
410                        this.maximumRequestTargetLengthInBytes = maximumRequestTargetLengthInBytes;
411                        return this;
412                }
413
414                @NonNull
415                public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) {
416                        this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes;
417                        return this;
418                }
419
420                @NonNull
421                public Builder multipartParser(@Nullable MultipartParser multipartParser) {
422                        this.multipartParser = multipartParser;
423                        return this;
424                }
425
426                @NonNull
427                public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) {
428                        this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier;
429                        return this;
430                }
431
432                /**
433                 * Sets the executor service supplier used to run streaming response producers.
434                 *
435                 * @param streamingExecutorServiceSupplier the executor service supplier, or {@code null} for the default
436                 * @return this builder
437                 */
438                @NonNull
439                public Builder streamingExecutorServiceSupplier(@Nullable Supplier<ExecutorService> streamingExecutorServiceSupplier) {
440                        this.streamingExecutorServiceSupplier = streamingExecutorServiceSupplier;
441                        return this;
442                }
443
444                /**
445                 * Sets the per-stream producer queue capacity in bytes.
446                 *
447                 * @param streamingQueueCapacityInBytes the queue capacity, or {@code null} for the default
448                 * @return this builder
449                 */
450                @NonNull
451                public Builder streamingQueueCapacityInBytes(@Nullable Integer streamingQueueCapacityInBytes) {
452                        this.streamingQueueCapacityInBytes = streamingQueueCapacityInBytes;
453                        return this;
454                }
455
456                /**
457                 * Sets the maximum payload chunk size used for HTTP/1.1 chunked streaming.
458                 *
459                 * @param streamingChunkSizeInBytes the payload chunk size, or {@code null} for the default
460                 * @return this builder
461                 */
462                @NonNull
463                public Builder streamingChunkSizeInBytes(@Nullable Integer streamingChunkSizeInBytes) {
464                        this.streamingChunkSizeInBytes = streamingChunkSizeInBytes;
465                        return this;
466                }
467
468                /**
469                 * Sets the maximum total duration for a streaming response.
470                 * <p>
471                 * Use {@link Duration#ZERO} to disable the timeout.
472                 *
473                 * @param streamingResponseTimeout the streaming response timeout, or {@code null} for the default
474                 * @return this builder
475                 */
476                @NonNull
477                public Builder streamingResponseTimeout(@Nullable Duration streamingResponseTimeout) {
478                        this.streamingResponseTimeout = streamingResponseTimeout;
479                        return this;
480                }
481
482                /**
483                 * Sets the maximum idle duration between bytes produced for a streaming response.
484                 * <p>
485                 * Use {@link Duration#ZERO} to disable the timeout.
486                 *
487                 * @param streamingResponseIdleTimeout the streaming response idle timeout, or {@code null} for the default
488                 * @return this builder
489                 */
490                @NonNull
491                public Builder streamingResponseIdleTimeout(@Nullable Duration streamingResponseIdleTimeout) {
492                        this.streamingResponseIdleTimeout = streamingResponseIdleTimeout;
493                        return this;
494                }
495
496                @NonNull
497                public Builder idGenerator(@Nullable IdGenerator<?> idGenerator) {
498                        this.idGenerator = idGenerator;
499                        return this;
500                }
501
502                @NonNull
503                public HttpServer build() {
504                        return new DefaultHttpServer(this);
505                }
506        }
507}