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