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) 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 @Nonnull 123 static Builder withPort(@Nonnull Integer port) { 124 requireNonNull(port); 125 return new Builder(port); 126 } 127 128 /** 129 * Builder used to construct a standard implementation of {@link Server}. 130 * <p> 131 * This class is intended for use by a single thread. 132 * 133 * @author <a href="https://www.revetkn.com">Mark Allen</a> 134 */ 135 @NotThreadSafe 136 final class Builder { 137 @Nonnull 138 Integer port; 139 @Nullable 140 String host; 141 @Nullable 142 Integer concurrency; 143 @Nullable 144 Duration requestTimeout; 145 @Nullable 146 Duration socketSelectTimeout; 147 @Nullable 148 Duration shutdownTimeout; 149 @Nullable 150 Integer maximumRequestSizeInBytes; 151 @Nullable 152 Integer requestReadBufferSizeInBytes; 153 @Nullable 154 Integer socketPendingConnectionLimit; 155 @Nullable 156 MultipartParser multipartParser; 157 @Nullable 158 Supplier<ExecutorService> requestHandlerExecutorServiceSupplier; 159 160 @Nonnull 161 private Builder(@Nonnull Integer port) { 162 requireNonNull(port); 163 this.port = port; 164 } 165 166 @Nonnull 167 public Builder port(@Nonnull Integer port) { 168 requireNonNull(port); 169 this.port = port; 170 return this; 171 } 172 173 @Nonnull 174 public Builder host(@Nullable String host) { 175 this.host = host; 176 return this; 177 } 178 179 @Nonnull 180 public Builder concurrency(@Nullable Integer concurrency) { 181 this.concurrency = concurrency; 182 return this; 183 } 184 185 @Nonnull 186 public Builder requestTimeout(@Nullable Duration requestTimeout) { 187 this.requestTimeout = requestTimeout; 188 return this; 189 } 190 191 @Nonnull 192 public Builder socketSelectTimeout(@Nullable Duration socketSelectTimeout) { 193 this.socketSelectTimeout = socketSelectTimeout; 194 return this; 195 } 196 197 @Nonnull 198 public Builder socketPendingConnectionLimit(@Nullable Integer socketPendingConnectionLimit) { 199 this.socketPendingConnectionLimit = socketPendingConnectionLimit; 200 return this; 201 } 202 203 @Nonnull 204 public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) { 205 this.shutdownTimeout = shutdownTimeout; 206 return this; 207 } 208 209 @Nonnull 210 public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) { 211 this.maximumRequestSizeInBytes = maximumRequestSizeInBytes; 212 return this; 213 } 214 215 @Nonnull 216 public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) { 217 this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes; 218 return this; 219 } 220 221 @Nonnull 222 public Builder multipartParser(@Nullable MultipartParser multipartParser) { 223 this.multipartParser = multipartParser; 224 return this; 225 } 226 227 @Nonnull 228 public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) { 229 this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier; 230 return this; 231 } 232 233 @Nonnull 234 public Server build() { 235 return new DefaultServer(this); 236 } 237 } 238}