001/* 002 * Copyright 2022-2025 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 javax.annotation.Nonnull; 020import javax.annotation.Nullable; 021import javax.annotation.concurrent.NotThreadSafe; 022import java.time.Duration; 023import java.util.concurrent.ExecutorService; 024import java.util.function.Consumer; 025import java.util.function.Supplier; 026 027import static java.util.Objects.requireNonNull; 028 029/** 030 * Contract for HTTP server implementations that are designed to be managed by a {@link com.soklet.Soklet} instance. 031 * <p> 032 * <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> 033 * <p> 034 * For example: 035 * <pre>{@code SokletConfig config = SokletConfig.withServer( 036 * Server.withPort(8080).build() 037 * ).build(); 038 * 039 * try (Soklet soklet = Soklet.withConfig(config)) { 040 * soklet.start(); 041 * System.out.println("Soklet started, press [enter] to exit"); 042 * soklet.awaitShutdown(ShutdownTrigger.ENTER_KEY); 043 * }}</pre> 044 * 045 * @author <a href="https://www.revetkn.com">Mark Allen</a> 046 */ 047public interface Server extends AutoCloseable { 048 /** 049 * Starts the server, which makes it able to accept requests from clients. 050 * <p> 051 * If the server is already started, no action is taken. 052 * <p> 053 * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong> 054 */ 055 void start(); 056 057 /** 058 * Stops the server, which makes it unable to accept requests from clients. 059 * <p> 060 * If the server is already stopped, no action is taken. 061 * <p> 062 * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong> 063 */ 064 void stop(); 065 066 /** 067 * Is this server started (that is, able to handle requests from clients)? 068 * 069 * @return {@code true} if the server is started, {@code false} otherwise 070 */ 071 @Nonnull 072 Boolean isStarted(); 073 074 /** 075 * 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}. 076 * <p> 077 * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong> 078 * 079 * @param sokletConfig configuration for the Soklet instance that controls this server 080 * @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 081 */ 082 void initialize(@Nonnull SokletConfig sokletConfig, 083 @Nonnull RequestHandler requestHandler); 084 085 /** 086 * {@link AutoCloseable}-enabled synonym for {@link #stop()}. 087 * <p> 088 * <strong>This method is designed for internal use by {@link com.soklet.Soklet} only and should not be invoked elsewhere.</strong> 089 * 090 * @throws Exception if an exception occurs while stopping the server 091 */ 092 @Override 093 default void close() throws Exception { 094 stop(); 095 } 096 097 /** 098 * Request/response processing contract for {@link Server} implementations. 099 * <p> 100 * 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. 101 * <p> 102 * <strong>Most Soklet applications will use Soklet's default {@link Server} implementation and therefore do not need to implement this interface directly.</strong> 103 * 104 * @author <a href="https://www.revetkn.com">Mark Allen</a> 105 */ 106 @FunctionalInterface 107 interface RequestHandler { 108 /** 109 * Callback to be invoked by a {@link Server} implementation after it has received an HTTP request but prior to writing an HTTP response. 110 * <p> 111 * 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. 112 * <p> 113 * 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. 114 * 115 * @param request a Soklet {@link Request} representation of the {@link Server}'s internal HTTP request data 116 * @param requestResultConsumer invoked by {@link com.soklet.Soklet} when it's time for the {@link Server} to write HTTP response data to the client 117 */ 118 void handleRequest(@Nonnull Request request, 119 @Nonnull Consumer<RequestResult> requestResultConsumer); 120 } 121 122 /** 123 * Acquires a builder for {@link Server} instances. 124 * 125 * @param port the port number on which the server should listen 126 * @return the builder 127 */ 128 @Nonnull 129 static Builder withPort(@Nonnull Integer port) { 130 requireNonNull(port); 131 return new Builder(port); 132 } 133 134 /** 135 * Builder used to construct a standard implementation of {@link Server}. 136 * <p> 137 * This class is intended for use by a single thread. 138 * 139 * @author <a href="https://www.revetkn.com">Mark Allen</a> 140 */ 141 @NotThreadSafe 142 final class Builder { 143 @Nonnull 144 Integer port; 145 @Nullable 146 String host; 147 @Nullable 148 Integer concurrency; 149 @Nullable 150 Duration requestTimeout; 151 @Nullable 152 Duration socketSelectTimeout; 153 @Nullable 154 Duration shutdownTimeout; 155 @Nullable 156 Integer maximumRequestSizeInBytes; 157 @Nullable 158 Integer requestReadBufferSizeInBytes; 159 @Nullable 160 Integer socketPendingConnectionLimit; 161 @Nullable 162 MultipartParser multipartParser; 163 @Nullable 164 Supplier<ExecutorService> requestHandlerExecutorServiceSupplier; 165 @Nullable 166 IdGenerator<?> idGenerator; 167 168 @Nonnull 169 private Builder(@Nonnull Integer port) { 170 requireNonNull(port); 171 this.port = port; 172 } 173 174 @Nonnull 175 public Builder port(@Nonnull Integer port) { 176 requireNonNull(port); 177 this.port = port; 178 return this; 179 } 180 181 @Nonnull 182 public Builder host(@Nullable String host) { 183 this.host = host; 184 return this; 185 } 186 187 @Nonnull 188 public Builder concurrency(@Nullable Integer concurrency) { 189 this.concurrency = concurrency; 190 return this; 191 } 192 193 @Nonnull 194 public Builder requestTimeout(@Nullable Duration requestTimeout) { 195 this.requestTimeout = requestTimeout; 196 return this; 197 } 198 199 @Nonnull 200 public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) { 201 this.socketSelectTimeout = socketSelectTimeout; 202 return this; 203 } 204 205 @Nonnull 206 public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) { 207 this.socketPendingConnectionLimit = socketPendingConnectionLimit; 208 return this; 209 } 210 211 @Nonnull 212 public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) { 213 this.shutdownTimeout = shutdownTimeout; 214 return this; 215 } 216 217 @Nonnull 218 public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) { 219 this.maximumRequestSizeInBytes = maximumRequestSizeInBytes; 220 return this; 221 } 222 223 @Nonnull 224 public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) { 225 this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes; 226 return this; 227 } 228 229 @Nonnull 230 public Builder multipartParser(@Nullable MultipartParser multipartParser) { 231 this.multipartParser = multipartParser; 232 return this; 233 } 234 235 @Nonnull 236 public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) { 237 this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier; 238 return this; 239 } 240 241 @Nonnull 242 public Builder idGenerator(@Nullable IdGenerator<?> idGenerator) { 243 this.idGenerator = idGenerator; 244 return this; 245 } 246 247 @Nonnull 248 public Server build() { 249 return new DefaultServer(this); 250 } 251 } 252}