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 requestHandlerTimeout; 167 @Nullable 168 Integer requestHandlerConcurrency; 169 @Nullable 170 Integer requestHandlerQueueCapacity; 171 @Nullable 172 Duration socketSelectTimeout; 173 @Nullable 174 Duration shutdownTimeout; 175 @Nullable 176 Integer maximumRequestSizeInBytes; 177 @Nullable 178 Integer maximumHeaderCount; 179 @Nullable 180 Integer maximumRequestTargetLengthInBytes; 181 @Nullable 182 Integer requestReadBufferSizeInBytes; 183 @Nullable 184 Integer socketPendingConnectionLimit; 185 @Nullable 186 Integer maximumConnections; 187 @Nullable 188 MultipartParser multipartParser; 189 @Nullable 190 Supplier<ExecutorService> requestHandlerExecutorServiceSupplier; 191 @Nullable 192 Supplier<ExecutorService> streamingExecutorServiceSupplier; 193 @Nullable 194 Integer streamingQueueCapacityInBytes; 195 @Nullable 196 Integer streamingChunkSizeInBytes; 197 @Nullable 198 Duration streamingResponseTimeout; 199 @Nullable 200 Duration streamingResponseIdleTimeout; 201 @Nullable 202 IdGenerator<?> idGenerator; 203 204 @NonNull 205 private Builder(@NonNull Integer port) { 206 requireNonNull(port); 207 this.port = port; 208 } 209 210 @NonNull 211 public Builder port(@NonNull Integer port) { 212 requireNonNull(port); 213 this.port = port; 214 return this; 215 } 216 217 @NonNull 218 public Builder host(@Nullable String host) { 219 this.host = host; 220 return this; 221 } 222 223 @NonNull 224 public Builder concurrency(@Nullable Integer concurrency) { 225 this.concurrency = concurrency; 226 return this; 227 } 228 229 /** 230 * Sets the maximum duration for reading the HTTP request line and headers. 231 * <p> 232 * If this value is not specified, Soklet uses the server default. 233 * 234 * @param requestHeaderTimeout the request header timeout, or {@code null} for the default 235 * @return this builder 236 */ 237 @NonNull 238 public Builder requestHeaderTimeout(@Nullable Duration requestHeaderTimeout) { 239 this.requestHeaderTimeout = requestHeaderTimeout; 240 return this; 241 } 242 243 /** 244 * Sets the maximum duration for reading the HTTP request body after the request 245 * line and headers have been received. 246 * <p> 247 * If this value is not specified, Soklet uses the server default. 248 * 249 * @param requestBodyTimeout the request body timeout, or {@code null} for the default 250 * @return this builder 251 */ 252 @NonNull 253 public Builder requestBodyTimeout(@Nullable Duration requestBodyTimeout) { 254 this.requestBodyTimeout = requestBodyTimeout; 255 return this; 256 } 257 258 @NonNull 259 public Builder requestHandlerTimeout(@Nullable Duration requestHandlerTimeout) { 260 this.requestHandlerTimeout = requestHandlerTimeout; 261 return this; 262 } 263 264 @NonNull 265 public Builder requestHandlerConcurrency(@Nullable Integer requestHandlerConcurrency) { 266 this.requestHandlerConcurrency = requestHandlerConcurrency; 267 return this; 268 } 269 270 @NonNull 271 public Builder requestHandlerQueueCapacity(@Nullable Integer requestHandlerQueueCapacity) { 272 this.requestHandlerQueueCapacity = requestHandlerQueueCapacity; 273 return this; 274 } 275 276 @NonNull 277 public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) { 278 this.socketSelectTimeout = socketSelectTimeout; 279 return this; 280 } 281 282 @NonNull 283 public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) { 284 this.socketPendingConnectionLimit = socketPendingConnectionLimit; 285 return this; 286 } 287 288 @NonNull 289 public Builder maximumConnections(@Nullable Integer maximumConnections) { 290 this.maximumConnections = maximumConnections; 291 return this; 292 } 293 294 @NonNull 295 public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) { 296 this.shutdownTimeout = shutdownTimeout; 297 return this; 298 } 299 300 /** 301 * Sets the maximum accepted HTTP request size in bytes. 302 * <p> 303 * This limit applies to the whole received HTTP request, including request line, 304 * headers, transfer framing, and body bytes. Applications that think in terms of 305 * payload size should leave room for request metadata and protocol framing. 306 * 307 * @param maximumRequestSizeInBytes the maximum request size, or {@code null} for the default 308 * @return this builder 309 */ 310 @NonNull 311 public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) { 312 this.maximumRequestSizeInBytes = maximumRequestSizeInBytes; 313 return this; 314 } 315 316 /** 317 * Sets the maximum number of HTTP header fields accepted in one request. 318 * 319 * @param maximumHeaderCount the maximum header count, or {@code null} for the default 320 * @return this builder 321 */ 322 @NonNull 323 public Builder maximumHeaderCount(@Nullable Integer maximumHeaderCount) { 324 this.maximumHeaderCount = maximumHeaderCount; 325 return this; 326 } 327 328 /** 329 * Sets the maximum request-target length accepted in bytes. 330 * 331 * @param maximumRequestTargetLengthInBytes the maximum request-target length, or {@code null} for the default 332 * @return this builder 333 */ 334 @NonNull 335 public Builder maximumRequestTargetLengthInBytes(@Nullable Integer maximumRequestTargetLengthInBytes) { 336 this.maximumRequestTargetLengthInBytes = maximumRequestTargetLengthInBytes; 337 return this; 338 } 339 340 @NonNull 341 public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) { 342 this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes; 343 return this; 344 } 345 346 @NonNull 347 public Builder multipartParser(@Nullable MultipartParser multipartParser) { 348 this.multipartParser = multipartParser; 349 return this; 350 } 351 352 @NonNull 353 public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) { 354 this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier; 355 return this; 356 } 357 358 /** 359 * Sets the executor service supplier used to run streaming response producers. 360 * 361 * @param streamingExecutorServiceSupplier the executor service supplier, or {@code null} for the default 362 * @return this builder 363 */ 364 @NonNull 365 public Builder streamingExecutorServiceSupplier(@Nullable Supplier<ExecutorService> streamingExecutorServiceSupplier) { 366 this.streamingExecutorServiceSupplier = streamingExecutorServiceSupplier; 367 return this; 368 } 369 370 /** 371 * Sets the per-stream producer queue capacity in bytes. 372 * 373 * @param streamingQueueCapacityInBytes the queue capacity, or {@code null} for the default 374 * @return this builder 375 */ 376 @NonNull 377 public Builder streamingQueueCapacityInBytes(@Nullable Integer streamingQueueCapacityInBytes) { 378 this.streamingQueueCapacityInBytes = streamingQueueCapacityInBytes; 379 return this; 380 } 381 382 /** 383 * Sets the maximum payload chunk size used for HTTP/1.1 chunked streaming. 384 * 385 * @param streamingChunkSizeInBytes the payload chunk size, or {@code null} for the default 386 * @return this builder 387 */ 388 @NonNull 389 public Builder streamingChunkSizeInBytes(@Nullable Integer streamingChunkSizeInBytes) { 390 this.streamingChunkSizeInBytes = streamingChunkSizeInBytes; 391 return this; 392 } 393 394 /** 395 * Sets the maximum total duration for a streaming response. 396 * <p> 397 * Use {@link Duration#ZERO} to disable the timeout. 398 * 399 * @param streamingResponseTimeout the streaming response timeout, or {@code null} for the default 400 * @return this builder 401 */ 402 @NonNull 403 public Builder streamingResponseTimeout(@Nullable Duration streamingResponseTimeout) { 404 this.streamingResponseTimeout = streamingResponseTimeout; 405 return this; 406 } 407 408 /** 409 * Sets the maximum idle duration between bytes produced for a streaming response. 410 * <p> 411 * Use {@link Duration#ZERO} to disable the timeout. 412 * 413 * @param streamingResponseIdleTimeout the streaming response idle timeout, or {@code null} for the default 414 * @return this builder 415 */ 416 @NonNull 417 public Builder streamingResponseIdleTimeout(@Nullable Duration streamingResponseIdleTimeout) { 418 this.streamingResponseIdleTimeout = streamingResponseIdleTimeout; 419 return this; 420 } 421 422 @NonNull 423 public Builder idGenerator(@Nullable IdGenerator<?> idGenerator) { 424 this.idGenerator = idGenerator; 425 return this; 426 } 427 428 @NonNull 429 public HttpServer build() { 430 return new DefaultHttpServer(this); 431 } 432 } 433}