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}