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 Server} (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.withServer( 037 * Server.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 Server 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 Server} will invoke this method exactly once at initialization time - this allows {@link com.soklet.Soklet} to "talk" to your {@link Server}. 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 Server}-provided request as input and supplies a {@link MarshaledResponse} as output for the {@link Server} 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 Server} implementations. 100 * <p> 101 * 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. 102 * <p> 103 * <strong>Most Soklet applications will use Soklet's default {@link Server} 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 Server} implementation after it has received an HTTP request but prior to writing an HTTP response. 111 * <p> 112 * 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. 113 * <p> 114 * 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. 115 * 116 * @param request a Soklet {@link Request} representation of the {@link Server}'s internal HTTP request data 117 * @param requestResultConsumer invoked by {@link com.soklet.Soklet} when it's time for the {@link Server} to write HTTP response data to the client 118 */ 119 void handleRequest(@NonNull Request request, 120 @NonNull Consumer<RequestResult> requestResultConsumer); 121 } 122 123 /** 124 * Acquires a builder for {@link Server} 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 Server} 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 Server} instance 140 */ 141 @NonNull 142 static Server fromPort(@NonNull Integer port) { 143 return withPort(port).build(); 144 } 145 146 /** 147 * Builder used to construct a standard implementation of {@link Server}. 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 requestTimeout; 163 @Nullable 164 Duration requestHandlerTimeout; 165 @Nullable 166 Integer requestHandlerConcurrency; 167 @Nullable 168 Integer requestHandlerQueueCapacity; 169 @Nullable 170 Duration socketSelectTimeout; 171 @Nullable 172 Duration shutdownTimeout; 173 @Nullable 174 Integer maximumRequestSizeInBytes; 175 @Nullable 176 Integer requestReadBufferSizeInBytes; 177 @Nullable 178 Integer socketPendingConnectionLimit; 179 @Nullable 180 Integer maximumConnections; 181 @Nullable 182 MultipartParser multipartParser; 183 @Nullable 184 Supplier<ExecutorService> requestHandlerExecutorServiceSupplier; 185 @Nullable 186 IdGenerator<?> idGenerator; 187 188 @NonNull 189 private Builder(@NonNull Integer port) { 190 requireNonNull(port); 191 this.port = port; 192 } 193 194 @NonNull 195 public Builder port(@NonNull Integer port) { 196 requireNonNull(port); 197 this.port = port; 198 return this; 199 } 200 201 @NonNull 202 public Builder host(@Nullable String host) { 203 this.host = host; 204 return this; 205 } 206 207 @NonNull 208 public Builder concurrency(@Nullable Integer concurrency) { 209 this.concurrency = concurrency; 210 return this; 211 } 212 213 @NonNull 214 public Builder requestTimeout(@Nullable Duration requestTimeout) { 215 this.requestTimeout = requestTimeout; 216 return this; 217 } 218 219 @NonNull 220 public Builder requestHandlerTimeout(@Nullable Duration requestHandlerTimeout) { 221 this.requestHandlerTimeout = requestHandlerTimeout; 222 return this; 223 } 224 225 @NonNull 226 public Builder requestHandlerConcurrency(@Nullable Integer requestHandlerConcurrency) { 227 this.requestHandlerConcurrency = requestHandlerConcurrency; 228 return this; 229 } 230 231 @NonNull 232 public Builder requestHandlerQueueCapacity(@Nullable Integer requestHandlerQueueCapacity) { 233 this.requestHandlerQueueCapacity = requestHandlerQueueCapacity; 234 return this; 235 } 236 237 @NonNull 238 public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) { 239 this.socketSelectTimeout = socketSelectTimeout; 240 return this; 241 } 242 243 @NonNull 244 public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) { 245 this.socketPendingConnectionLimit = socketPendingConnectionLimit; 246 return this; 247 } 248 249 @NonNull 250 public Builder maximumConnections(@Nullable Integer maximumConnections) { 251 this.maximumConnections = maximumConnections; 252 return this; 253 } 254 255 @NonNull 256 public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) { 257 this.shutdownTimeout = shutdownTimeout; 258 return this; 259 } 260 261 @NonNull 262 public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) { 263 this.maximumRequestSizeInBytes = maximumRequestSizeInBytes; 264 return this; 265 } 266 267 @NonNull 268 public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) { 269 this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes; 270 return this; 271 } 272 273 @NonNull 274 public Builder multipartParser(@Nullable MultipartParser multipartParser) { 275 this.multipartParser = multipartParser; 276 return this; 277 } 278 279 @NonNull 280 public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) { 281 this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier; 282 return this; 283 } 284 285 @NonNull 286 public Builder idGenerator(@Nullable IdGenerator<?> idGenerator) { 287 this.idGenerator = idGenerator; 288 return this; 289 } 290 291 @NonNull 292 public Server build() { 293 return new DefaultServer(this); 294 } 295 } 296}